diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..0361e93
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,48 @@
+
+RewriteEngine On
+
+# Rewrite rules
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^index/?$ index.php [NC,L]
+
+RewriteRule ^api/?$ webapi/api.php?class=login&action=create [NC,L]
+RewriteRule ^api/([^/]+)/?$ webapi/api.php?class=$1&action=list [L,QSA]
+RewriteRule ^api/([^/]+)/([^/]+)/?$ webapi/api.php?class=$1&action=$2 [L,QSA]
+
+# Exchange
+RewriteRule ^exchange/?$ exchange.php [NC,L]
+RewriteRule ^exchange/([^/]+)/?$ exchange.php?oid=$1 [L,QSA]
+RewriteRule ^exchange/([^/]+)/([^/]+)/?$ exchange.php?oid=$1&uid=$2 [L,QSA]
+
+# OneDrive
+RewriteRule ^onedrive/?$ onedrive.php [NC,L]
+RewriteRule ^onedrive/([^/]+)/?$ onedrive.php?oid=$1 [L,QSA]
+RewriteRule ^onedrive/([^/]+)/([^/]+)/?$ onedrive.php?oid=$1&uid=$2 [L,QSA]
+
+# SharePoint
+RewriteRule ^sharepoint/?$ sharepoint.php [NC,L]
+RewriteRule ^sharepoint/([^/]+)/?$ sharepoint.php?oid=$1 [L,QSA]
+RewriteRule ^sharepoint/([^/]+)/([^/]+)/?$ sharepoint.php?oid=$1&sid=$2 [L,QSA]
+RewriteRule ^sharepoint/([^/]+)/([^/]+)/([^/]+)/?$ sharepoint.php?oid=$1&sid=$2&cid=$3 [L,QSA]
+RewriteRule ^sharepoint/([^/]+)/([^/]+)/([^/]+)/([^/]+)/?$ sharepoint.php?oid=$1&sid=$2&cid=$3&type=$4 [L,QSA]
+
+# Disable directory browsing
+Options -Indexes
+Options +FollowSymLinks
+
+
+
+Order allow,deny
+Deny from all
+
+
+
+Order allow,deny
+Deny from all
+
+
+
+Order allow,deny
+Deny from all
+
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0606262
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 VeeamHUB
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 2e799fa..109a063 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,71 @@
-# martini
-Project Martini as shown at VeeamON 2019: Additional helpdesk and central management feature for Veeam Backup for Microsoft Office 365
+Project Martini: Additional helpdesk and central management feature for Veeam Backup for Microsoft Office 365
+==================
+
+## About
+Project Martini adds an extra layer on top of existing Veeam Backup for Microsoft Office 365 installations. This can be used as a helpdesk and central management solution.
+
+It allows the following features:
+- Create tenants/GEO based locations
+- Deploy Veeam Backup for Microsoft Office 365 in AWS via Terraform
+- Manage 1 or more Veeam Backup for Microsoft Office 365 installations
+- A web interface which provides central management and self-service restore capabilities
+- A command line for automation and initial setup
+- An API for integration with 3rd party solutions
+
+## Requirements
+- Linux VM (Ubuntu/Debian are fully tested and supported)
+
+## Installation
+
+
+## Configuration
+
+
+## Usage
+Open a webbrowser and go to index.php. From here you can either login as an admin or a tenant.
+
+## Dependencies for the web interface
+Make sure you download dependencies using `composer`.
+
+For more information on how to install `composer`:
+- Linux (https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx)
+- Windows (https://getcomposer.org/doc/00-intro.md#installation-windows)
+
+This project leverages a mixture HTML, PHP and Javascript. The following libraries are used:
+- [Flatpickr.js](http://flatpickr.js.org/)
+- [Font Awesome](http://fontawesome.com/)
+- [GuzzleHTTP](https://github.com/guzzle/guzzle)
+- [jQuery](https://jquery.com/)
+- [SweetAlert2](https://sweetalert2.github.io)
+- [Twitter Bootstrap](http://getbootstrap.com/)
+
+It is required to have a webserver running with PHP5 or higher and the mod_rewrite module enabled. The easiest way to do this is leverage a Linux VM with Apache2.
+
+As an example you can use the following [Linux Ubuntu with Apache guide](https://www.linode.com/docs/web-servers/lamp/install-lamp-stack-on-ubuntu-16-04).
+
+This portal leverages rewrite rules via .htaccess and therefor mod_rewrite needs to be enabled in Apache. More information on this can be found via [Enabling mod_rewrite for Apache running on Linux Ubuntu](https://www.digitalocean.com/community/tutorials/how-to-rewrite-urls-with-mod_rewrite-for-apache-on-ubuntu-16-04).
+
+**Important step**
+
+Disable MultiView within the directory document root for Apache. This can be done my modifying the default site configuration and set it as below:
+```
+
+ Options Indexes FollowSymLinks
+ AllowOverride All
+ Require all granted
+
+```
+**It is advised to increase or disable the PHP maximum execution time limit.**
+This can modified in the php.ini file as described per [changing the maximum execution time limit](https://www.simplified.guide/php/increase-max-execution-time)
+
+## Questions and feature request
+Please use the GitHub issue tracker(https://github.com/veeamhub/martini/issues) for any questions or feature requests.
+
+## Distributed under MIT license
+Copyright (c) 2019 VeeamHUB
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..f740d1c
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "veeam/project-martini",
+ "description": "Project Martini",
+ "authors": [
+ {
+ "name": "Niels Engelen",
+ "email": "niels.engelen@veeam.com"
+ },
+ {
+ "name": "Timothy Dewin",
+ "email": "timothy.dewin@veeam.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Terraform\\": "core/"
+ }
+ },
+ "require": {
+ "aws/aws-sdk-php": "~3",
+ "components/jquery": "3.2",
+ "twbs/bootstrap": "3.3.7",
+ "symfony/process": "^4.2",
+ "fostam/getopts": "^1.1"
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..8db5fb0
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,620 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "c70fb57861821235b31c6e8fa78264ca",
+ "packages": [
+ {
+ "name": "aws/aws-sdk-php",
+ "version": "3.93.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/aws/aws-sdk-php.git",
+ "reference": "ae3f4eb786b1560d07bc4f01536f48835bebf3d1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/ae3f4eb786b1560d07bc4f01536f48835bebf3d1",
+ "reference": "ae3f4eb786b1560d07bc4f01536f48835bebf3d1",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-simplexml": "*",
+ "guzzlehttp/guzzle": "^5.3.3|^6.2.1",
+ "guzzlehttp/promises": "~1.0",
+ "guzzlehttp/psr7": "^1.4.1",
+ "mtdowling/jmespath.php": "~2.2",
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "andrewsville/php-token-reflection": "^1.4",
+ "aws/aws-php-sns-message-validator": "~1.0",
+ "behat/behat": "~3.0",
+ "doctrine/cache": "~1.4",
+ "ext-dom": "*",
+ "ext-openssl": "*",
+ "ext-pcntl": "*",
+ "ext-sockets": "*",
+ "nette/neon": "^2.3",
+ "phpunit/phpunit": "^4.8.35|^5.4.3",
+ "psr/cache": "^1.0",
+ "psr/simple-cache": "^1.0"
+ },
+ "suggest": {
+ "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
+ "doctrine/cache": "To use the DoctrineCacheAdapter",
+ "ext-curl": "To send requests using cURL",
+ "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages",
+ "ext-sockets": "To use client-side monitoring"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Aws\\": "src/"
+ },
+ "files": [
+ "src/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Amazon Web Services",
+ "homepage": "http://aws.amazon.com"
+ }
+ ],
+ "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
+ "homepage": "http://aws.amazon.com/sdkforphp",
+ "keywords": [
+ "amazon",
+ "aws",
+ "cloud",
+ "dynamodb",
+ "ec2",
+ "glacier",
+ "s3",
+ "sdk"
+ ],
+ "time": "2019-05-06T18:08:54+00:00"
+ },
+ {
+ "name": "components/jquery",
+ "version": "3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/components/jquery.git",
+ "reference": "bad7f6f9b694c9473a3aa9028185760ce09bb7e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/components/jquery/zipball/bad7f6f9b694c9473a3aa9028185760ce09bb7e5",
+ "reference": "bad7f6f9b694c9473a3aa9028185760ce09bb7e5",
+ "shasum": ""
+ },
+ "type": "component",
+ "extra": {
+ "component": {
+ "scripts": [
+ "jquery.js"
+ ],
+ "files": [
+ "jquery.min.js",
+ "jquery.min.map",
+ "jquery.slim.js",
+ "jquery.slim.min.js",
+ "jquery.slim.min.map"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "JS Foundation and other contributors"
+ }
+ ],
+ "description": "jQuery JavaScript Library",
+ "homepage": "http://jquery.com",
+ "time": "2017-03-17T13:52:40+00:00"
+ },
+ {
+ "name": "fostam/getopts",
+ "version": "v1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/fostam/php-getopts.git",
+ "reference": "d1f1726aa1b281b15f05cb1cd775bdf5316f6095"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/fostam/php-getopts/zipball/d1f1726aa1b281b15f05cb1cd775bdf5316f6095",
+ "reference": "d1f1726aa1b281b15f05cb1cd775bdf5316f6095",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~5.6|~7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~5.5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Fostam\\GetOpts\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Stefan Stammler",
+ "email": "stefan@fostam.org"
+ }
+ ],
+ "description": "Flexible PHP command line argument parser with automated help message generation and validation support",
+ "keywords": [
+ "argument",
+ "command line",
+ "getopt",
+ "option",
+ "parser",
+ "parsing",
+ "script",
+ "shell"
+ ],
+ "time": "2017-11-22T19:29:07+00:00"
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "6.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
+ "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/promises": "^1.0",
+ "guzzlehttp/psr7": "^1.4",
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+ "psr/log": "^1.0"
+ },
+ "suggest": {
+ "psr/log": "Required for using the Log middleware"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.3-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ],
+ "time": "2018-04-22T15:46:56+00:00"
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "v1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ],
+ "time": "2016-12-20T10:07:11+00:00"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "1.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "9f83dded91781a01c63574e387eaa769be769115"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115",
+ "reference": "9f83dded91781a01c63574e387eaa769be769115",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "time": "2018-12-04T20:46:45+00:00"
+ },
+ {
+ "name": "mtdowling/jmespath.php",
+ "version": "2.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jmespath/jmespath.php.git",
+ "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/adcc9531682cf87dfda21e1fd5d0e7a41d292fac",
+ "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "bin": [
+ "bin/jp.php"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "JmesPath\\": "src/"
+ },
+ "files": [
+ "src/JmesPath.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Declaratively specify how to extract elements from a JSON document",
+ "keywords": [
+ "json",
+ "jsonpath"
+ ],
+ "time": "2016-12-03T22:08:25+00:00"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "time": "2016-08-06T14:39:51+00:00"
+ },
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "2.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
+ "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~3.7.0",
+ "satooshi/php-coveralls": ">=1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "time": "2016-02-11T07:05:27+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v4.2.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "8cf39fb4ccff793340c258ee7760fd40bfe745fe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/8cf39fb4ccff793340c258ee7760fd40bfe745fe",
+ "reference": "8cf39fb4ccff793340c258ee7760fd40bfe745fe",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com",
+ "time": "2019-04-10T16:20:36+00:00"
+ },
+ {
+ "name": "twbs/bootstrap",
+ "version": "v3.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/twbs/bootstrap.git",
+ "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/twbs/bootstrap/zipball/0b9c4a4007c44201dce9a6cc1a38407005c26c86",
+ "reference": "0b9c4a4007c44201dce9a6cc1a38407005c26c86",
+ "shasum": ""
+ },
+ "replace": {
+ "twitter/bootstrap": "self.version"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.3.x-dev"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jacob Thornton",
+ "email": "jacobthornton@gmail.com"
+ },
+ {
+ "name": "Mark Otto",
+ "email": "markdotto@gmail.com"
+ }
+ ],
+ "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.",
+ "homepage": "http://getbootstrap.com",
+ "keywords": [
+ "JS",
+ "css",
+ "framework",
+ "front-end",
+ "less",
+ "mobile-first",
+ "responsive",
+ "web"
+ ],
+ "time": "2016-07-25T15:51:55+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": []
+}
diff --git a/core/Blocks/Block.php b/core/Blocks/Block.php
new file mode 100644
index 0000000..4e9aab5
--- /dev/null
+++ b/core/Blocks/Block.php
@@ -0,0 +1,42 @@
+_block = $block;
+ $this->_type = $type;
+ $this->_name = $name;
+ }
+
+ public function &__get($name) {
+ if (array_key_exists($name, $this->_data)) {
+ return $this->_data[$name];
+ }
+ }
+
+ public function __set($name, $value) {
+ $this->_data[$name] = $value;
+ }
+
+ public function getType() {
+ return $this->_type;
+ }
+
+ public function getName() {
+ return $this->_name;
+ }
+
+ public function toArray() {
+ return [$this->_block => [$this->_type => $this->_data]];
+ }
+
+ public function getData() {
+ return $this->_data;
+ }
+
+ public function toJson() {
+ return json_encode($this->terraform, JSON_PRETTY_PRINT);
+ }
+}
\ No newline at end of file
diff --git a/core/Blocks/Data.php b/core/Blocks/Data.php
new file mode 100644
index 0000000..afe0ad5
--- /dev/null
+++ b/core/Blocks/Data.php
@@ -0,0 +1,21 @@
+_block => [$this->_type => [$this->_name => $this->_data]]];
+ }
+
+ public function getTfProp($property = 'id', $encapsulate = true) {
+ $resource = "{$this->_type}.{$this->_name}.{$property}";
+
+ if ($encapsulate) {
+ $resource = '${data.' . $resource . '}';
+ }
+ return $resource;
+ }
+}
\ No newline at end of file
diff --git a/core/Blocks/Output.php b/core/Blocks/Output.php
new file mode 100644
index 0000000..fd3f157
--- /dev/null
+++ b/core/Blocks/Output.php
@@ -0,0 +1,8 @@
+_block => [$this->_type => [$this->_name => $this->_data]]];
+ }
+
+ public function getTfProp($property = 'id', $encapsulate = true) {
+ $resource = "{$this->_type}.{$this->_name}.{$property}";
+
+ if ($encapsulate) {
+ $resource = '${' . $resource . '}';
+ }
+ return $resource;
+ }
+}
\ No newline at end of file
diff --git a/core/Blocks/Variable.php b/core/Blocks/Variable.php
new file mode 100644
index 0000000..219ded3
--- /dev/null
+++ b/core/Blocks/Variable.php
@@ -0,0 +1,9 @@
+default = $variableValues;
+ }
+}
\ No newline at end of file
diff --git a/core/Helpers/Aws/Aws.php b/core/Helpers/Aws/Aws.php
new file mode 100644
index 0000000..e7093cc
--- /dev/null
+++ b/core/Helpers/Aws/Aws.php
@@ -0,0 +1,30 @@
+aws = new Sdk([
+ 'region' => $region,
+ 'version' => 'latest',
+ 'credentials' => [
+ 'key' => $accesskey,
+ 'secret' => $secretkey,
+ ],
+ ]);
+ }
+
+ public function listVpcs($options = [], $fullResponse = false) {
+ $ec2 = $this->aws->createEc2();
+ $result = $ec2->describeVpcs($options);
+
+ return $fullResponse ? $result->toArray() : array_column($result->toArray()['Vpcs'], 'VpcId');
+ }
+
+ public function getSdk() {
+ return $this->aws;
+ }
+}
\ No newline at end of file
diff --git a/core/Macros/Aws/Aws.php b/core/Macros/Aws/Aws.php
new file mode 100644
index 0000000..f092716
--- /dev/null
+++ b/core/Macros/Aws/Aws.php
@@ -0,0 +1,38 @@
+ ['0.0.0.0/0'],
+ 'from_port' => 0,
+ 'to_port' => 0,
+ 'protocol' => "-1",
+ ];
+
+ if (!count($rules)) {
+ $ingress = $defaults;
+ }
+
+ foreach ($rules as $rule => $value) {
+ for ($i = 0; $i < count($rules); $i++) {
+ for ($j = 0; $j < count($value); $j++) {
+ $ingress[] = [ 'cidr_blocks' => [ $rule ], 'from_port' => $value[$j], 'to_port' => $value[$j], 'protocol' => 'TCP'];
+ }
+ }
+ }
+
+ $sg = new Resource('aws_security_group', $name);
+ $sg->ingress = $ingress;
+ $sg->egress = $defaults;
+ $sg->vpc_id = $vpcId;
+ $sg->name = $name;
+ $sg->description = "$name security group";
+ $sg->tags = [ 'Name' => $name ];
+
+ return $sg;
+ }
+}
\ No newline at end of file
diff --git a/core/Macros/Azure/Azure.php b/core/Macros/Azure/Azure.php
new file mode 100644
index 0000000..34cf804
--- /dev/null
+++ b/core/Macros/Azure/Azure.php
@@ -0,0 +1,38 @@
+ ['0.0.0.0/0'],
+ 'from_port' => 0,
+ 'to_port' => 0,
+ 'protocol' => "-1",
+ ];
+
+ if (!count($rules)) {
+ $ingress = $defaults;
+ }
+
+ foreach ($rules as $rule => $value) {
+ for ($i = 0; $i < count($rule); $i++) {
+ for ($j = 0; $j < count($value); $j++) {
+ $ingress[] = [ 'cidr_blocks' => [ $rule ], 'from_port' => $value[$j], 'to_port' => $value[$j], 'protocol' => 'TCP'];
+ }
+ }
+ }
+
+ $sg = new Resource('aws_security_group', $name);
+ $sg->ingress = $ingress;
+ $sg->egress = $defaults;
+ $sg->vpc_id = $vpcId;
+ $sg->name = $name;
+ $sg->description = "$name security group";
+ $sg->tags = [ 'Name' => $name ];
+
+ return $sg;
+ }
+}
\ No newline at end of file
diff --git a/core/Terraform.php b/core/Terraform.php
new file mode 100644
index 0000000..f927b73
--- /dev/null
+++ b/core/Terraform.php
@@ -0,0 +1,43 @@
+terraform[$name];
+ }
+
+ public function __set($name, $value){
+ if (!($value instanceof Block)) {
+ throw new \Exception('Value must be a type of block.');
+ }
+
+ $this->terraform[$name] = $value;
+ }
+
+ public function deepMerge() {
+ $a = [];
+
+ foreach ($this->terraform as $key => $value) {
+ $a = array_merge_recursive($a, $value->toArray());
+ }
+
+ return $a;
+ }
+
+ public function toJson() {
+ $a = $this->deepMerge();
+
+ return self::jsonEncode($a);
+ }
+
+ public static function jsonEncode($input, $pretty = true) {
+ $flag = $pretty ? JSON_PRETTY_PRINT : 0;
+
+ return json_encode($input, $flag | JSON_UNESCAPED_SLASHES);
+ }
+}
+?>
\ No newline at end of file
diff --git a/core/auth.php b/core/auth.php
new file mode 100644
index 0000000..da46f8a
--- /dev/null
+++ b/core/auth.php
@@ -0,0 +1,223 @@
+token = $token;
+ $this->renew = $renew;
+ $this->lifetime = $lifetime;
+ $this->status = 0;
+ }
+
+ function getToken() { return $this->token; }
+ function getRenew() { return $this->renew; }
+ function getLifetime() { return $this->lifetime; }
+ function getStatus() { return $this->status; }
+}
+
+class MartiniAuthObject {
+ var $auth = 0;
+ var $id = 0;
+ var $name = '';
+ var $token = 0;
+ var $tenantid = -1;
+ var $isadmin = False;
+
+ function __construct($auth, $id) {
+ $this->auth = $auth;
+ $this->id = $id;
+ $this->name = "";
+ $this->token = new MartiniTokenObject(0,0,0);
+ }
+
+ function getId() { return $this->id; }
+ function getName() { return $this->name; }
+ function isAuthed() { return ($this->auth > 0); }
+}
+
+/*
+ * Authenticate class for an admin or tenant
+ * 0: noaccess
+ * >0: authenticated
+ */
+function authenticate($username, $password, $type = 'tenant') {
+ $db = getDBConnection();
+ $ao = new MartiniAuthObject(0, 0);
+
+ try {
+ $hashpwd = hash("sha512", sprintf("martini!%s", $password));
+
+ switch ($type) {
+ case "admin":
+ $stmt = $db->prepare("select * from martini_user where name = ?");
+ $stmt->execute(array($username));
+
+ if ($row = $stmt->fetch()) {
+ if ($hashpwd == strtolower($row["hashpassword"])) {
+ $ao->auth = 1;
+ $ao->id = $row["id"];
+ $ao->name = $row["name"];
+ $ao->isadmin = true;
+ }
+ }
+ break;
+ case "tenant":
+ $stmt = $db->prepare("select * from martini_tenant where email = ?");
+ $stmt->execute(array($username));
+
+ if ($row = $stmt->fetch()) {
+ if ($hashpwd == strtolower($row["password"])) {
+ $ao->auth = 1;
+ $ao->id = $row["id"];
+ $ao->tenantid = $row["id"];
+ $ao->name = $row["email"];
+ $ao->isadmin = false;
+ }
+ }
+
+ break;
+ }
+ } catch (PDOException $ex) {
+ }
+
+ return $ao;
+}
+
+/*
+ * Returns a token if authenticated
+ */
+function authenticateWithToken($username, $password) {
+ $result = authenticate($username, $password, 'admin');
+
+ if ($result->auth > 0 && $result->isadmin) {
+ $validlifetime = 3600;
+ $date = new DateTime();
+ $unixtime = $date->getTimestamp() + $validlifetime;
+ $token = bin2hex(random_bytes(24));
+ $renew = bin2hex(random_bytes(24));
+
+ $hashedtoken = hash("sha512", "mixeduptoken!$token");
+ $hashedrenew = hash("sha512", "mixeduptoken!$renew");
+
+ $db = getDBConnection();
+
+ $stmt = $db->prepare("INSERT INTO martini_token (token, renew, validuntil, userid) VALUES (?,?,?,?)");
+ $stmt->execute(array($hashedtoken, $hashedrenew, $unixtime, $result->id));
+
+ $result->token->token = $token;
+ $result->token->renew = $renew;
+ $result->token->lifetime = $unixtime;
+ }
+
+ return $result;
+}
+
+/*
+ * Password validation for tenant authenticate
+ */
+function validateTenantPass($mail, $pass) {
+ $db = getDBConnection();
+ $hash = hashPass($pass);
+ $authenticated = 0;
+
+ try {
+ $stmt = $db->prepare("select * from martini_tenant where email = ? and password = ?");
+ $stmt->execute(array($mail, $hash));
+
+ if ($row = $stmt->fetch()) {
+ if ($row['email'] == $mail) {
+ foreach ($result as $row) {
+ if ($hashpwd == strtolower($row["hashpassword"])) {
+ $ao->auth = 1;
+ $ao->id = $row["id"];
+ $ao->name = $row["name"];
+ }
+ }
+ }
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $authenticated;
+}
+
+/*
+ * Renew admin token
+ */
+function renewToken($token, $renew) {
+ $db = getDBConnection();
+ $tokeno = new MartiniTokenObject(0, 0, 0);
+
+ try {
+ $hashedtoken = hash("sha512", "mixeduptoken!$token");
+ $hashedrenew = hash("sha512", "mixeduptoken!$renew");
+
+ $stmt = $db->prepare("select * from martini_token where token = ? and renew = ?");
+ $stmt->execute(array($hashedtoken,$hashedrenew));
+ $result = $stmt->fetchAll();
+
+ foreach ($result as $row) {
+ $date = new DateTime();
+ if ($row['validuntil'] > $date->getTimestamp()) {
+ $token = bin2hex(random_bytes(24));
+ $hashedtoken = hash("sha512", "mixeduptoken!$token");
+ $validlifetime = 3600 ;
+ $unixtime = $date->getTimestamp() + $validlifetime;
+ $stmt = $db->prepare("UPDATE martini_token SET token = ?, validuntil = ? WHERE renew = ? and id = ?");
+ $stmt->execute(array($hashedtoken, $unixtime,$hashedrenew, $row['id']));
+ $tokeno->status = 1;
+ $tokeno->token = $token;
+ $tokeno->renew = $renew;
+ $tokeno->lifetime = $unixtime;
+ } else {
+ $tokeno->status = 2;
+ }
+ }
+
+ } catch(PDOException $ex) {
+ error_log($ex);
+ }
+
+ return $tokeno;
+}
+
+/*
+ * Verify admin token
+ * returns:
+ * 0: invalid
+ * 1: valid
+ * 2: expired
+ */
+function verifyToken($token) {
+ $db = getDBConnection();
+ $tokeno = new MartiniTokenObject($token,0,0);
+
+ try {
+ $date = new DateTime();
+ $hashedtoken = hash("sha512", "mixeduptoken!$token");
+ $stmt = $db->prepare("select * from martini_token where token = ?");
+ $stmt->execute(array($hashedtoken));
+ $result = $stmt->fetchAll();
+
+ foreach ($result as $row) {
+ $tokeno->lifetime = $row['validuntil'];
+
+ if ($row['validuntil'] > $date->getTimestamp()) {
+ $tokeno->status = 1;
+ } else {
+ $tokeno->status = 2;
+ }
+ }
+ } catch(PDOException $ex) {
+ error_log("danger $ex");
+ }
+
+ return $tokeno;
+}
+?>
\ No newline at end of file
diff --git a/core/brokerendpoint.php b/core/brokerendpoint.php
new file mode 100644
index 0000000..919ad84
--- /dev/null
+++ b/core/brokerendpoint.php
@@ -0,0 +1,95 @@
+getMessage()}", $code, $previous);
+ }
+
+ public function __toString() {
+ return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
+ }
+}
+class BrokerEndpointException extends Exception {
+ public function __construct(Exception $previous = null, $code = 0) {
+ parent::__construct("Broker Endpoint Exception", $code, $previous);
+ }
+
+ public function __toString() {
+ return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
+ }
+}
+
+class BrokerEndpoint {
+ public $port;
+
+ public function __construct($port) {
+ $this->port = $port;
+ }
+}
+
+function getBrokerendpoints() {
+ $db = getDBConnection();
+ $list = [];
+
+ try {
+ $stmt = $db->prepare("select id, port from martini_endpoint");
+ $stmt->execute();
+ $result = $stmt->fetchAll();
+
+ foreach ($result as $row) {
+ array_push($list,(new BrokerEndpoint($row["port"])));
+ }
+
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException($ex);
+ }
+ return $list;
+}
+
+function addbrokerendpoint($port) {
+ $db = getDBConnection();
+ $db->beginTransaction();
+
+ try {
+ $stmt = $db->prepare("select id from martini_endpoint where port = ?");
+ $stmt->execute(array($port));
+
+ if ($row = $stmt->fetch()) {
+ throw new BrokerEndpointException();
+ } else {
+ $stmt = $db->prepare("insert into martini_endpoint(port,validuntil,pid,tenantid) values(?,0,0,0) ");
+ $result = $stmt->execute(array($port));
+ error_log("adding port $result");
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException($ex);
+ } finally {
+ $db->commit();
+ }
+}
+
+function deletebrokerendpoint($port) {
+ $db = getDBConnection();
+ $db->beginTransaction();
+
+ try {
+ $stmt = $db->prepare("select id from martini_endpoint where port = ?");
+ $stmt->execute(array($port));
+
+ if ($row = $stmt->fetch()) {
+ $stmt = $db->prepare("delete from martini_endpoint where id = ?");
+ $stmt->execute(array($row['id']));
+ $db->commit();
+ } else {
+ $db->commit();
+
+ throw new BrokerEndpointException();
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException($ex);
+ }
+
+ error_log("deleting $port");
+}
+?>
\ No newline at end of file
diff --git a/core/cloudprovideroptionlist.php b/core/cloudprovideroptionlist.php
new file mode 100644
index 0000000..7450c64
--- /dev/null
+++ b/core/cloudprovideroptionlist.php
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/core/configuration.php b/core/configuration.php
new file mode 100644
index 0000000..c8855c9
--- /dev/null
+++ b/core/configuration.php
@@ -0,0 +1,118 @@
+query(sprintf("select * from martini_general_aws_config"));
+
+ if ($result->rowCount() != '0') {
+ foreach ($result as $row) {
+ $provider = new stdClass();
+ $provider->region = $row['region'];
+ $provider->accesskey = $row['accesskey'];
+ $provider->secretkey = $row['secretkey'];
+ }
+
+ return $provider;
+ }
+ } catch (PDOException $ex) {
+ print "Got error";
+ print var_dump($ex);
+ }
+}
+
+function getAWSRegionSettings($region) {
+ $db = getDBConnection();
+
+ try {
+ $result = $db->query(sprintf("select * from martini_provider_aws_region WHERE region = '%s'", $region));
+
+ if ($result->rowCount() != '0') {
+ foreach ($result as $row) {
+ $provider = new stdClass();
+ $provider->vpc = $row['vpc'];
+ $provider->privatekey = $row['privatekey'];
+ }
+
+ return $provider;
+ }
+ } catch (PDOException $ex) {
+ print "Got error";
+ print var_dump($ex);
+ }
+}
+
+function saveAWSGeneralSettings($json) {
+ $db = getDBConnection();
+
+ try {
+ $result = $db->query(sprintf("select * from martini_general_aws_config"));
+
+ if ($result->rowCount() != '0') {
+ foreach ($result as $row) {
+ $settings = new stdClass();
+ $settings->accesskey = $row['accesskey'];
+ $settings->secretkey = $row['secretkey'];
+ }
+ }
+
+ $decoded = json_decode($json);
+ $region = $decoded->region;
+ $accesskey = $decoded->accesskey;
+ $secretkey = $decoded->secretkey;
+
+ if (!empty($accesskey)) {
+ if (!empty($secretkey)) {
+ $sql = sprintf("replace into martini_general_aws_config values ('aws', '%s', '%s', '%s')", $region, $accesskey, $secretkey);
+ } else {
+ $sql = sprintf("replace into martini_general_aws_config values ('aws', '%s', '%s', '%s')", $region, $accesskey, $settings->secretkey);
+ }
+ } else {
+ $sql = sprintf("replace into martini_general_aws_config values ('aws', '%s', '%s', '%s')", $region, $settings->accesskey, $settings->secretkey);
+ }
+
+ $stmt = $db->prepare($sql);
+
+ echo json_encode($stmt->execute());
+ } catch (PDOException $ex) {
+ print "Got error";
+ print var_dump($ex);
+ }
+}
+
+function saveAWSRegionSettings($json) {
+ $db = getDBConnection();
+
+ try {
+ $result = $db->query(sprintf("select * from martini_provider_aws_region"));
+
+ if ($result->rowCount() != '0') {
+ foreach ($result as $row) {
+ $provider = new stdClass();
+ $provider->privatekey = $row['privatekey'];
+ }
+ }
+
+ $decoded = json_decode($json);
+ $region = $decoded->region;
+ $vpc = $decoded->vpc;
+ $privatekey = $decoded->privatekey;
+
+ if ((isset($privatekey) && !empty($privatekey))) {
+ $sql = sprintf("replace into martini_provider_aws_region values ('%s', '%s', '%s')", $region, $vpc, $privatekey);
+ } else {
+ $sql = sprintf("replace into martini_provider_aws_region values ('%s', '%s', '%s')", $region, $vpc, $provider->privatekey);
+ }
+
+ $stmt = $db->prepare($sql);
+
+ echo json_encode($stmt->execute());
+ } catch (PDOException $ex) {
+ print "Got error";
+ print var_dump($ex);
+ }
+}
+?>
\ No newline at end of file
diff --git a/core/database.php b/core/database.php
new file mode 100644
index 0000000..17b2982
--- /dev/null
+++ b/core/database.php
@@ -0,0 +1,11 @@
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+ return $db;
+}
+?>
\ No newline at end of file
diff --git a/core/jobs.php b/core/jobs.php
new file mode 100644
index 0000000..22b9f18
--- /dev/null
+++ b/core/jobs.php
@@ -0,0 +1,17 @@
+getJobs();
+
+ return $jobs;
+}
+
+function startJob($instanceid, $jobid) {
+ $veeam = instanceAuth($instanceid);
+ $veeam->startJob($jobid);
+}
+?>
\ No newline at end of file
diff --git a/core/license.php b/core/license.php
new file mode 100644
index 0000000..acb0722
--- /dev/null
+++ b/core/license.php
@@ -0,0 +1,45 @@
+getLicensedUsers();
+ if (isset($lic["results"])) {
+ $licenses = $lic["results"];
+ } else {
+ throw new LicenseQueryException("Queried but no results");
+ }
+
+ return $licenses;
+}
+
+class LicenseOrgUsage {
+ public $orgid;
+ public $orgname;
+ public $licensedUsers;
+ public $newUsers;
+
+ function __construct($orgid, $orgname, $licensedUsers, $newUsers) {
+ $this->orgid = $orgid;
+ $this->orgname = $orgname;
+ $this->licensedUsers = $licensedUsers;
+ $this->newUsers = $newUsers ;
+ }
+}
+function getLicenseInformation($instanceid) {
+ $licenses = [];
+ $veeam = instanceAuth($instanceid);
+ $orgs = $veeam->getOrganizations();
+
+ foreach($orgs as $org) {
+ $licinfo = $veeam->getLicenseInfo($org['id']);
+
+ array_push($licenses,(new LicenseOrgUsage($org['id'], $org['name'], $licinfo['licensedUsers'], $licinfo['newUsers'])));
+ }
+ return $licenses;
+}
+?>
\ No newline at end of file
diff --git a/core/scheduler.php b/core/scheduler.php
new file mode 100644
index 0000000..ec02498
--- /dev/null
+++ b/core/scheduler.php
@@ -0,0 +1,210 @@
+addOption('action')
+ ->long('action')
+ ->short('a')
+ ->description('Action to perform: clean, create, delete, deploy, destroy or update)')
+ ->argument('ACTION')
+ ->required();
+
+$getopts->parse();
+
+$options = $getopts->getOptions();
+$action = $options['action'];
+
+switch ($action) {
+ case 'clean':
+ deleteTenantInstance();
+ break;
+ case 'create':
+ deployTenantInstances();
+ break;
+ case 'delete':
+ deleteTenantInstance();
+ break;
+ case 'deploy':
+ deployTenantInstances();
+ break;
+ case 'destroy':
+ deleteTenantInstance();
+ break;
+ case 'update':
+ updateTenantInstance();
+ break;
+ default:
+ exit('Invalid action.');
+}
+
+/*
+ * Generate tenant instance configuration for Terraform
+ * Files are saved under /home/deployment
+ */
+function createTenantInstanceConfig($id, $name, $json, $key) {
+ $folder = '/home/deployment/tenant_' . $id . '_' . $name . '/';
+ $privatekey = $folder . '/setup/terraform.pem';
+
+ /* Create the Terraform JSON file */
+ $jsonpath = $folder . 'terraform-'.$name.'.json.tf';
+
+ if (!is_dir($folder)) {
+ mkdir($folder , 0755, true);
+ recurse_copy('setup', $folder . '/setup');
+ }
+
+ file_put_contents($jsonpath, $json); /* Terraform JSON */
+ file_put_contents($privatekey, $key); /* Terraform private key */
+
+ return $folder;
+}
+
+/*
+ * Delete a tenant instance via Terraform
+ */
+function deleteTenantInstance() {
+ $tenants = getTenants();
+ $orphaned = getOrphanedInstances();
+
+ for ($i = 0; $i < count($tenants); $i++) {
+ try {
+ $tenantid = $tenants[$i]->id;
+ $instances = getTenantAllInstances($tenantid);
+
+ for ($x = 0; $x < count($instances); $x++) {
+ if ($instances[$x]['status'] == -100) { /* Only delete ones with the correct status */
+ $id = $instances[$x]['id'];
+ $name = $instances[$x]['name'];
+ $folder = '/home/deployment/tenant_' . $id . '_' . $name . '/';
+
+ echo "Removing '".$instances[$x]['type']."' instance: $name (tenant ID: $tenantid)\r\n";
+ cleanupInstance($id);
+
+ /* Check the instance(s) */
+ if (is_dir($folder)) {
+ system('cd ' . $folder . ' && terraform destroy -auto-approve');
+ system('rm -rf ' . $folder);
+ }
+ }
+ }
+ } catch (Exception $e) {
+ }
+ }
+
+ for ($x = 0; $x < count($orphaned); $x++) {
+ try {
+ $id = $orphaned[$x]['id'];
+ $name = $orphaned[$x]['name'];
+
+ echo "Removing '".$orphaned[$x]['type']."' instance: $name (instance ID: $orphaned[$x]['id'])\r\n";
+ cleanupInstance($id);
+ } catch (Exception $e) {
+ }
+ }
+}
+
+
+/*
+ * Deploy a tenant instance via Terraform
+ */
+function deployTenantInstances() {
+ $tenants = getTenants();
+
+ for ($i = 0; $i < count($tenants); $i++) {
+ try {
+ $tenantid = $tenants[$i]->id;
+ $instances = getTenantAllInstances($tenantid);
+
+ for ($x = 0; $x < count($instances); $x++) {
+ if ($instances[$x]['status'] == 0) { /* Only scheduled ones need to be deployed */
+ if ($instances[$x]['type'] == 'aws') {
+ $region = getAWSRegionSettings($instances[$x]['location']);
+ $json = $instances[$x]['json'];
+ $privatekey = $region->privatekey;
+ $id = $instances[$x]['id'];
+ $name = $instances[$x]['name'];
+ $folder = createTenantInstanceConfig($id, $name, $json, $privatekey);
+
+ echo "Creating 'AWS' instance: $name (tenant ID: $tenantid).\r\n";
+ } else {
+ echo "Not implemented yet.\r\n";
+
+ echo "Creating 'Other' instance: $name (tenant ID: $tenantid)\r\n";
+ }
+
+ /* Deploy the instance */
+ updateInstanceState('2', $id);
+ system('cd '. $folder . ' && terraform init');
+ system('cd '. $folder . ' && terraform apply -auto-approve');
+ }
+ }
+ } catch (Exception $e) {
+ }
+ }
+}
+
+/*
+ * Update a tenant instance state
+ */
+function updateTenantInstance() {
+ $tenants = getTenants();
+
+ for ($i = 0; $i < count($tenants); $i++) {
+ try {
+ $tenantid = $tenants[$i]->id;
+ $instances = getTenantAllInstances($tenantid);
+
+ for ($x = 0; $x < count($instances); $x++) {
+ if ($instances[$x]['status'] == 2) { /* Only scheduled ones need to be updated */
+ $id = $instances[$x]['id'];
+ $name = $instances[$x]['name'];
+ $folder = '/home/deployment/tenant_' . $id . '_' . $name . '/';
+
+ /* Check the instance(s) */
+ if (is_dir($folder)) {
+ if ($instances[$x]['type'] == 'aws') {
+ $region = getAWSRegionSettings($instances[$x]['location']);
+ $privatekey = $region->privatekey;
+ $output = json_decode(shell_exec('cd ' . $folder . ' && terraform output -json'));
+ $ip = $output->public_ip->value;
+ $password = $output->password_data->value;
+ openssl_private_decrypt(base64_decode($password), $decrypted, $privatekey);
+
+ echo "Updating settings for 'AWS' instance: $name (tenant ID: $tenantid)\r\n";
+ updateInstance($id, $ip, '4443', 'Administrator', $decrypted);
+ updateInstanceState('1', $id);
+ }
+ }
+ }
+ }
+ } catch (Exception $e) {
+ }
+ }
+}
+
+/*
+ * Recurse copy method for setup files
+ */
+function recurse_copy($src, $dst) {
+ $dir = opendir($src);
+ mkdir($dst, 0755, true);
+
+ while (false !== ($file = readdir($dir))) {
+ if (($file != '.' ) && ($file != '..')) {
+ if (is_dir($src . '/' . $file)) {
+ recurse_copy($src . '/' . $file,$dst . '/' . $file);
+ } else {
+ copy($src . '/' . $file,$dst . '/' . $file);
+ }
+ }
+ }
+
+ closedir($dir);
+}
+?>
\ No newline at end of file
diff --git a/core/securestore.php b/core/securestore.php
new file mode 100644
index 0000000..1d577c7
--- /dev/null
+++ b/core/securestore.php
@@ -0,0 +1,66 @@
+prepare($sql);
+ $stmt->execute(array($keyref));
+ } catch(PDOException $ex) {
+ error_log($ex);
+ }
+}
+
+function getPassword($keyref) {
+ $db = getDBConnection();
+ $pwd = '';
+ $hn = gethostname();
+ $mysqlsalt = "cocktailsarefun$keyref$hn";
+
+ try {
+ $stmt = $db->prepare("select DECODE(encryptedpassword, ?) as pw from martini_securestore where keyval = ?");
+ $stmt->execute(array($mysqlsalt, $keyref));
+ $result = $stmt->fetchAll();
+
+ foreach ($result as $row) {
+ $pwd = $row['pw'];
+ }
+ } catch(PDOException $ex) {
+ error_log($ex);
+ }
+
+ return $pwd;
+}
+
+function savePassword($keyref, $pw) {
+ $hashpw = $pw;
+ $db = getDBConnection();
+ $hn = gethostname();
+ $mysqlsalt = "cocktailsarefun$keyref$hn";
+
+ try {
+ $sql = "insert into martini_securestore(keyval, encryptedpassword) values (?,ENCODE(?,?))";
+ $stmt = $db->prepare($sql);
+ $stmt->execute(array($keyref, $hashpw, $mysqlsalt));
+ } catch(PDOException $ex) {
+ error_log($ex);
+ }
+}
+
+function updatePassword($id, $keyref, $pw) {
+ $hashpw = $pw;
+ $db = getDBConnection();
+ $hn = gethostname();
+ $mysqlsalt = "cocktailsarefun$keyref$hn";
+
+ try {
+ $sql = "update martini_securestore set keyval = ?, encryptedpassword = ENCODE(?,?) where id = ?";
+ $stmt = $db->prepare($sql);
+ $stmt->execute(array($keyref, $hashpw, $mysqlsalt, $id));
+ } catch(PDOException $ex) {
+ error_log($ex);
+ }
+}
+?>
\ No newline at end of file
diff --git a/core/setup/bootstrap.ps1 b/core/setup/bootstrap.ps1
new file mode 100644
index 0000000..9eb9e20
--- /dev/null
+++ b/core/setup/bootstrap.ps1
@@ -0,0 +1,28 @@
+
+Write-Host "Delete any existing WinRM listeners"
+winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null
+winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null
+
+Write-Host "Create a new WinRM listener and configure"
+winrm create winrm/config/listener?Address=*+Transport=HTTP
+winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
+winrm set winrm/config '@{MaxTimeoutms="7200000"}'
+winrm set winrm/config/service '@{AllowUnencrypted="true"}'
+winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
+winrm set winrm/config/service/auth '@{Basic="true"}'
+winrm set winrm/config/client/auth '@{Basic="true"}'
+
+Write-Host "Configure UAC to allow privilege elevation in remote shells"
+$Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
+$Setting = 'LocalAccountTokenFilterPolicy'
+Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force
+
+Write-Host "turn off PowerShell execution policy restrictions"
+Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope LocalMachine
+
+Write-Host "Configure and restart the WinRM Service; Enable the required firewall exception"
+Stop-Service -Name WinRM
+Set-Service -Name WinRM -StartupType Automatic
+netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
+Start-Service -Name WinRM
+
\ No newline at end of file
diff --git a/core/setup/install-vbo365.ps1 b/core/setup/install-vbo365.ps1
new file mode 100644
index 0000000..7d2fdce
--- /dev/null
+++ b/core/setup/install-vbo365.ps1
@@ -0,0 +1,105 @@
+# Veeam Backup for Microsoft Office 365 auto install
+$jsoninstall = '{
+ "license": { "src":"veeam_backup_microsoft_office.lic" },
+ "steps": [
+ {
+ "src":"Veeam.Backup365_3.0.0.422.msi",
+ "install":"__file__",
+ "arguments":[
+ "/qn",
+ "/log __log__",
+ "ACCEPT_EULA=1",
+ "ACCEPT_THIRDPARTY_LICENSES=1"
+ ]
+ },
+ {
+ "src":"VeeamExplorerForExchange_3.0.0.422.msi",
+ "install":"__file__",
+ "arguments":[
+ "/qn",
+ "/log __log__",
+ "ACCEPT_EULA=1",
+ "ACCEPT_THIRDPARTY_LICENSES=1"
+ ]
+ },
+ {
+ "src":"VeeamExplorerForSharePoint_3.0.0.422.msi",
+ "install":"__file__",
+ "arguments":[
+ "/qn",
+ "/log __log__",
+ "ACCEPT_EULA=1",
+ "ACCEPT_THIRDPARTY_LICENSES=1"
+ ]
+ }
+ ]
+}'
+
+function log {
+ param($logline)
+ write-host ("[{0}] - {1}" -f (get-date).ToString("yyyyMMdd - hh:mm:ss"), $logline)
+}
+
+function replaceenv {
+ param( $line, $file, $log)
+
+ $line = $line -replace "__file__", $file
+ $line = $line -replace "__log__", $log
+
+ return $line
+}
+
+function VBOinstall {
+ param ($hostname)
+ $json = @($jsoninstall | ConvertFrom-Json)[0]
+ $steps = $json.steps
+ $tmp = "C:\VBO365Install"
+
+ foreach ($step in $steps) {
+ if ($step.disabled -and $step.disabled -eq 1 ) {
+ log(("Disabled step detected {0}" -f $step.src))
+ } else {
+ $src = ("{0}" -f $step.src)
+ $tmpfile = Join-Path -Path $tmp -ChildPath $src
+ $tmplog = Join-Path -Path $tmp -ChildPath "$src.log"
+ $installline = replaceenv -line $step.install -file $tmpfile -log $tmplog
+ $rebuildargs = @()
+
+ foreach($pa in $step.arguments) {
+ $rebuildargs += ((replaceenv -line $pa -file $src -log $tmplog))
+ }
+
+ log("Installing now:")
+ log($installline)
+ log($rebuildargs -join ",")
+
+ Start-Process -FilePath $installline -ArgumentList $rebuildargs -Wait
+ }
+ }
+
+ Import-Module "C:\Program Files\Veeam\Backup365\Veeam.Archiver.PowerShell\Veeam.Archiver.PowerShell.psd1"
+
+ #if ($json.license -and $json.license.src) {
+ # $tmpfile = Join-Path -Path $tmp -ChildPath $json.license.src
+ # Install-VBOLicense -Path $tmpfile
+ #}
+
+ $cert = New-SelfSignedCertificate -subject $hostname -NotAfter (Get-Date).AddYears(10) -KeyDescription "Veeam Backup for Microsoft Office 365 auto install" -KeyFriendlyName "Veeam Backup for Microsoft Office 365 auto install"
+ $certfile = (join-path $tmp "cert.pfx")
+ $securepassword = ConvertTo-SecureString "VBOpassword!" -AsPlainText -Force
+
+ Export-PfxCertificate -Cert $cert -FilePath $certfile -Password $securepassword
+
+ Write-Host "Enabling RESTful API service"
+ Set-VBORestAPISettings -EnableService -CertificateFilePath $certfile -CertificatePassword $securepassword
+
+ Write-Host "Enabling Tenant Auhtentication Settings"
+ Set-VBOTenantAuthenticationSettings -EnableAuthentication -CertificateFilePath $certfile -CertificatePassword $securepassword
+}
+
+Write-Host "Starting Veeam Backup for Microsoft Office 365 install"
+VBOinstall -hostname ([System.Net.Dns]::GetHostEntry([string]$env:computername).hostname)
+
+Write-Host "Creating Veeam Backup for Microsoft Office 365 firewall rules"
+netsh advfirewall firewall add rule name="Veeam Backup for Microsoft Office 365 RESTful API Service" protocol=TCP dir=in localport=4443 action=allow
+netsh advfirewall firewall add rule name="Veeam Backup for Microsoft Office 365" protocol=TCP dir=in localport=9191 action=allow
\ No newline at end of file
diff --git a/core/setup/prep-vbo365.ps1 b/core/setup/prep-vbo365.ps1
new file mode 100644
index 0000000..7e68355
--- /dev/null
+++ b/core/setup/prep-vbo365.ps1
@@ -0,0 +1,15 @@
+# Download Veeam Backup for Microsoft Office 365 v3
+$url = "https://download2.veeam.com/VeeamBackupOffice365_3.0.0.422.zip"
+$output = "$PSScriptRoot\VBO365.zip"
+Invoke-WebRequest -Uri $url -OutFile $output
+
+# Unpack VBO365.zip
+Expand-Archive -Force -LiteralPath $output -DestinationPath C:\VBO365Install
+
+# Install Chocolatey and .NET framework 4.7.2
+Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
+choco install dotnet4.7.2 -y
+
+# Restart server to finalize .NET framework 4.7.2 installation
+Write-Host "Rebooting server"
+Restart-Computer -Force
\ No newline at end of file
diff --git a/core/tenant.php b/core/tenant.php
new file mode 100644
index 0000000..9445486
--- /dev/null
+++ b/core/tenant.php
@@ -0,0 +1,569 @@
+id = $id;
+ $this->name = $name;
+ $this->email = $email;
+ $this->registered = $registered;
+ }
+}
+
+class TenantNotFoundException extends Exception {
+ public $tenantid = 0;
+
+ public function __construct($id, $code = 0, Exception $previous = null) {
+ $this->tenantid = $id;
+ parent::__construct("Tenant not found with id {$this->tenantid}", $code, $previous);
+ }
+}
+
+class TenantDatabaseException extends Exception {
+ public function __construct($code = 0, Exception $previous = null) {
+ parent::__construct("Tenant Database exception", $code, $previous);
+ }
+}
+
+class InstanceNotFoundException extends Exception {
+ public $instanceid = 0;
+
+ public function __construct($id, $code = 0, Exception $previous = null) {
+ $this->instanceid = $id;
+ parent::__construct("Instance not found with id {$this->instanceid}", $code, $previous);
+ }
+}
+
+class InstanceNoFreeBrokerSlotException extends Exception {
+ public function __construct($code = 0, Exception $previous = null) {
+ parent::__construct("No free port slot", $code, $previous);
+ }
+}
+
+class DeployMethodUnknownCloudException extends Exception {
+ public function __construct($cloudmessage="",$code = 0, Exception $previous = null) {
+ parent::__construct("Method for deployment is not known, can be unknown cloud or region $cloudmessage", $code, $previous);
+ }
+}
+
+function guidv4() {
+ $data = openssl_random_pseudo_bytes(16);
+
+ assert(strlen($data) == 16);
+
+ $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
+ $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
+
+ return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
+}
+
+function brokerTenantInstance($id, $clientip, $autokill = 300) {
+ $port = -1;
+ $db = getDBConnection();
+ $db->beginTransaction();
+
+ try {
+ $stmt = $db->prepare("select id, hostname from martini_tenant_instances where id = ?");
+ $stmt->execute(array($id));
+
+ if ($row = $stmt->fetch()) {
+ $port = 0;
+ $now = time();
+
+ $stmtslot = $db->prepare("select id,port from martini_endpoint where validuntil < ? for update;");
+ $stmtslot->execute(array($now));
+
+ if($rowslot = $stmtslot->fetch()) {
+ $stmtupd = $db->prepare("update martini_endpoint set validuntil = ?, pid = ?, tenantid = ? where id = ? ");
+ $tenantfqdn = $row["hostname"];
+ $port = $rowslot['port'];
+ $pid = exec("/usr/bin/martini-pfwd -clientaddr $clientip -local :$port -name pfwd-$port -remote $tenantfqdn:3389 -autokill $autokill;cat /tmp/pfwd-$port.pid");
+ $now = time();
+ $stmtupd->execute(array(($now+$autokill+5),$pid,$id,$rowslot['id']));
+
+ error_log($pid);
+ } else {
+ throw new InstanceNoFreeBrokerSlotException();
+ }
+
+ $db->commit();
+ } else {
+ throw new InstanceNotFoundException($id);
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $port;
+}
+
+/*
+ * Delete a tenant
+ */
+function deleteTenant($id) {
+ $deleted = -1;
+ $db = getDBConnection();
+
+ try {
+ $stmt = $db->prepare("select id from martini_tenant where id = ?");
+ $stmt->execute(array($id));
+
+ if ($row = $stmt->fetch()) {
+ $stmt = $db->prepare("delete from martini_tenant where id = ?");
+ $stmt->execute(array($id));
+
+ $deleted = $id;
+ } else {
+ throw new TenantNotFoundException($id);
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $deleted;
+}
+
+/*
+ * Deploy a tenant
+ */
+function deployTenant($tenantid, $type, $config) {
+ $rid = -1;
+
+ $t = getTenant($tenantid);
+ $type = strtolower($type);
+
+ switch($type) {
+ case 'aws':
+ if (isAllowedAWSRegion($config->region)) {
+ $name = $t->name. '-' . guidv4();
+ $region = $config->region;
+ $configjson = genAWSConfig($name, $region);
+ $rid = saveInstance($tenantid, $name, $configjson, $type, 0, $region);
+ } else {
+ throw new DeployMethodUnknownCloudException("cloud AWS known but not defined region {$config->region}");
+ }
+
+ break;
+ default:
+ throw new DeployMethodUnknownCloudException("$type is unknown, allowed : AWS");
+ }
+
+ return $rid;
+}
+
+/*
+ * Get tenant details
+ */
+function getTenant($id) {
+ $db = getDBConnection();
+
+ try {
+ $stmt = $db->prepare("select * from martini_tenant where id = ?");
+ $stmt->execute(array($id));
+
+ if ($row = $stmt->fetch()) {
+ $tenant = new stdClass();
+ $tenant->id = $row['id'];
+ $tenant->name = $row['name'];
+ $tenant->email = $row['email'];
+ $tenant->registered = $row['registered'];
+ } else {
+ throw new TenantNotFoundException($id);
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $tenant;
+}
+
+/*
+ * Get orphaned instances
+ */
+function getOrphanedInstances() {
+ $db = getDBConnection();
+
+ try {
+ $stmt = $db->prepare("select id, name, hostname, type, status, location from martini_tenant_instances where tenant_id = ?");
+ $stmt->execute(array("-1"));
+ $instances = $stmt->fetchAll(\PDO::FETCH_ASSOC);
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $instances;
+}
+
+function getTenantAllInstancesRef($id = -1) {
+ return getTenantAllInstances($id, "id, name, tenant_id, type, status, location, hostname, port, username");
+}
+
+/*
+ * Get all instances for a tenant
+ */
+function getTenantAllInstances($id = -1, $fields = '*') {
+ $db = getDBConnection();
+
+ try {
+ if ($id == -1) {
+ $stmt = $db->prepare("select $fields from martini_tenant_instances");
+ $stmt->execute();
+ $instances = $stmt->fetchAll(\PDO::FETCH_ASSOC);
+ } else {
+ $stmt = $db->prepare("select $fields from martini_tenant_instances where tenant_id = ?");
+ $stmt->execute(array($id));
+ $instances = $stmt->fetchAll(\PDO::FETCH_ASSOC);
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $instances;
+}
+
+/*
+ * Get a specific instance for a tenant
+ */
+function getTenantInstance($id, $resolvepw = false) {
+ $db = getDBConnection();
+
+ try {
+ $stmt = $db->prepare("select * from martini_tenant_instances where id = ?");
+ $stmt->execute(array($id));
+
+ if ($row = $stmt->fetch()) {
+ $instance = new stdClass();
+ $instance->id = $row['id'];
+ $instance->name = $row['name'];
+ $instance->tenantid = $row['tenant_id'];
+ $instance->type = $row['type'];
+ $instance->status = $row['status'];
+ $instance->location = $row['location'];
+ $instance->hostname = $row['hostname'];
+ $instance->port = $row['port'];
+ $instance->username = $row['username'];
+
+ if ($resolvepw) {
+ $pw = getPassword($row['password']);
+ $instance->password = $pw;
+ } else {
+ $pw = "*******";
+ $instance->password = $pw;
+ }
+ } else {
+ throw new InstanceNotFoundException($id);
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $instance;
+}
+
+/*
+ * Get total instances for a tenant
+ */
+function getTenantInstanceCounter($id) {
+ $db = getDBConnection();
+
+ try {
+ $stmt = $db->prepare("select * from martini_tenant_instances where tenant_id = ?");
+ $stmt->execute(array($id));
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $stmt->rowCount();
+}
+
+/*
+ * Get all instances for all tenants
+ */
+function getTenantInstances() {
+ $db = getDBConnection();
+
+ try {
+ $stmt = $db->prepare("select * from martini_tenant_instances");
+ $stmt->execute();
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $stmt->rowCount();
+}
+
+/*
+ * Get all tenants
+ */
+function getTenants() {
+ $db = getDBConnection();
+ $tenants = array();
+
+ try {
+ $result = $db->query("select * from martini_tenant");
+
+ foreach ($result as $row) {
+ array_push($tenants, new MartiniTenant($row['id'], $row['name'], $row['email'], $row['registered']));
+ }
+
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $tenants;
+}
+
+/*
+ * Random pass generator
+ */
+function randPass() {
+ $s = '';
+
+ for ($i = 0, $z = strlen($a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')-1; $i != 12; $x = rand(0, $z), $s .= $a{$x}, $i++);
+
+ return $s;
+}
+
+/*
+ * Password hasher
+ */
+function hashPass($password) {
+ return hash('sha512', sprintf("martini!%s", $password));
+}
+
+/*
+ * Save a tenant
+ */
+function saveTenant($tenant, $password = false) {
+ $db = getDBConnection();
+
+ if ($tenant->registered == -1) {
+ $date = new DateTime();
+ $tenant->registered = $date->getTimestamp();
+ }
+ try {
+ if ($tenant->id == -1) { /* New tenant */
+ $pass = randPass();
+ $hash = hashPass($pass);
+
+ $sql = "insert into martini_tenant (name, email, registered, password) values (?,?,?,?)";
+ $stmt = $db->prepare($sql);
+ $stmt->execute(array($tenant->name, $tenant->email, $tenant->registered, $hash));
+
+ $id = $db->lastInsertId();
+
+ $tenant->id = (int)$id;
+ $tenant->password = $pass;
+ } else { /* Update tenant */
+ if ($password) {
+ $pass = randPass();
+ $hash = hashPass($pass);
+
+ $sql = "update martini_tenant set name = ?, email = ?, password = ? where id = ?";
+ $stmt = $db->prepare($sql);
+ $stmt->execute(array($tenant->name, $tenant->email, $hash, $tenant->id));
+
+ $tenant->password = $pass;
+ } else {
+ $sql = "update martini_tenant set name = ?, email = ? where id = ?";
+ $stmt = $db->prepare($sql);
+ $stmt->execute(array($tenant->name, $tenant->email, $tenant->id));
+ }
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $tenant;
+}
+
+/*
+ * Clean up an instance (this will actually delete it)
+ */
+function cleanupInstance($id) {
+ $deleted = -1;
+ $db = getDBConnection();
+
+ try {
+ $stmt = $db->prepare("select id from martini_tenant_instances where id = ?");
+ $stmt->execute(array($id));
+
+ if ($row = $stmt->fetch()) {
+ deletePassword($row['password']);
+
+ $stmt = $db->prepare("delete from martini_tenant_instances where id = ?");
+ $stmt->execute(array($id));
+ $deleted = $id;
+ } else {
+ throw new InstanceNotFoundException($id);
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $deleted;
+}
+
+/*
+ * Mark an instance for removal
+ */
+function deleteInstance($id) {
+ $deleted = -1;
+ $db = getDBConnection();
+
+ try {
+ $stmt = $db->prepare("select id, password from martini_tenant_instances where id = ?");
+ $stmt->execute(array($id));
+
+ if ($row = $stmt->fetch()) {
+ $stmt = $db->prepare("update martini_tenant_instances set status = ? where id = ?");
+ $stmt->execute(array("-100", $id));
+ deletePassword($row['password']);
+
+ $deleted = $id;
+ } else {
+ throw new InstanceNotFoundException($id);
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $deleted;
+}
+
+/*
+ * Mark all instances for removal for a specific tenant
+ */
+function deleteInstances($id) {
+ $deleted = -1;
+ $db = getDBConnection();
+
+ try {
+ $stmt = $db->prepare("select * from martini_tenant_instances where tenant_id = ?");
+ $stmt->execute(array($id));
+
+ if ($row = $stmt->fetch()) {
+ $stmt = $db->prepare("update martini_tenant_instances set status = ? where tenant_id = ?");
+ $stmt->execute(array("-100", $id));
+ deletePassword($row['password']);
+
+ $deleted = $id;
+ } else {
+ throw new InstanceNotFoundException($id);
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $deleted;
+}
+
+/*
+ * Save an instance
+ */
+function saveInstance($id, $name, $json, $type, $status, $location) {
+ $db = getDBConnection();
+ $rid = -1;
+
+ try {
+ $sql = "insert into martini_tenant_instances (tenant_id, name, json, type, status, location) values (?,?,?,?,?,?)";
+ $stmt = $db->prepare($sql);
+ $stmt->execute(array($id, $name, $json, $type, $status, $location));
+ $rid = (int)$db->lastInsertId();
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $rid;
+}
+
+/*
+ * Used to (re)assign an instance to another tenant
+ */
+function assignInstance($tenantid, $instanceid) {
+ $db = getDBConnection();
+ $t = getTenant($tenantid);
+
+ try {
+ $sql = "update martini_tenant_instances set tenant_id = ? where id = ?";
+ $stmt = $db->prepare($sql);
+
+ if ($stmt->execute(array($tenantid, $instanceid))) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+ return false;
+}
+
+/*
+ * Update instance settings
+ */
+function updateInstance($id, $hostname, $port = 4443, $username, $password) {
+ $db = getDBConnection();
+
+ try {
+ $uuidkey = guidv4();
+ savePassword($uuidkey, $password);
+
+ $sql = "update martini_tenant_instances set hostname = ?, port = ?, username = ?, password = ? where id = ?";
+ $stmt = $db->prepare($sql);
+ if ($stmt->execute(array($hostname, $port, $username, $uuidkey, $id))) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+
+ return $sql;
+}
+
+/*
+ * Update settings for all instances for a tenant
+ */
+function updateInstances($id) {
+ $db = getDBConnection();
+
+ try {
+ $sql = "update martini_tenant_instances set tenant_id = ? where tenant_id = ?";
+ $stmt = $db->prepare($sql);
+ $stmt->execute(array("-1", $id));
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+}
+
+/*
+ * Update specific instance state
+ * 0 = scheduled for deployment
+ * 1 = deployed
+ * 2 = deployment in progress
+ * -1 = unmanaged
+ * -100 = marked for removal
+ */
+function updateInstanceState($status = 1, $id) {
+ $db = getDBConnection();
+
+ try {
+ $sql = "update martini_tenant_instances set status = ? where id = ?";
+ $stmt = $db->prepare($sql);
+ $stmt->execute(array($status, $id));
+ } catch (PDOException $ex) {
+ throw new TenantDatabaseException(0, $ex);
+ }
+}
+?>
\ No newline at end of file
diff --git a/core/terraform.config.php b/core/terraform.config.php
new file mode 100644
index 0000000..67db859
--- /dev/null
+++ b/core/terraform.config.php
@@ -0,0 +1,138 @@
+region;
+ }
+
+ $terraform = new \Terraform\Terraform();
+ $combinedname = 'aws_' . $instancename;
+
+ /* Define provider block */
+ $provider = new \Terraform\Blocks\Provider('aws');
+
+ $provider->region = $region;
+ $provider->access_key = $awssettings->accesskey;
+ $provider->secret_key = $awssettings->secretkey;
+ $terraform->provider = $provider;
+
+ /* Define data block */
+ $data = new \Terraform\Blocks\Data('aws_ami', 'windows-2016-latest');
+ $data->most_recent = true;
+ $data->owners = [ 'amazon' ];
+ $data->filter = [
+ 'name' => 'name',
+ 'values' => ["Windows_Server-2016-English-Full-Base*"]
+ ];
+ $terraform->data = $data;
+
+ /* Define resource block */
+ /* Create AWS instance resource */
+ $awsinstance = new Resource('aws_instance', $combinedname);
+ $awsinstance->ami = '${data.aws_ami.windows-2016-latest.id}';
+ $awsinstance->get_password_data = true;
+ $awsinstance->instance_type = 't2.medium';
+ $awsinstance->key_name = 'terraform';
+ $awsinstance->user_data = '${file("setup/bootstrap.ps1")}';
+ $awsinstance->tags = [ 'Name' => $instancename ];
+ $awsinstance->vpc_security_group_ids = [ '${aws_security_group.security_group_' . $instancename . '.id}' ];
+ $awsinstance->connection = [
+ 'type' => 'winrm',
+ 'port' => '5985',
+ 'https' => false,
+ 'insecure' => true,
+ 'timeout' => '5m',
+ 'user' => 'Administrator',
+ 'password' => '${rsadecrypt(self.password_data, file("setup/terraform.pem"))}'
+ ];
+ $awsprovisioner = [
+ 'file' => [
+ 'source' => 'setup/prep-vbo365.ps1',
+ 'destination' => 'C:\\VBO365Install\\prep-vbo365.ps1'
+ ],
+ 'remote-exec' => [
+ 'inline' => [
+ 'powershell.exe -File C:\\VBO365Install\\prep-vbo365.ps1'
+ ]
+ ]
+ ];
+ $awsinstance->provisioner = [ $awsprovisioner ];
+
+ $terraform->{"awsinstance"} = $awsinstance;
+
+ /* Create null resource for the installation */
+ $install = new Resource('null_resource', 'install_vbo_server');
+ $install->depends_on = [ 'aws_instance.' . $combinedname ];
+ $install->connection = [
+ 'host' => '${aws_instance.' . $combinedname . '.public_ip}',
+ 'type' => 'winrm',
+ 'port' => '5985',
+ 'https' => false,
+ 'insecure' => true,
+ 'timeout' => '5m',
+ 'user' => 'Administrator',
+ 'password' => '${rsadecrypt("${aws_instance.' . $combinedname . '.password_data}", file("setup/terraform.pem"))}'
+ ];
+ $nullprovisioner = [
+ 'local-exec' => [
+ 'command' => 'sleep 60'
+ ],
+ 'file' => [
+ 'source' => 'setup/install-vbo365.ps1',
+ 'destination' => 'C:\\VBO365Install\\install-vbo365.ps1'
+ ],
+ 'remote-exec' => [
+ 'inline' => [
+ 'powershell.exe -File C:\\VBO365Install\\install-vbo365.ps1'
+ ]
+ ]
+ ];
+ $install->provisioner = [ $nullprovisioner ];
+
+ $terraform->{"install"} = $install;
+
+
+ /* Define security group block */
+ /*
+ * Defaults:
+ * 3389: RDP
+ * 4443: Veeam Backup for Microsoft Office 365 RESTful API service
+ * 5985: WinRM (HTTP)
+ * 9191: Veeam Backup for Microsoft Office 365 service
+ */
+ $rules = [
+ '0.0.0.0/0' => [3389, 4443, 5985, 9191],
+ ];
+ $sg = AwsMacros::securityGroup('security_group_' . $instancename, 'vpc-a3f7d8c8', $rules);
+ $sg->description = 'Security group for tenant instance ' . $instancename;
+ $terraform->sg = $sg;
+
+
+ /* Output the IP's */
+ foreach (['public_ip', 'password_data'] as $result) {
+ $output = new Output($result);
+ $output->value = '${aws_instance.' . $combinedname . '.'.$result.'}';
+ $terraform->{"output_$result"} = $output;
+ }
+
+
+ /* Create the config file */
+ $config = $terraform->toJson();
+
+ return $config;
+}
+?>
\ No newline at end of file
diff --git a/core/translateapi.php b/core/translateapi.php
new file mode 100644
index 0000000..372086d
--- /dev/null
+++ b/core/translateapi.php
@@ -0,0 +1,24 @@
+hostname, $instance->port);
+ $login = $veeam->login($instance->username, $instance->password);
+
+ if ($login == '200') {
+ return $veeam;
+ } elseif ($login == '400') {
+ throw new InstanceAuthException($instanceid);
+ } else {
+ throw new InstanceAuthException($instanceid, 'unexpected return code from server');
+ }
+}
+?>
\ No newline at end of file
diff --git a/core/veeam.php b/core/veeam.php
new file mode 100644
index 0000000..f09d9ee
--- /dev/null
+++ b/core/veeam.php
@@ -0,0 +1,326 @@
+setToken($_SESSION['token']);
+ $veeam->refreshToken($_SESSION['refreshtoken']);
+}
+
+/* Configuration Calls */
+if ($action == 'getvpcs') {
+ $settings = getAWSGeneralSettings();
+ $accesskey = $settings->accesskey;
+ $secretkey = $settings->secretkey;
+
+ $aws = new AwsHelpers\Aws($region, $accesskey, $secretkey);
+
+ echo json_encode($aws->listVpcs());
+}
+if ($action == 'saveawsgeneralsettings') {
+ saveAWSGeneralSettings($json);
+}
+if ($action == 'saveawsregionsettings') {
+ saveAWSRegionSettings($json);
+}
+
+/* Instance Calls */
+if ($action == 'addinstance') { /* Create a new instance */
+ $decoded = json_decode($json);
+
+ if (strtolower($provider) == 'aws') {
+ $name = $decoded->name;
+ $region = $decoded->region;
+ $config = genAWSConfig($name, $region);
+
+ saveInstance($id, $name, $config, $type, $status, $region);
+ } elseif (strtolower($provider) == 'vbo') {
+ $name = $decoded->name;
+ $hostname = $decoded->hostname;
+ $port = $decoded->port;
+ $username = $decoded->username;
+ $password = $decoded->password;
+ $region = $decoded->region;
+
+ $instanceid = saveInstance($id, $name, '0', $type, $status, '0');
+ updateInstance($instanceid, $hostname, $port, $username, $password);
+ } else {
+ echo 'Not implemented yet.';
+ exit;
+ }
+}
+if ($action == 'getinstance') { /* Get specific instance details */
+ echo json_encode(getTenantInstance($id));
+}
+if ($action == 'deleteinstance') { /* Delete an instance */
+ echo json_encode(deleteInstance($id));
+}
+if ($action == 'deleteinstances') { /* Delete all instances for a tenant */
+ echo json_encode(deleteInstances($id));
+}
+if ($action == 'updateinstance') { /* Update an instance */
+ $decoded = json_decode($json);
+ $instance = getTenantInstance($decoded->id, true);
+
+ if (empty($decoded->password)) {
+ $password = $instance->password;
+ } else {
+ $password = $decoded->password;
+ }
+
+ echo json_encode(updateInstance($decoded->id, $decoded->hostname, $decoded->port, $decoded->username, $password));
+}
+if ($action == 'updateinstances') { /* Update all instance for a tenant */
+ echo json_encode(updateInstances($id, $status));
+}
+
+if ($action == 'connect') {
+ $tenant = getTenantInstance($id, true);
+
+ $veeam = new VBO($tenant->hostname, $tenant->port);
+ $login = $veeam->login($tenant->username, $tenant->password);
+
+ if ($login == '200') {
+ $_SESSION['refreshtoken'] = $veeam->getRefreshToken();
+ $_SESSION['token'] = $veeam->getToken();
+ $_SESSION['username'] = $tenant->username;
+ $_SESSION['hostname'] = $tenant->hostname;
+ $_SESSION['port'] = $tenant->port;
+ $_SESSION['connected'] = true;
+
+ echo json_encode(true);
+ } elseif ($login == '400') {
+ echo 'Authorization error: Invalid credential';
+ }
+}
+if ($action == 'disconnect') {
+ unset($_SESSION['refreshtoken']);
+ unset($_SESSION['token']);
+ unset($_SESSION['username']);
+ unset($_SESSION['hostname']);
+ unset($_SESSION['port']);
+ unset($_SESSION['connected']);
+
+ header("Refresh:0");
+
+ echo json_encode(true);
+}
+
+/* Tenant Calls */
+if ($action == 'addtenant') { /* Create a new tenant */
+ $decoded = json_decode($json);
+ $tenant = New MartiniTenant(-1, $decoded->name, $decoded->email, -1);
+
+ echo json_encode(saveTenant($tenant));
+}
+if ($action == 'gettenant') { /* Get specific tenant details */
+ echo json_encode(getTenant($id));
+}
+if ($action == 'deletetenant') { /* Delete a tenant */
+ echo json_encode(deleteTenant($id));
+}
+if ($action == 'updatetenant') { /* Update a tenant */
+ $decoded = json_decode($json);
+ $tenant = getTenant($decoded->id);
+ $update = New MartiniTenant($decoded->id, $decoded->name, $decoded->email, $tenant->registered);
+
+ if (isset($decoded->password) && $decoded->password == 'true') {
+ echo json_encode(saveTenant($update, true));
+ } else {
+ echo json_encode(saveTenant($update, false));
+ }
+}
+
+/* Veeam Backup for Microsoft Office 365 RESTful API calls */
+/* Jobs Calls */
+if ($action == 'changejobstate') {
+ $veeam->changeJobState($id, $json);
+}
+if ($action == 'getjobs') {
+ $jobs = $veeam->getJobs($id);
+ echo json_encode($jobs);
+}
+if ($action == 'getjobsession') {
+ $getjobsession = $veeam->getJobSession($id);
+ echo json_encode($getjobsession);
+}
+if ($action == 'startjob') {
+ $veeam->startJob($id);
+}
+
+/* Organizations Calls */
+if ($action == 'getorganizations') {
+ $org = $veeam->getOrganizations();
+ echo json_encode($org);
+}
+
+/* Repositories Calls */
+if ($action == 'getrepo') {
+ $repo = $veeam->getBackupRepository($id);
+ echo json_encode($repo);
+}
+
+/* Sessions Calls */
+if ($action == 'getbackupsessionlog') {
+ $log = $veeam->getBackupSessionLog($id);
+ echo json_encode($log);
+}
+if ($action == 'getbackupsessions') {
+ $log = $veeam->getBackupSessions();
+ echo json_encode($log);
+}
+if ($action == 'getrestoresessionevents') {
+ $log = $veeam->getRestoreSessionEvents($id);
+ echo json_encode($log);
+}
+if ($action == 'getrestoresessions') {
+ $log = $veeam->getRestoreSessions();
+ echo json_encode($log);
+}
+
+/* Restore Session Calls */
+if ($action == 'startrestore') {
+ if (isset($id) && ($id != "tenant")) {
+ $session = $veeam->startRestoreSession($json, $id);
+ } else {
+ $session = $veeam->startRestoreSession($json);
+ }
+
+ $_SESSION['rid'] = $session['id'];
+ $_SESSION['rtype'] = strtolower($session['type']);
+ echo $session['id']; /* Return the Restore Session ID */
+}
+if ($action == 'stoprestore') {
+ $session = $veeam->stopRestoreSession($id);
+ unset($_SESSION['rid']);
+ unset($_SESSION['rtype']);
+}
+
+/* Exchange Calls */
+if ($action == 'getmailitems') {
+ $items = $veeam->getMailboxItems($mailboxid, $rid, $folderid, $offset);
+ echo json_encode($items);
+}
+
+/* Exchange Restore Calls */
+if ($action == 'exportmailbox') {
+ $veeam->exportMailbox($mailboxid, $rid, $json);
+}
+if ($action == 'exportmailitem') {
+ $veeam->exportMailItem($itemid, $mailboxid, $rid, $json);
+}
+if ($action == 'exportmultiplemailitems') {
+ $veeam->exportMultipleMailItems($itemid, $mailboxid, $rid, $json);
+}
+if ($action == 'restoremailbox') {
+ $veeam->restoreMailbox($mailboxid, $rid, $json);
+}
+if ($action == 'restoremailitem') {
+ $veeam->restoreMailItem($itemid, $mailboxid, $rid, $json);
+}
+if ($action == 'restoremultiplemailitems') {
+ $veeam->restoreMultipleMailItems($mailboxid, $rid, $json);
+}
+
+/* OneDrive Calls */
+if ($action == 'getonedriveitems') {
+ $items = $veeam->getOneDriveTree($rid, $userid, $type, $folderid, $offset);
+ echo json_encode($items);
+}
+if ($action == 'getonedriveitemsbyfolder') {
+ $items = $veeam->getOneDriveTree($rid, $userid, $type, $folderid);
+ echo json_encode($items);
+}
+if ($action == 'getonedriveparentfolder') {
+ $items = $veeam->getOneDriveParentFolder($rid, $userid, $type, $folderid);
+ echo json_encode($items);
+}
+
+/* OneDrive Restore Calls */
+if ($action == 'exportonedrive') {
+ $veeam->exportOneDrive($userid, $rid, $json);
+}
+if ($action == 'exportonedriveitem') {
+ $veeam->exportOneDriveItem($itemid, $userid, $rid, $json, $type);
+}
+if ($action == 'exportmultipleonedriveitems') {
+ $veeam->exportMultipleOneDriveItems($itemid, $userid, $rid, $json, $type);
+}
+if ($action == 'restoreonedrive') {
+ $veeam->restoreOneDrive($userid, $rid, $json);
+}
+if ($action == 'restoreonedriveitem') {
+ $veeam->restoreOneDriveItem($itemid, $userid, $rid, $json, $type);
+}
+if ($action == 'restoremultipleonedriveitems') {
+ $veeam->restoreMultipleOneDriveItems($userid, $rid, $json);
+}
+
+/* SharePoint Calls */
+if ($action == 'getsharepointcontent') {
+ $users = $veeam->getSharePointContent($rid, $siteid, $type);
+ echo json_encode($users);
+}
+if ($action == 'getsharepointitems') {
+ $items = $veeam->getSharePointTree($rid, $siteid, $folderid, $type, $offset);
+ echo json_encode($items);
+}
+if ($action == 'getsharepointitemsbyfolder') {
+ $items = $veeam->getSharePointTree($rid, $siteid, $folderid, $type);
+ echo json_encode($items);
+}
+if ($action == 'getsharepointparentfolder') {
+ $items = $veeam->getSharePointParentFolder($rid, $siteid, $type, $folderid);
+ echo json_encode($items);
+}
+
+/* SharePoint Restore Calls */
+if ($action == 'exportsharepoint') {
+ $veeam->exportSharePoint($siteid, $rid, $json);
+}
+if ($action == 'exportsharepointitem') {
+ $veeam->exportSharePointItem($itemid, $siteid, $rid, $json, $type);
+}
+if ($action == 'exportmultiplesharepointitem') {
+ $veeam->exportMultipleSharePointItem($siteid, $rid, $json);
+}
+if ($action == 'restoresharepoint') {
+ $veeam->restoreSharePoint($siteid, $rid, $json);
+}
+if ($action == 'restoresharepointitem') {
+ $veeam->restoreSharePointItem($itemid, $siteid, $rid, $json, $type);
+}
+if ($action == 'restoremultiplesharepointitems') {
+ $veeam->restoreMultipleSharePointItems($siteid, $rid, $json);
+}
+?>
\ No newline at end of file
diff --git a/core/veeam.vbo.class.php b/core/veeam.vbo.class.php
new file mode 100644
index 0000000..77e7b2a
--- /dev/null
+++ b/core/veeam.vbo.class.php
@@ -0,0 +1,2375 @@
+client = new Client([
+ 'base_uri' => 'https://'.$host.':'.$port.'/v3/',
+ 'connect_timeout' => 30,
+ 'http_errors' => false
+ ]);
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $user Username
+ * @param $pass Password
+ * @return SESSION
+ */
+ public function login($user, $pass) {
+ try {
+ $response = $this->client->request('POST', 'token', [
+ 'form_params' => [
+ 'grant_type' => 'password',
+ 'username' => $user,
+ 'password' => $pass
+ ],
+ 'headers' => [
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ $this->refreshtoken = $result['refresh_token'];
+ $this->token = $result['access_token'];
+
+ return '200';
+ } elseif ($response->getStatusCode() === 400) {
+ throw new VBOAuthenticationException('Login error(0): Not authenticated');
+ } else {
+ throw new VBOAuthenticationException('Login error(1): ' . $response->getStatusCode());
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ throw new VBOAuthenticationException('Login Error(3): ' . $exception['error']);
+ } else {
+ throw new VBOAuthenticationException('Login Error(4): ' . $e->getMessage());
+ }
+ }
+ }
+
+ public function logout() {
+ unset($_SESSION);
+ session_destroy();
+ header("Refresh:0");
+ }
+
+ /**
+ * @return SESSION
+ */
+ public function getToken() {
+ return($this->token);
+ }
+
+ /**
+ * @param $token Token
+ */
+ public function setToken($token) {
+ $this->token = $token;
+ }
+
+ /**
+ * @return SESSION
+ */
+ public function getRefreshToken() {
+ return($this->refreshtoken);
+ }
+
+ /**
+ * @param $refreshtoken Refresh Token
+ * @return SESSION
+ */
+ public function refreshToken($refreshtoken) {
+ try {
+ $response = $this->client->request('POST', 'token', [
+ 'form_params' => [
+ 'grant_type' => 'refresh_token',
+ 'refresh_token' => $refreshtoken
+ ],
+ 'headers' => [
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ $this->refreshtoken = $result['refresh_token'];
+ $this->token = $result['access_token'];
+ } elseif ($response->getStatusCode() === 400) {
+ return '400';
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['error'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+
+ /**
+ * @return $result
+ */
+ public function getBackupRepositories() {
+ try {
+ $response = $this->client->request('GET', 'BackupRepositories', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Organization ID
+ * @return $result
+ */
+ public function getJobs($id = NULL) {
+ if ($id) {
+ $call = 'Organizations/'.$id.'/Jobs';
+ } else {
+ $call = 'Jobs/';
+ }
+
+ try {
+ $response = $this->client->request('GET', $call, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+
+ throw new VBOAuthenticationException('getJobs Error(0): Not authenticated');
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ throw new VBOGenericException('getJobs Error(1): ' . $exception['message']);
+ } else {
+ throw new VBOGenericException('getJobs Error(1): ' . $e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * @param $id Job ID
+ * @return $result
+ */
+ public function getJobSelectedItems($id) {
+ try {
+ $response = $this->client->request('GET', 'Jobs/'.$id.'/SelectedItems', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Job ID
+ * @return $result
+ */
+ public function getJobSession($id) {
+ try {
+ $response = $this->client->request('GET', 'Jobs/'.$id.'/JobSessions', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Organization ID
+ * @return $result
+ */
+ public function getLicenseInfo($id) {
+ try {
+ $response = $this->client->request('GET', 'Organizations/'.$id.'/LicensingInformation', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Organization ID
+ * @return $result
+ */
+ public function getLicensedUsers($id = 0, $offset = 0, $limit = 100000) {
+ try {
+ $call = 'LicensedUsers?offset='.$offset.'&limit='.$limit;
+
+ if ($id != 0) {
+ $call .= '&organizationId='.$id;
+ }
+
+ $response = $this->client->request('GET', $call, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+
+ throw new VBOAuthenticationException('Licenseuser Error(0): Not authenticated');
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ throw new VBOGenericException('Licenseuser Error(1): ' . $exception['message']);
+ } else {
+ throw new VBOGenericException('Licenseuser Error(2): ' . $e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * @return $result
+ */
+ public function getOrganization() {
+ try {
+ $response = $this->client->request('GET', 'Organization', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Organization ID
+ * @return $result
+ */
+ public function getOrganizationByID($id) {
+ try {
+ $response = $this->client->request('GET', 'Organizations/'.$id, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore session ID
+ * @return $result
+ */
+ public function getOrganizationID($rid) {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$rid.'/Organization', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ } elseif ($response->getStatusCode() === 500) {
+ return '500';
+ } else {
+ echo $response->getStatusCode() . ' - ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Organization ID
+ * @return $result
+ */
+ public function getOrganizationJobs($id) {
+ try {
+ $response = $this->client->request('GET', 'Organizations/'.$id.'/Jobs', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore session ID
+ * @return $result
+ */
+ public function getOrganizationRepository($id) {
+ try {
+ $response = $this->client->request('GET', 'Organizations/'.$id.'/usedRepositories', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @return $result
+ */
+ public function getOrganizations() {
+ try {
+ $response = $this->client->request('GET', 'Organizations', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Organization ID
+ * @return $result
+ */
+ public function getOrganizationUsers($id) {
+ try {
+ $response = $this->client->request('GET', 'Organizations/'.$id.'/Users', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @return $result
+ */
+ public function getProxies() {
+ try {
+ $response = $this->client->request('GET', 'Proxies', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Repository ID
+ * @return $result
+ */
+ public function getProxy($id) {
+ try {
+ $response = $this->client->request('GET', 'Proxies/'.$id, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Backup Repository ID
+ * @param $uid User ID
+ * @return $result
+ */
+ public function getUserData($id, $uid) {
+ try {
+ $response = $this->client->request('GET', 'BackupRepositories/'.$id.'/UserData/'.$uid, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Job ID
+ * @param $json JSON format
+ * @return $result
+ */
+ public function changeJobState($id, $json) {
+ try {
+ $response = $this->client->request('POST', 'Jobs/'.$id.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'verify' => false,
+ 'body' => $json,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Job ID
+ * @return string
+ */
+ public function startJob($id) {
+ $retmessage = "Not Started";
+ try {
+ $response = $this->client->request('POST', 'Jobs/'.$id.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => '{ "start": null }'
+ ]
+ );
+
+ if ($response->getStatusCode() === 200) {
+ echo 'Job has been started.';
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+
+ throw new VBOAuthenticationException('startJob Error(0): Not authenticated');
+ } else {
+ $result = json_decode($response->getBody(), true);
+ $msg = $result['message'];
+
+ throw new VBOGenericException('startJob Error(1): RemoteError ' . $msg);
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+ $msg = $exception['message'];
+
+ throw new VBOGenericException('startJob Error(2): ' . $msg);
+ } else {
+ $msg = $e->getMessage();
+
+ throw new VBOGenericException('startJob Error(3): ' . $msg);
+ }
+ }
+ }
+
+ /**
+ * @param $json JSON code for Exchange, OneDrive or SharePoint
+ * @id Organization ID
+ * @return $result
+ */
+ public function startRestoreSession($json, $id = NULL) {
+ if ($id) { /* Used for admin restores */
+ $call = 'Organizations/'.$id.'/Action';
+ } else { /* Used for tenant restores */
+ $call = 'Organization/Action';
+ }
+
+ try {
+ $response = $this->client->request('POST', $call, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => true,
+ 'verify' => false,
+ 'body' => $json
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 201) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ } elseif ($response->getStatusCode() === 500) {
+ return($result);
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $id Organization ID
+ * @param $json JSON
+ * @return string
+ */
+ public function stopRestoreSession($rid) {
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => '{ "stop": null }'
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 201) {
+ return($result);
+ } else {
+ $result = json_decode($response->getBody(), true);
+
+ echo $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+
+ /**
+ * Start Session Log functions
+ */
+ /**
+ * @return $result
+ */
+ public function getBackupSessions() {
+ try {
+ $response = $this->client->request('GET', 'JobSessions', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @return $result
+ */
+ public function getBackupSessionLog($id) {
+ try {
+ $response = $this->client->request('GET', 'JobSessions/'.$id.'/LogItems?limit=1000', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @return $result
+ */
+ public function getRestoreSessions() {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @return $result
+ */
+ public function getRestoreSessionEvents($id) {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$id.'/Events', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * Start Exchange functions
+ */
+
+ /**
+ * @param $rid Restore Session ID
+ * @return $result
+ */
+ public function getMailbox($rid) {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$rid.'/Organization/Mailboxes/?offset=0&limit=1000', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ } elseif ($response->getStatusCode() === 500) {
+ return '500';
+ } else {
+ echo $response->getStatusCode() . ' - ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $uid User ID
+ * @return $result
+ */
+ public function getMailboxID($rid, $uid) {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$rid.'/Organization/Mailboxes/'.$uid, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $mid Mailbox ID
+ * @return $result
+ */
+ public function getMailboxFolders($mid, $rid) {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$rid.'/Organization/Mailboxes/'.$mid.'/folders?limit=1000', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $mid Mailbox ID
+ * @param $offset Offset to start from
+ * @return $result
+ */
+ public function getMailboxItems($mid, $rid, $fid = null, $offset = null) {
+ if (isset($fid) || (strcmp($fid, 'null') !== 0)) {
+ $call = 'RestoreSessions/'.$rid.'/Organization/Mailboxes/'.$mid.'/Items?folderId='.$fid;
+
+ if (isset($offset)) {
+ $call .= '&offset=' . $offset . '&limit=30';
+ }
+ } else {
+ $call = 'RestoreSessions/'.$rid.'/Organization/Mailboxes/'.$mid.'/Items';
+
+ if (isset($offset)) {
+ $call .= '?offset=' . $offset . '&limit=30';
+ }
+ }
+
+ try {
+ $response = $this->client->request('GET', $call, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ ],
+ 'http_errors' => false,
+ 'verify' => false
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $mid Mailbox ID
+ * @param $json JSON format
+ * @return $file
+ */
+ public function exportMailbox($mid, $rid, $json) {
+ $tmpFile = sys_get_temp_dir() . '/' . $mid;
+ $resource = fopen($tmpFile, 'w');
+
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/Mailboxes/'.$mid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/octet-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json,
+ 'sink' => $resource
+ ]
+ );
+
+ fclose($resource);
+
+ if ($response->getStatusCode() === 200) {
+ echo $tmpFile;
+ } else {
+ echo 'error';
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $mid Mailbox ID
+ * @param $iid Item ID
+ * @param $json JSON format
+ * @return $file
+ */
+ public function exportMailItem($iid, $mid, $rid, $json) {
+ $tmpFile = sys_get_temp_dir() . '/' . $iid;
+ $resource = fopen($tmpFile, 'w');
+
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/Mailboxes/'.$mid.'/Items/'.$iid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/octet-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json,
+ 'sink' => $resource,
+ ]
+ );
+
+ fclose($resource);
+
+ if ($response->getStatusCode() === 200) {
+ echo $tmpFile;
+ } else {
+ echo 'error';
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $mid Mailbox ID
+ * @param $iid File name for export
+ * @param $json JSON format
+ * @return $file
+ */
+ public function exportMultipleMailItems($iid, $mid, $rid, $json) {
+ $tmpFile = sys_get_temp_dir() . '/' . $iid;
+ $resource = fopen($tmpFile, 'w');
+
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/Mailboxes/'.$mid.'/Items/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/octet-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json,
+ 'sink' => $resource,
+ ]
+ );
+
+ fclose($resource);
+
+ if ($response->getStatusCode() === 200) {
+ echo $tmpFile;
+ } else {
+ echo 'error';
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $mid Mailbox ID
+ * @param $json JSON
+ * @return STRING
+ */
+ public function restoreMailbox($mid, $rid, $json) {
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/Mailboxes/'.$mid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ if ($result['createdItemsCount'] >= '1') {
+ echo 'Mailbox has been restored.';
+ } elseif ($result['mergedItemsCount'] == '1') {
+ echo 'Item has been restored and has been merged.';
+ } elseif ($result['failedItemsCount'] == '1') {
+ echo 'Item restore failed.';
+ } elseif ($result['skippedItemsCount'] == '1') {
+ echo 'Item has been skipped.';
+ } else {
+ echo 'Restore failed.';
+ }
+ } else {
+ echo 'Restore failed: ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $mid Mailbox ID
+ * @param $iid Item ID
+ * @param $json JSON
+ * @return STRING
+ */
+ public function restoreMailItem($iid, $mid, $rid, $json) {
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/Mailboxes/'.$mid.'/Items/'.$iid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ if ($result['createdItemsCount'] == '1') {
+ echo 'Item has been restored.';
+ } elseif ($result['mergedItemsCount'] == '1') {
+ echo 'Item has been restored and has been merged.';
+ } elseif ($result['failedItemsCount'] == '1') {
+ echo 'Item restore failed.';
+ } elseif ($result['skippedItemsCount'] == '1') {
+ echo 'Item has been skipped.';
+ } else {
+ echo 'Restore failed: ' . $result['message'];
+ }
+ } else {
+ echo 'Restore failed: ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $mid Mailbox ID
+ * @param $json JSON
+ * @return STRING
+ */
+ public function restoreMultipleMailItems($mid, $rid, $json) {
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/Mailboxes/'.$mid.'/Items/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ if ($result['createdItemsCount'] >= '1') {
+ echo 'Items have been restored.';
+ } elseif ($result['mergedItemsCount'] == '1') {
+ echo 'Items have been restored and have been merged.';
+ } elseif ($result['failedItemsCount'] == '1') {
+ echo 'Items restore failed.';
+ } elseif ($result['skippedItemsCount'] == '1') {
+ echo 'Items have been skipped.';
+ } else {
+ echo 'Restore failed: ' . $result['message'];
+ }
+ } else {
+ echo 'Restore failed: ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * Start OneDrive for Business functions
+ */
+
+ /**
+ * @param $rid Restore Session ID
+ * @return $result
+ */
+ public function getOneDrives($rid) {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$rid.'/Organization/OneDrives?offset=0&limit=1000', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ } elseif ($response->getStatusCode() === 500) {
+ return '500';
+ } else {
+ echo $response->getStatusCode() . ' - ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $uid User ID
+ * @return $result
+ */
+ public function getOneDriveID($rid, $uid) {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$rid.'/Organization/OneDrives/'.$uid, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $uid OneDrive User ID
+ * @param $pid Parent ID (null or item ID)
+ * @param $type Folders (default) or documents
+ * @param $parent Request parent folder - true or false
+ * @return $result
+ */
+ public function getOneDriveParentFolder($rid, $uid, $type = 'Folders', $pid) {
+ $call = 'RestoreSessions/'.$rid.'/Organization/OneDrives/'.$uid.'/'.$type.'/'.$pid;
+
+ try {
+ $response = $this->client->request('GET', $call, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $uid OneDrive User ID
+ * @param $pid Parent ID (null or item ID)
+ * @param $type Folders (default) or documents
+ * @param $offset Offset
+ * @return $result
+ */
+ public function getOneDriveTree($rid, $uid, $type = 'Folders', $pid = null, $offset = null) {
+ $call = 'RestoreSessions/'.$rid.'/Organization/OneDrives/'.$uid.'/'.$type;
+
+ if (isset($pid)) {
+ $call .= '?parentID=' . $pid;
+
+ if (isset($offset)) {
+ $call .= '&offset=' . $offset;
+ }
+ } else {
+ $call .= '?parentID=null';
+
+ if (isset($offset)) {
+ $call .= '&offset=' . $offset;
+ }
+ }
+
+ try {
+ $response = $this->client->request('GET', $call, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $uid User ID
+ * @param $json JSON format
+ * @return $file
+ */
+ public function exportOneDrive($uid, $rid, $json) {
+ $tmpFile = sys_get_temp_dir() . '/' . $uid;
+ $resource = fopen($tmpFile, 'w');
+
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/OneDrives/'.$uid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/octet-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'verify' => false,
+ 'body' => $json,
+ 'sink' => $resource,
+ ]
+ );
+
+ fclose($resource);
+
+ if ($response->getStatusCode() === 200) {
+ echo $tmpFile;
+ } else {
+ echo $response->getStatusCode();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $uid User ID
+ * @param $iid Item ID
+ * @param $json JSON format
+ * @param $type Folders (default) or documents
+ * @return $file
+ */
+ public function exportOneDriveItem($iid, $uid, $rid, $json, $type = 'Folders') {
+ $tmpFile = sys_get_temp_dir() . '/' . $iid;
+ $resource = fopen($tmpFile, 'w');
+
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/OneDrives/'.$uid.'/'.$type.'/'.$iid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/octet-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'verify' => false,
+ 'body' => $json,
+ 'sink' => $resource,
+ ]
+ );
+
+ fclose($resource);
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ echo $tmpFile;
+ } elseif ($response->getStatusCode() === 500) {
+ echo '500';
+ } else {
+ echo $response->getStatusCode();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $uid User ID
+ * @param $iid Item ID
+ * @param $json JSON format
+ * @param $type Documents
+ * @return $file
+ */
+ public function exportMultipleOneDriveItems($iid, $uid, $rid, $json, $type = 'Documents') {
+ $tmpFile = sys_get_temp_dir() . '/' . $iid;
+ $resource = fopen($tmpFile, 'w');
+
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/OneDrives/'.$uid.'/'.$type.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/octet-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json,
+ 'sink' => $resource,
+ ]
+ );
+
+ fclose($resource);
+
+ if ($response->getStatusCode() === 200) {
+ echo $tmpFile;
+ } else {
+ echo 'error';
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $uid User ID
+ * @param $json JSON format
+ * @return $result
+ */
+ public function restoreOneDrive($uid, $rid, $json) {
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/OneDrives/'.$uid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ if ($result['restoredItemsCount'] >= '1') {
+ echo 'Item has been restored.';
+ } else {
+ echo 'Failed to restore the item.';
+ }
+ } else {
+ echo 'Restore failed: ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $iid Item ID
+ * @param $rid Restore Session ID
+ * @param $uid User ID
+ * @param $json JSON format
+ * @param $type Folders (default) or documents
+ * @return $result
+ */
+ public function restoreOneDriveItem($iid, $uid, $rid, $json, $type = 'Folders') {
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/OneDrives/'.$uid.'/'.$type.'/'.$iid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ if ($result['restoredItemsCount'] >= '1') {
+ echo 'Item has been restored.';
+ } else {
+ echo 'Failed to restore the item.';
+ }
+ } else {
+ echo $response->getStatusCode() . ' Restore failed: ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $uid User ID
+ * @param $json JSON format
+ * @param $type Documents
+ * @return $result
+ */
+ public function restoreMultipleOneDriveItems($uid, $rid, $json, $type = 'Documents') {
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/OneDrives/'.$uid.'/'.$type.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ if ($result['restoredItemsCount'] >= '1') {
+ echo 'Items have been restored.';
+ } else {
+ echo 'Failed to restore the items.';
+ }
+ } else {
+ echo $response->getStatusCode() . ' Restore failed: ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+
+ /**
+ * Start SharePoint functions
+ */
+
+ /**
+ * @param $rid Restore Session ID
+ * @return $result
+ */
+ public function getSharePointSites($rid) {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$rid.'/Organization/Sites?offset=0&limit=1000', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ } elseif ($response->getStatusCode() === 500) {
+ return '500';
+ } else {
+ echo $response->getStatusCode() . ' - ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $sid SharePoint Site ID
+ * @param $iid Item ID
+ * @param $json JSON format
+ * @param $type Folders (default) or documents
+ * @return $file
+ */
+ public function exportSharePointItem($iid, $sid, $rid, $json, $type = 'Folders') {
+ $tmpFile = sys_get_temp_dir() . '/' . $iid;
+ $resource = fopen($tmpFile, 'w');
+
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/Sites/'.$sid.'/'.$type.'/'.$iid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/octet-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'verify' => false,
+ 'body' => $json,
+ 'sink' => $resource,
+ ]
+ );
+
+ fclose($resource);
+
+ if ($response->getStatusCode() === 200) {
+ echo $tmpFile;
+ } else {
+ echo $response->getStatusCode();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $sid SharePoint Site ID
+ * @return $result
+ */
+ public function getSharePointLists($rid, $sid) {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$rid.'/Organization/Sites/'.$sid.'/Lists?offset=0&limit=1000', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $sid SharePoint Site ID
+ * @return $result
+ */
+ public function getSharePointContent($rid, $sid, $type) {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$rid.'/Organization/Sites/'.$sid.'/'.$type, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ } elseif ($response->getStatusCode() === 500) {
+ return '500';
+ } else {
+ echo $response->getStatusCode() . ' - ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $sid SharePoint Site ID
+ * @param $cid Content ID
+ * @param $type Folders (default), items or documents
+ * @return $result
+ */
+ public function getSharePointListName($rid, $sid, $cid, $type = 'Folders') {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$rid.'/Organization/Sites/'.$sid.'/'.$type.'/'.$cid, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $sid SharePoint Site ID
+ * @return $result
+ */
+ public function getSharePointSiteName($rid, $sid) {
+ try {
+ $response = $this->client->request('GET', 'RestoreSessions/'.$rid.'/Organization/Sites/'.$sid, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $sid SharePoint Site ID
+ * @param $pid Parent Content ID
+ * @param $type Folders (default), items or documents
+ * @param $offset Offset
+ * @return $result
+ */
+ public function getSharePointTree($rid, $sid, $pid, $type = 'Folders', $offset = null) {
+ $call = 'RestoreSessions/'.$rid.'/Organization/Sites/'.$sid.'/'.$type.'?parentId='.$pid;
+
+ if (isset($offset)) {
+ $call .= '&offset=' . $offset;
+ }
+
+ try {
+ $response = $this->client->request('GET', $call, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ return($result);
+ } elseif ($response->getStatusCode() === 401) {
+ $this->logout();
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $sid SharePoint Site ID
+ * @param $json JSON format
+ * @return $result
+ */
+ public function restoreSharePoint($sid, $rid, $json) {
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/Sites/'.$sid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ if (count($result['restoreIssues']) >= '1') {
+ echo 'SharePoint site has been restored with warnings.';
+ } elseif ($result['failedWebsCount'] >= '1') {
+ echo 'Failed to restore the SharePoint site.';
+ } elseif ($result['failedRestrictionsCount'] >= '1') {
+ echo 'Failed to restore the SharePoint site due to restrictions errors.';
+ } else {
+ echo 'SharePoint site has been restored.';
+ }
+ } else {
+ echo 'Restore failed: ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $iid Item ID
+ * @param $rid Restore Session ID
+ * @param $sid SharePoint Site ID
+ * @param $json JSON format
+ * @param $type Folders (default) or documents
+ * @return $result
+ */
+ public function restoreSharePointItem($iid, $sid, $rid, $json, $type = 'Folders') {
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/Sites/'.$sid.'/'.$type.'/'.$iid.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ if ($result['restoredItemsCount'] >= '1') {
+ echo 'Item has been restored.';
+ } elseif ($result['skippedItemsByNoChangesCount'] == '1') {
+ echo 'Failed to restore the item: items already exists with the same permissions.';
+ } elseif ($result['failedRestrictionsCount'] == '1') {
+ echo 'Failed to restore the item: permission denied to restore the item with this account.';
+ } else {
+ echo 'Failed to restore the item.';
+ }
+ } else {
+ echo 'Restore failed: ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+
+ /**
+ * @param $rid Restore Session ID
+ * @param $sid Site ID
+ * @param $json JSON format
+ * @param $type Documents
+ * @return $result
+ */
+ public function restoreMultipleSharePointItems($sid, $rid, $json, $type = 'Documents') {
+ try {
+ $response = $this->client->request('POST', 'RestoreSessions/'.$rid.'/Organization/Sites/'.$sid.'/'.$type.'/Action', [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $this->token,
+ 'Accept' => 'application/json-stream',
+ 'Content-Type' => 'application/json'
+ ],
+ 'http_errors' => false,
+ 'verify' => false,
+ 'body' => $json,
+ ]
+ );
+
+ $result = json_decode($response->getBody(), true);
+
+ if ($response->getStatusCode() === 200) {
+ if ($result['restoredItemsCount'] >= '1') {
+ echo 'Items have been restored.';
+ } else {
+ echo 'Failed to restore the items.';
+ }
+ } else {
+ echo $response->getStatusCode() . ' Restore failed: ' . $result['message'];
+ }
+ } catch (RequestException $e) {
+ if ($e->hasResponse()) {
+ $exception = (string) $e->getResponse()->getBody();
+ $exception = json_decode($exception, true);
+
+ echo 'Error: ' . $exception['message'];
+ } else {
+ echo $e->getMessage();
+ }
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/css/exchange.css b/css/exchange.css
new file mode 100644
index 0000000..9c10879
--- /dev/null
+++ b/css/exchange.css
@@ -0,0 +1,41 @@
+#sidebar .menu-segment ul,
+#sidebar .menu-segment li {
+ margin: 0;
+ padding: 0;
+ text-align: left;
+}
+
+.exchange-container {
+ display: table;
+ float: left;
+ margin-bottom: 100px;
+ margin-left: 5px;
+ overflow: auto;
+ padding-top: 10px;
+ width: 100%;
+}
+.exchange-container table {
+ width: 100%;
+}
+
+div.inbox-header {
+ border-bottom: 1px solid #ccc;
+ float: none;
+ height: 25px;
+ margin-top: 10px;
+ text-align: center;
+ vertical-align: middle;
+}
+ul.nav-pills {
+ margin: 0;
+ padding: 5px 10px;
+}
+ul.inbox-nav {
+ display: inline-block;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+ul.inbox-divider {
+ border-bottom: 1px solid #d5d8df;
+}
\ No newline at end of file
diff --git a/css/flatpickr.min.css b/css/flatpickr.min.css
new file mode 100644
index 0000000..5bcd1ff
--- /dev/null
+++ b/css/flatpickr.min.css
@@ -0,0 +1,13 @@
+.flatpickr-calendar{background:transparent;opacity:0;display:none;text-align:center;visibility:hidden;padding:0;-webkit-animation:none;animation:none;direction:ltr;border:0;font-size:14px;line-height:24px;border-radius:5px;position:absolute;width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-touch-action:manipulation;touch-action:manipulation;background:#fff;-webkit-box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);}.flatpickr-calendar.open,.flatpickr-calendar.inline{opacity:1;max-height:640px;visibility:visible}.flatpickr-calendar.open{display:inline-block;z-index:99999}.flatpickr-calendar.animate.open{-webkit-animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1);animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1)}.flatpickr-calendar.inline{display:block;position:relative;top:2px}.flatpickr-calendar.static{position:absolute;top:calc(100% + 2px);}.flatpickr-calendar.static.open{z-index:999;display:block}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7){-webkit-box-shadow:none !important;box-shadow:none !important}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1){-webkit-box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-calendar .hasWeeks .dayContainer,.flatpickr-calendar .hasTime .dayContainer{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.flatpickr-calendar .hasWeeks .dayContainer{border-left:0}.flatpickr-calendar.showTimeInput.hasTime .flatpickr-time{height:40px;border-top:1px solid #e6e6e6}.flatpickr-calendar.noCalendar.hasTime .flatpickr-time{height:auto}.flatpickr-calendar:before,.flatpickr-calendar:after{position:absolute;display:block;pointer-events:none;border:solid transparent;content:'';height:0;width:0;left:22px}.flatpickr-calendar.rightMost:before,.flatpickr-calendar.rightMost:after{left:auto;right:22px}.flatpickr-calendar:before{border-width:5px;margin:0 -5px}.flatpickr-calendar:after{border-width:4px;margin:0 -4px}.flatpickr-calendar.arrowTop:before,.flatpickr-calendar.arrowTop:after{bottom:100%}.flatpickr-calendar.arrowTop:before{border-bottom-color:#e6e6e6}.flatpickr-calendar.arrowTop:after{border-bottom-color:#fff}.flatpickr-calendar.arrowBottom:before,.flatpickr-calendar.arrowBottom:after{top:100%}.flatpickr-calendar.arrowBottom:before{border-top-color:#e6e6e6}.flatpickr-calendar.arrowBottom:after{border-top-color:#fff}.flatpickr-calendar:focus{outline:0}.flatpickr-wrapper{position:relative;display:inline-block}.flatpickr-months{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}.flatpickr-months .flatpickr-month{background:transparent;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9);height:28px;line-height:1;text-align:center;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:hidden;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.flatpickr-months .flatpickr-prev-month,.flatpickr-months .flatpickr-next-month{text-decoration:none;cursor:pointer;position:absolute;top:0;line-height:16px;height:28px;padding:10px;z-index:3;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9);}.flatpickr-months .flatpickr-prev-month.disabled,.flatpickr-months .flatpickr-next-month.disabled{display:none}.flatpickr-months .flatpickr-prev-month i,.flatpickr-months .flatpickr-next-month i{position:relative}.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,.flatpickr-months .flatpickr-next-month.flatpickr-prev-month{/*
+ /*rtl:begin:ignore*/left:0;/*
+ /*rtl:end:ignore*/}/*
+ /*rtl:begin:ignore*/
+/*
+ /*rtl:end:ignore*/
+.flatpickr-months .flatpickr-prev-month.flatpickr-next-month,.flatpickr-months .flatpickr-next-month.flatpickr-next-month{/*
+ /*rtl:begin:ignore*/right:0;/*
+ /*rtl:end:ignore*/}/*
+ /*rtl:begin:ignore*/
+/*
+ /*rtl:end:ignore*/
+.flatpickr-months .flatpickr-prev-month:hover,.flatpickr-months .flatpickr-next-month:hover{color:#959ea9;}.flatpickr-months .flatpickr-prev-month:hover svg,.flatpickr-months .flatpickr-next-month:hover svg{fill:#f64747}.flatpickr-months .flatpickr-prev-month svg,.flatpickr-months .flatpickr-next-month svg{width:14px;height:14px;}.flatpickr-months .flatpickr-prev-month svg path,.flatpickr-months .flatpickr-next-month svg path{-webkit-transition:fill .1s;transition:fill .1s;fill:inherit}.numInputWrapper{position:relative;height:auto;}.numInputWrapper input,.numInputWrapper span{display:inline-block}.numInputWrapper input{width:100%;}.numInputWrapper input::-ms-clear{display:none}.numInputWrapper span{position:absolute;right:0;width:14px;padding:0 4px 0 2px;height:50%;line-height:50%;opacity:0;cursor:pointer;border:1px solid rgba(57,57,57,0.15);-webkit-box-sizing:border-box;box-sizing:border-box;}.numInputWrapper span:hover{background:rgba(0,0,0,0.1)}.numInputWrapper span:active{background:rgba(0,0,0,0.2)}.numInputWrapper span:after{display:block;content:"";position:absolute}.numInputWrapper span.arrowUp{top:0;border-bottom:0;}.numInputWrapper span.arrowUp:after{border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:4px solid rgba(57,57,57,0.6);top:26%}.numInputWrapper span.arrowDown{top:50%;}.numInputWrapper span.arrowDown:after{border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid rgba(57,57,57,0.6);top:40%}.numInputWrapper span svg{width:inherit;height:auto;}.numInputWrapper span svg path{fill:rgba(0,0,0,0.5)}.numInputWrapper:hover{background:rgba(0,0,0,0.05);}.numInputWrapper:hover span{opacity:1}.flatpickr-current-month{font-size:135%;line-height:inherit;font-weight:300;color:inherit;position:absolute;width:75%;left:12.5%;padding:6.16px 0 0 0;line-height:1;height:28px;display:inline-block;text-align:center;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}.flatpickr-current-month span.cur-month{font-family:inherit;font-weight:700;color:inherit;display:inline-block;margin-left:.5ch;padding:0;}.flatpickr-current-month span.cur-month:hover{background:rgba(0,0,0,0.05)}.flatpickr-current-month .numInputWrapper{width:6ch;width:7ch\0;display:inline-block;}.flatpickr-current-month .numInputWrapper span.arrowUp:after{border-bottom-color:rgba(0,0,0,0.9)}.flatpickr-current-month .numInputWrapper span.arrowDown:after{border-top-color:rgba(0,0,0,0.9)}.flatpickr-current-month input.cur-year{background:transparent;-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;cursor:text;padding:0 0 0 .5ch;margin:0;display:inline-block;font-size:inherit;font-family:inherit;font-weight:300;line-height:inherit;height:auto;border:0;border-radius:0;vertical-align:initial;}.flatpickr-current-month input.cur-year:focus{outline:0}.flatpickr-current-month input.cur-year[disabled],.flatpickr-current-month input.cur-year[disabled]:hover{font-size:100%;color:rgba(0,0,0,0.5);background:transparent;pointer-events:none}.flatpickr-weekdays{background:transparent;text-align:center;overflow:hidden;width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:28px;}.flatpickr-weekdays .flatpickr-weekdaycontainer{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}span.flatpickr-weekday{cursor:default;font-size:90%;background:transparent;color:rgba(0,0,0,0.54);line-height:1;margin:0;text-align:center;display:block;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;font-weight:bolder}.dayContainer,.flatpickr-weeks{padding:1px 0 0 0}.flatpickr-days{position:relative;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:start;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;width:307.875px;}.flatpickr-days:focus{outline:0}.dayContainer{padding:0;outline:0;text-align:left;width:307.875px;min-width:307.875px;max-width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;display:inline-block;display:-ms-flexbox;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-wrap:wrap;-ms-flex-pack:justify;-webkit-justify-content:space-around;justify-content:space-around;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1;}.dayContainer + .dayContainer{-webkit-box-shadow:-1px 0 0 #e6e6e6;box-shadow:-1px 0 0 #e6e6e6}.flatpickr-day{background:none;border:1px solid transparent;border-radius:150px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#393939;cursor:pointer;font-weight:400;width:14.2857143%;-webkit-flex-basis:14.2857143%;-ms-flex-preferred-size:14.2857143%;flex-basis:14.2857143%;max-width:39px;height:39px;line-height:39px;margin:0;display:inline-block;position:relative;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;}.flatpickr-day.inRange,.flatpickr-day.prevMonthDay.inRange,.flatpickr-day.nextMonthDay.inRange,.flatpickr-day.today.inRange,.flatpickr-day.prevMonthDay.today.inRange,.flatpickr-day.nextMonthDay.today.inRange,.flatpickr-day:hover,.flatpickr-day.prevMonthDay:hover,.flatpickr-day.nextMonthDay:hover,.flatpickr-day:focus,.flatpickr-day.prevMonthDay:focus,.flatpickr-day.nextMonthDay:focus{cursor:pointer;outline:0;background:#e6e6e6;border-color:#e6e6e6}.flatpickr-day.today{border-color:#959ea9;}.flatpickr-day.today:hover,.flatpickr-day.today:focus{border-color:#959ea9;background:#959ea9;color:#fff}.flatpickr-day.selected,.flatpickr-day.startRange,.flatpickr-day.endRange,.flatpickr-day.selected.inRange,.flatpickr-day.startRange.inRange,.flatpickr-day.endRange.inRange,.flatpickr-day.selected:focus,.flatpickr-day.startRange:focus,.flatpickr-day.endRange:focus,.flatpickr-day.selected:hover,.flatpickr-day.startRange:hover,.flatpickr-day.endRange:hover,.flatpickr-day.selected.prevMonthDay,.flatpickr-day.startRange.prevMonthDay,.flatpickr-day.endRange.prevMonthDay,.flatpickr-day.selected.nextMonthDay,.flatpickr-day.startRange.nextMonthDay,.flatpickr-day.endRange.nextMonthDay{background:#569ff7;-webkit-box-shadow:none;box-shadow:none;color:#fff;border-color:#569ff7}.flatpickr-day.selected.startRange,.flatpickr-day.startRange.startRange,.flatpickr-day.endRange.startRange{border-radius:50px 0 0 50px}.flatpickr-day.selected.endRange,.flatpickr-day.startRange.endRange,.flatpickr-day.endRange.endRange{border-radius:0 50px 50px 0}.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)){-webkit-box-shadow:-10px 0 0 #569ff7;box-shadow:-10px 0 0 #569ff7}.flatpickr-day.selected.startRange.endRange,.flatpickr-day.startRange.startRange.endRange,.flatpickr-day.endRange.startRange.endRange{border-radius:50px}.flatpickr-day.inRange{border-radius:0;-webkit-box-shadow:-5px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-5px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-day.disabled,.flatpickr-day.disabled:hover,.flatpickr-day.prevMonthDay,.flatpickr-day.nextMonthDay,.flatpickr-day.notAllowed,.flatpickr-day.notAllowed.prevMonthDay,.flatpickr-day.notAllowed.nextMonthDay{color:rgba(57,57,57,0.3);background:transparent;border-color:transparent;cursor:default}.flatpickr-day.disabled,.flatpickr-day.disabled:hover{cursor:not-allowed;color:rgba(57,57,57,0.1)}.flatpickr-day.week.selected{border-radius:0;-webkit-box-shadow:-5px 0 0 #569ff7,5px 0 0 #569ff7;box-shadow:-5px 0 0 #569ff7,5px 0 0 #569ff7}.flatpickr-day.hidden{visibility:hidden}.rangeMode .flatpickr-day{margin-top:1px}.flatpickr-weekwrapper{display:inline-block;float:left;}.flatpickr-weekwrapper .flatpickr-weeks{padding:0 12px;-webkit-box-shadow:1px 0 0 #e6e6e6;box-shadow:1px 0 0 #e6e6e6}.flatpickr-weekwrapper .flatpickr-weekday{float:none;width:100%;line-height:28px}.flatpickr-weekwrapper span.flatpickr-day,.flatpickr-weekwrapper span.flatpickr-day:hover{display:block;width:100%;max-width:none;color:rgba(57,57,57,0.3);background:transparent;cursor:default;border:none}.flatpickr-innerContainer{display:block;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;}.flatpickr-rContainer{display:inline-block;padding:0;-webkit-box-sizing:border-box;box-sizing:border-box}.flatpickr-time{text-align:center;outline:0;display:block;height:0;line-height:40px;max-height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}.flatpickr-time:after{content:"";display:table;clear:both}.flatpickr-time .numInputWrapper{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;width:40%;height:40px;float:left;}.flatpickr-time .numInputWrapper span.arrowUp:after{border-bottom-color:#393939}.flatpickr-time .numInputWrapper span.arrowDown:after{border-top-color:#393939}.flatpickr-time.hasSeconds .numInputWrapper{width:26%}.flatpickr-time.time24hr .numInputWrapper{width:49%}.flatpickr-time input{background:transparent;-webkit-box-shadow:none;box-shadow:none;border:0;border-radius:0;text-align:center;margin:0;padding:0;height:inherit;line-height:inherit;color:#393939;font-size:14px;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;}.flatpickr-time input.flatpickr-hour{font-weight:bold}.flatpickr-time input.flatpickr-minute,.flatpickr-time input.flatpickr-second{font-weight:400}.flatpickr-time input:focus{outline:0;border:0}.flatpickr-time .flatpickr-time-separator,.flatpickr-time .flatpickr-am-pm{height:inherit;display:inline-block;float:left;line-height:inherit;color:#393939;font-weight:bold;width:2%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.flatpickr-time .flatpickr-am-pm{outline:0;width:18%;cursor:pointer;text-align:center;font-weight:400}.flatpickr-time input:hover,.flatpickr-time .flatpickr-am-pm:hover,.flatpickr-time input:focus,.flatpickr-time .flatpickr-am-pm:focus{background:#f3f3f3}.flatpickr-input[readonly]{cursor:pointer}@-webkit-keyframes fpFadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:1;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes fpFadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:1;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}
\ No newline at end of file
diff --git a/css/fontawesome.min.css b/css/fontawesome.min.css
new file mode 100644
index 0000000..812418a
--- /dev/null
+++ b/css/fontawesome.min.css
@@ -0,0 +1,5 @@
+/*!
+ * Font Awesome Free 5.2.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:a 2s infinite linear}.fa-pulse{animation:a 1s infinite steps(8)}@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blind:before{content:"\f29d"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-certificate:before{content:"\f0a3"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-concierge-bell:before{content:"\f562"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-deviantart:before{content:"\f1bd"}.fa-diagnoses:before{content:"\f470"}.fa-dice:before{content:"\f522"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-gift:before{content:"\f06b"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hashtag:before{content:"\f292"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hot-tub:before{content:"\f593"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-internet-explorer:before{content:"\f26b"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mercury:before{content:"\f223"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-motorcycle:before{content:"\f21c"}.fa-mouse-pointer:before{content:"\f245"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-carry:before{content:"\f4ce"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poo:before{content:"\f2fe"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-r-project:before{content:"\f4f7"}.fa-random:before{content:"\f074"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-rendact:before{content:"\f3e4"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-search:before{content:"\f002"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skull:before{content:"\f54c"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowflake:before{content:"\f2dc"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-swatchbook:before{content:"\f5c3"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toolbox:before{content:"\f552"}.fa-tooth:before{content:"\f5c9"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-train:before{content:"\f238"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900}
\ No newline at end of file
diff --git a/css/onedrive.css b/css/onedrive.css
new file mode 100644
index 0000000..fa7930d
--- /dev/null
+++ b/css/onedrive.css
@@ -0,0 +1,19 @@
+#sidebar .menu-segment ul,
+#sidebar .menu-segment li {
+ margin: 0;
+ padding: 0;
+ text-align: left;
+}
+
+.onedrive-container {
+ display: table;
+ float: left;
+ margin-bottom: 100px;
+ margin-left: 5px;
+ overflow: auto;
+ padding-top: 10px;
+ width: 100%;
+}
+.onedrive-container table {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/css/selfservicestyle.css b/css/selfservicestyle.css
new file mode 100644
index 0000000..12fdec5
--- /dev/null
+++ b/css/selfservicestyle.css
@@ -0,0 +1,311 @@
+body {
+ font: 13px/19px Arial, Helvetica, sans-serif;
+ padding: 0px !important;
+}
+h1 {
+ min-height: 1rem;
+ font-size: 30px;
+}
+h1, h2, h3, h4, h5 {
+ color: #000000;
+ font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif;
+ font-weight: 700;
+ margin-left: 20px;
+}
+
+#logout {
+ margin-right: 25px;
+}
+#main {
+ background: #fcfeff;
+ color: #000000;
+ height: 100%;
+ left: 210px;
+ overflow: auto;
+ padding-bottom: 30px;
+ padding-left: 30px;
+ padding-right: 30px;
+ padding-top: 30px;
+ right: 0;
+ position: fixed;
+ top: 50px;
+}
+
+#sidebar {
+ background: #222;
+ height: 100%;
+ left: 0;
+ overflow: auto;
+ position: fixed;
+ top: 50px;
+ width: 210px;
+}
+#sidebar,
+#sidebar a {
+ color: #ABB4BE;
+}
+#sidebar .logo-container {
+ cursor: pointer;
+ font-weight: 100;
+ font-size: 30px;
+ line-height: 30px;
+ margin: 20px 0 0 0;
+ text-align: center;
+}
+#sidebar .logo-container .logo {
+ border: 2px solid #61C7B3;
+ border-radius: 100px;
+ color: #4CAF50;
+ font-size: 25px;
+ position: relative;
+ padding: 10px;
+ text-indent: 1px;
+}
+#sidebar .menu-segment {
+ padding-left: 5px;
+ text-align: center;
+ word-wrap: break-word;
+}
+#sidebar .menu-segment ul,
+#sidebar .menu-segment li {
+ cursor: pointer;
+ list-style: none;
+ margin: 10px;
+ padding: 3px;
+ text-align: left;
+}
+#sidebar .menu-segment li a {
+ display: block;
+ margin: 0;
+ padding: 10px 10px 10px 10px;
+}
+#sidebar .menu-segment li.active a,
+#sidebar .menu-segment li.active a:hover {
+ background: #282d36;
+}
+#sidebar .menu-segment li a:hover {
+ background: #21262d;
+}
+#sidebar .separator {
+ background: #2D3138;
+ height: 1px;
+ margin: 10px 15px;
+}
+#sidebar .bottom-padding {
+ height: 100px;
+}
+
+/* Navigation */
+.navbar {
+ overflow: hidden!important;
+ margin-bottom:0px !important;
+ position: static;
+}
+.navbar-inner {
+ border-radius: 0px !important;
+}
+a.navbar-logo {
+ width: 210px;
+}
+a.navbar-logo:hover {
+ text-decoration: none;
+}
+img.logo {
+ height: 100%;
+ width: auto;
+}
+a.navbar-right: {
+ margin-right: 10px;
+}
+textarea.noresize {
+ resize: none;
+}
+
+/* Search Form */
+.search {
+ background: url("../images/search.png") no-repeat left;
+ background-size: 15px;
+ background-position: 13px;
+ border: 1px solid #ccc;
+ border-radius: 50px;
+ height: 35px;
+ margin-bottom: 10px;
+ padding-left: 35px;
+ outline: none;
+}
+.search:focus {
+ padding-left: 35px;
+}
+
+/* Caret style */
+.caret {
+ position: relative;
+}
+
+/* Dropdown style */
+.dropdown.open .caret:before,
+.dropdown.open .caret:after {
+ border-color: transparent;
+ border-style: solid;
+ border-width: 7px 8px;
+ display: block;
+ content: '';
+ height: 0;
+ position: absolute;
+ width: 0;
+ z-index: 1001;
+}
+.dropdown.open .caret:before {
+ border-bottom-color: #ccc;
+ bottom: -17px;
+ right: -8px;
+}
+.dropdown.open .caret:after {
+ border-bottom-color: #fff;
+ bottom: -18px;
+ right: -8px;
+}
+
+/* Panels */
+.panel-primary a:hover {
+ color: #000000;
+}
+.panel-gray {
+ border-color: #BFBFBF;
+}
+.panel-gray .panel-heading {
+ background-color: #BFBFBF;
+ border-color: #BFBFBF;
+ color: #ffffff;
+}
+.panel-gray a {
+ color: #BFBFBF;
+}
+.panel-gray a:hover {
+ color: #000000;
+}
+.panel-green {
+ border-color: #5cb85c;
+}
+.panel-green .panel-heading {
+ background-color: #5cb85c;
+ border-color: #5cb85c;
+ color: #ffffff;
+}
+.panel-green a {
+ color: #5cb85c;
+}
+.panel-green a:hover {
+ color: #000000;
+}
+.panel-lightgreen {
+ border-color: #00604B;
+}
+.panel-lightgreen .panel-heading {
+ background-color: #00604B;
+ border-color: #00604B;
+ color: #ffffff;
+}
+.panel-lightgreen a {
+ color: #00604B;
+}
+.panel-lightgreen a:hover {
+ color: #000000;
+}
+.panel-yellow {
+ border-color: #f0ad4e;
+}
+.panel-yellow .panel-heading {
+ background-color: #f0ad4e;
+ border-color: #f0ad4e;
+ color: #ffffff;
+}
+.panel-yellow a {
+ color: #f0ad4e;
+}
+.panel-yellow a:hover {
+ color: #000000;
+}
+
+.bottom-padding {
+ height: 100px;
+}
+.btn-margin {
+ margin-right: 10px;
+}
+.container {
+ padding-top: 50px;
+ position: fixed;
+}
+.errorClass {
+ border: 1px solid red;
+}
+.icon {
+ float: left;
+ margin-left: 10px;
+ margin-top: -38px;
+ position: relative;
+}
+.load-more-link {
+ border-radius: 23px;
+ margin-top: 20px;
+}
+.main-container {
+ display: table;
+ float: left;
+ margin-bottom: 100px;
+ overflow: auto;
+ width: 100%;
+}
+.marginexplore {
+ margin-bottom: 10px;
+}
+.paddingdate {
+ margin-left: -10px;
+}
+.select-align {
+ margin-top: 10px;
+}
+.swal2-popup {
+ font-size: 1.6rem !important;
+}
+
+/* Vertical align modal */
+.modal {
+ text-align: center;
+ padding: 0!important;
+}
+.modal-backdrop {
+ z-index: -1;
+}
+.modal:before {
+ display: inline-block;
+ height: 100%;
+ margin-right: -4px; /* Adjusts for spacing */
+ vertical-align: middle;
+}
+.modal-dialog {
+ display: inline-block;
+ text-align: left;
+ vertical-align: middle;
+}
+
+table.table-border {
+ border: 1px solid #ccc;
+ margin-top: 1px;
+}
+table.table-padding {
+ margin: auto !important;
+ margin-bottom: 20px;
+ padding: 0 !important;
+}
+table.table-small {
+ margin-bottom: 20px;
+ margin-left: 40px;
+ margin-top: 10px;
+ width: 92%;
+}
+.zeroPadding {
+ padding: 0 !important;
+ background: #ffffff;
+}
\ No newline at end of file
diff --git a/css/sharepoint.css b/css/sharepoint.css
new file mode 100644
index 0000000..90b3f86
--- /dev/null
+++ b/css/sharepoint.css
@@ -0,0 +1,19 @@
+#sidebar .menu-segment ul,
+#sidebar .menu-segment li {
+ margin: 0;
+ padding: 0;
+ text-align: left;
+}
+
+.sharepoint-container {
+ display: table;
+ float: left;
+ margin-bottom: 100px;
+ margin-left: 5px;
+ overflow: auto;
+ padding-top: 10px;
+ width: 100%;
+}
+.sharepoint-container table {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/css/style.css b/css/style.css
new file mode 100644
index 0000000..fce713b
--- /dev/null
+++ b/css/style.css
@@ -0,0 +1,454 @@
+body {
+ font: 13px/19px Arial, Helvetica, sans-serif;
+ padding: 0px !important;
+}
+h1 {
+ min-height: 1rem;
+ font-size: 30px;
+}
+h1, h2, h3, h4, h5 {
+ color: #000000;
+ font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif;
+ font-weight: 700;
+ margin-left: 20px;
+}
+
+#logout {
+ margin-right: 25px;
+}
+#main {
+ background: #fcfeff;
+ color: #000000;
+ height: 100%;
+ left: 210px;
+ overflow: auto;
+ padding-bottom: 30px;
+ padding-left: 30px;
+ padding-right: 30px;
+ padding-top: 30px;
+ right: 0;
+ position: fixed;
+ top: 50px;
+}
+
+#sidebar {
+ background: #4c6e5e;
+ height: 100%;
+ left: 0;
+ overflow: auto;
+ position: fixed;
+ top: 50px;
+ width: 210px;
+}
+#sidebar,
+#sidebar a {
+ color: #FFF;
+}
+#sidebar .logo-container {
+ cursor: pointer;
+ font-weight: 100;
+ font-size: 30px;
+ line-height: 30px;
+ margin: 20px 0 0 0;
+ text-align: center;
+}
+#sidebar .logo-container .logo {
+ border: 2px solid #FFF;
+ border-radius: 100px;
+ color: #FFF;
+ font-size: 25px;
+ position: relative;
+ padding: 10px;
+ text-indent: 1px;
+}
+#sidebar .menu-segment {
+ padding-left: 5px;
+ text-align: center;
+ word-wrap: break-word;
+}
+#sidebar .menu-segment ul,
+#sidebar .menu-segment li {
+ cursor: pointer;
+ list-style: none;
+ margin: 10px;
+ padding: 3px;
+ text-align: left;
+}
+#sidebar .menu-segment li a {
+ display: block;
+ margin: 0;
+ padding: 10px 10px 10px 10px;
+}
+#sidebar .menu-segment li.active a,
+#sidebar .menu-segment li.active a:hover {
+ background: #282d36;
+}
+#sidebar .menu-segment li a:hover {
+ background: #21262d;
+}
+#sidebar .separator {
+ background: #2D3138;
+ height: 1px;
+ margin: 10px 15px;
+}
+#sidebar .bottom-padding {
+ height: 100px;
+}
+
+/* Navigation */
+.navbar {
+ background: #4c6e5e;
+ color: #FFF;
+ overflow: hidden!important;
+ margin-bottom:0px !important;
+ position: static;
+}
+.navbar-default {
+ background: #4c6e5e;
+ border-color: #E7E7E7;
+}
+.navbar-inner {
+ border-radius: 0px !important;
+}
+.navbar-nav > li {
+ border-right: 1px solid #FFF;
+}
+
+/* Navbar */
+.navbar-default .navbar-nav > li > a {
+ color: #FFF;
+}
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+ background: #D5D5D5;
+ color: #555;
+}
+.navbar-default .navbar-nav > .active > a,
+.navbar-default .navbar-nav > .active > a:hover,
+.navbar-default .navbar-nav > .active > a:focus {
+ background: #E7E7E7;
+ color: #555;
+}
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .open > a:hover,
+.navbar-default .navbar-nav > .open > a:focus {
+ background: #D5D5D5;
+ color: #FFF;
+}
+a.navbar-logo {
+ width: 210px;
+}
+a.navbar-logo:hover {
+ text-decoration: none;
+}
+img.logo {
+ height: 100%;
+ width: auto;
+}
+
+/* Vertical align modal */
+.modal {
+ text-align: center;
+ padding: 0!important;
+}
+.modal-backdrop {
+ z-index: -1;
+}
+.modal:before {
+ display: inline-block;
+ height: 100%;
+ margin-right: -4px; /* Adjusts for spacing */
+ vertical-align: middle;
+}
+.modal-dialog {
+ display: inline-block;
+ text-align: left;
+ vertical-align: middle;
+}
+
+/* Panels used for tenant dashboard */
+.panel-primary a:hover {
+ color: #000000;
+}
+.panel-gray {
+ border-color: #BFBFBF;
+}
+.panel-gray .panel-heading {
+ background-color: #BFBFBF;
+ border-color: #BFBFBF;
+ color: #ffffff;
+}
+.panel-gray a {
+ color: #BFBFBF;
+}
+.panel-gray a:hover {
+ color: #000000;
+}
+.panel-green {
+ border-color: #5cb85c;
+}
+.panel-green .panel-heading {
+ background-color: #5cb85c;
+ border-color: #5cb85c;
+ color: #ffffff;
+}
+.panel-green a {
+ color: #5cb85c;
+}
+.panel-green a:hover {
+ color: #000000;
+}
+.panel-lightgreen {
+ border-color: #00604B;
+}
+.panel-lightgreen .panel-heading {
+ background-color: #00604B;
+ border-color: #00604B;
+ color: #ffffff;
+}
+.panel-lightgreen a {
+ color: #00604B;
+}
+.panel-lightgreen a:hover {
+ color: #000000;
+}
+.panel-yellow {
+ border-color: #f0ad4e;
+}
+.panel-yellow .panel-heading {
+ background-color: #f0ad4e;
+ border-color: #f0ad4e;
+ color: #ffffff;
+}
+.panel-yellow a {
+ color: #f0ad4e;
+}
+.panel-yellow a:hover {
+ color: #000000;
+}
+
+/* Circles used for admin dashboard */
+.circle-tile {
+ margin-bottom: 15px;
+ text-align: center;
+}
+.circle-tile-heading {
+ border: 3px solid rgba(255, 255, 255, 0.3);
+ border-radius: 100%;
+ color: #FFFFFF;
+ margin: 0 auto -40px;
+ position: relative;
+ transition: all 0.3s ease-in-out 0s;
+ width: 80px;
+}
+.circle-tile-heading .fa {
+ line-height: 72px;
+}
+.circle-tile-content {
+ padding-top: 50px;
+}
+.circle-tile-number {
+ font-size: 26px;
+ font-weight: 700;
+ line-height: 1;
+ padding: 5px 0 15px;
+}
+.circle-tile-description {
+ text-transform: uppercase;
+}
+.circle-tile-footer {
+ background-color: rgba(0, 0, 0, 0.1);
+ color: rgba(255, 255, 255, 0.5);
+ display: block;
+ padding: 5px;
+ transition: all 0.3s ease-in-out 0s;
+}
+.circle-tile-footer:hover {
+ background-color: rgba(0, 0, 0, 0.2);
+ color: rgba(255, 255, 255, 0.5);
+ text-decoration: none;
+}
+.circle-tile-heading.green:hover {
+ background-color: #138F77;
+}
+.circle-tile-heading.orange:hover {
+ background-color: #DA8C10;
+}
+.circle-tile-heading.blue:hover {
+ background-color: #2473A6;
+}
+.circle-tile-heading.red:hover {
+ background-color: #CF4435;
+}
+.circle-tile-heading.purple:hover {
+ background-color: #7F3D9B;
+}
+.tile-img {
+ text-shadow: 2px 2px 3px rgba(0, 0, 0, 0.9);
+}
+.green {
+ background-color: #16A085;
+}
+.blue {
+ background-color: #2980B9;
+}
+.orange {
+ background-color: #F39C12;
+}
+.red {
+ background-color: #E74C3C;
+}
+.purple {
+ background-color: #8E44AD;
+}
+.text-green {
+ color: #16A085;
+}
+.text-blue {
+ color: #2980B9;
+}
+.text-orange {
+ color: #F39C12;
+}
+.text-red {
+ color: #E74C3C;
+}
+.text-purple {
+ color: #8E44AD;
+}
+.text-faded {
+ color: rgba(255, 255, 255, 0.7);
+}
+
+/* Configuration fieldset title */
+fieldset.title {
+ border-top: 2px solid #000;
+ border-bottom: none;
+ border-left: none;
+ border-right: none;
+ display: block;
+ text-align: left;
+}
+
+/* General settings */
+th.name {
+ width: 30%;
+}
+th.email {
+ width: 20%;
+}
+th.hostname {
+ width: 20%;
+}
+th.provider {
+ width: 10%;
+}
+th.status {
+ width: 20%;
+}
+th.options {
+ width: 20%;
+}
+
+.btn-margin {
+ margin-right: 10px;
+}
+.icon {
+ float: left;
+ margin-left: 10px;
+ margin-top: -38px;
+ position: relative;
+}
+.main-container {
+ display: table;
+ float: left;
+ margin-bottom: 100px;
+ overflow: auto;
+ width: 100%;
+}
+.swal2-popup {
+ font-size: 1.6rem !important;
+}
+.zeroPadding {
+ padding: 0 !important;
+ background: #ffffff;
+}
+
+/* Login form */
+.input-loginform {
+ height: 50px;
+ margin: 0;
+ padding: 0 40px;
+ vertical-align: middle;
+ background: #fff;
+ border: 1px solid #000;
+ font-family: 'Roboto', sans-serif;
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 50px;
+ color: #888;
+ -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px;
+ -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none;
+ -o-transition: all .3s; -moz-transition: all .3s; -webkit-transition: all .3s; -ms-transition: all .3s; transition: all .3s;
+}
+.input-loginform:focus {
+ outline: 0;
+ background: #fff;
+ border: 2px solid #000;
+ -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none;
+}
+.input-loginform:-moz-placeholder {
+ color: #888;
+}
+.input-loginform:-ms-input-placeholder {
+ color: #888;
+}
+.input-loginform::-webkit-input-placeholder {
+ color: #888;
+}
+
+.login-block {
+ background: #4c6e5e;
+ float: left;
+ width: 100%;
+ height: 100vh;
+ padding: 50px 0;
+}
+.banner-sec {
+ background: url("../images/bg.jpg") no-repeat bottom;
+ background-size: cover;
+ min-height: 500px;
+ border-radius: 0 10px 10px 0;
+ padding: 0;
+}
+.login-container {
+ background: #fff;
+ border-radius: 10px;
+ box-shadow: 15px 20px 0px rgba(0,0,0,0.1);
+}
+.login-sec {
+ padding: 50px 30px;
+ position: relative;
+}
+.login-sec h2 {
+ margin-bottom: 30px;
+ font-weight: 800;
+ font-size: 30px;
+ color: #54b948;
+}
+.login-sec h2:after {
+ content: " ";
+ width: 100px;
+ height: 5px;
+ background: #cbeac8;
+ display: block;
+ margin-top: 20px;
+ border-radius: 3px;
+ margin-left: auto;
+ margin-right: auto;
+}
+.btn-login {
+ background: #54b948;
+ color: #fff;
+ font-weight:600;
+}
\ No newline at end of file
diff --git a/css/sweetalert2.min.css b/css/sweetalert2.min.css
new file mode 100644
index 0000000..1ef4ab1
--- /dev/null
+++ b/css/sweetalert2.min.css
@@ -0,0 +1 @@
+@-webkit-keyframes swal2-show{0%{-webkit-transform:scale(.7);transform:scale(.7)}45%{-webkit-transform:scale(1.05);transform:scale(1.05)}80%{-webkit-transform:scale(.95);transform:scale(.95)}100%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes swal2-show{0%{-webkit-transform:scale(.7);transform:scale(.7)}45%{-webkit-transform:scale(1.05);transform:scale(1.05)}80%{-webkit-transform:scale(.95);transform:scale(.95)}100%{-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes swal2-hide{0%{-webkit-transform:scale(1);transform:scale(1);opacity:1}100%{-webkit-transform:scale(.5);transform:scale(.5);opacity:0}}@keyframes swal2-hide{0%{-webkit-transform:scale(1);transform:scale(1);opacity:1}100%{-webkit-transform:scale(.5);transform:scale(.5);opacity:0}}@-webkit-keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.875em;width:1.5625em}}@keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.875em;width:1.5625em}}@-webkit-keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@-webkit-keyframes swal2-rotate-success-circular-line{0%{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}5%{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}12%{-webkit-transform:rotate(-405deg);transform:rotate(-405deg)}100%{-webkit-transform:rotate(-405deg);transform:rotate(-405deg)}}@keyframes swal2-rotate-success-circular-line{0%{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}5%{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}12%{-webkit-transform:rotate(-405deg);transform:rotate(-405deg)}100%{-webkit-transform:rotate(-405deg);transform:rotate(-405deg)}}@-webkit-keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;-webkit-transform:scale(.4);transform:scale(.4);opacity:0}50%{margin-top:1.625em;-webkit-transform:scale(.4);transform:scale(.4);opacity:0}80%{margin-top:-.375em;-webkit-transform:scale(1.15);transform:scale(1.15)}100%{margin-top:0;-webkit-transform:scale(1);transform:scale(1);opacity:1}}@keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;-webkit-transform:scale(.4);transform:scale(.4);opacity:0}50%{margin-top:1.625em;-webkit-transform:scale(.4);transform:scale(.4);opacity:0}80%{margin-top:-.375em;-webkit-transform:scale(1.15);transform:scale(1.15)}100%{margin-top:0;-webkit-transform:scale(1);transform:scale(1);opacity:1}}@-webkit-keyframes swal2-animate-error-icon{0%{-webkit-transform:rotateX(100deg);transform:rotateX(100deg);opacity:0}100%{-webkit-transform:rotateX(0);transform:rotateX(0);opacity:1}}@keyframes swal2-animate-error-icon{0%{-webkit-transform:rotateX(100deg);transform:rotateX(100deg);opacity:0}100%{-webkit-transform:rotateX(0);transform:rotateX(0);opacity:1}}body.swal2-toast-shown .swal2-container{background-color:transparent}body.swal2-toast-shown .swal2-container.swal2-shown{background-color:transparent}body.swal2-toast-shown .swal2-container.swal2-top{top:0;right:auto;bottom:auto;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-top-end,body.swal2-toast-shown .swal2-container.swal2-top-right{top:0;right:0;bottom:auto;left:auto}body.swal2-toast-shown .swal2-container.swal2-top-left,body.swal2-toast-shown .swal2-container.swal2-top-start{top:0;right:auto;bottom:auto;left:0}body.swal2-toast-shown .swal2-container.swal2-center-left,body.swal2-toast-shown .swal2-container.swal2-center-start{top:50%;right:auto;bottom:auto;left:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-center{top:50%;right:auto;bottom:auto;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}body.swal2-toast-shown .swal2-container.swal2-center-end,body.swal2-toast-shown .swal2-container.swal2-center-right{top:50%;right:0;bottom:auto;left:auto;-webkit-transform:translateY(-50%);transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-left,body.swal2-toast-shown .swal2-container.swal2-bottom-start{top:auto;right:auto;bottom:0;left:0}body.swal2-toast-shown .swal2-container.swal2-bottom{top:auto;right:auto;bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-end,body.swal2-toast-shown .swal2-container.swal2-bottom-right{top:auto;right:0;bottom:0;left:auto}body.swal2-toast-column .swal2-toast{flex-direction:column;align-items:stretch}body.swal2-toast-column .swal2-toast .swal2-actions{flex:1;align-self:stretch;height:2.2em;margin-top:.3125em}body.swal2-toast-column .swal2-toast .swal2-loading{justify-content:center}body.swal2-toast-column .swal2-toast .swal2-input{height:2em;margin:.3125em auto;font-size:1em}body.swal2-toast-column .swal2-toast .swal2-validation-message{font-size:1em}.swal2-popup.swal2-toast{flex-direction:row;align-items:center;width:auto;padding:.625em;box-shadow:0 0 .625em #d9d9d9;overflow-y:hidden}.swal2-popup.swal2-toast .swal2-header{flex-direction:row}.swal2-popup.swal2-toast .swal2-title{flex-grow:1;justify-content:flex-start;margin:0 .6em;font-size:1em}.swal2-popup.swal2-toast .swal2-footer{margin:.5em 0 0;padding:.5em 0 0;font-size:.8em}.swal2-popup.swal2-toast .swal2-close{position:initial;width:.8em;height:.8em;line-height:.8}.swal2-popup.swal2-toast .swal2-content{justify-content:flex-start;font-size:1em}.swal2-popup.swal2-toast .swal2-icon{width:2em;min-width:2em;height:2em;margin:0}.swal2-popup.swal2-toast .swal2-icon-text{font-size:2em;font-weight:700;line-height:1em}.swal2-popup.swal2-toast .swal2-icon.swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line]{top:.875em;width:1.375em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:.3125em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:.3125em}.swal2-popup.swal2-toast .swal2-actions{height:auto;margin:0 .3125em}.swal2-popup.swal2-toast .swal2-styled{margin:0 .3125em;padding:.3125em .625em;font-size:1em}.swal2-popup.swal2-toast .swal2-styled:focus{box-shadow:0 0 0 .0625em #fff,0 0 0 .125em rgba(50,100,150,.4)}.swal2-popup.swal2-toast .swal2-success{border-color:#a5dc86}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line]{position:absolute;width:2em;height:2.8125em;-webkit-transform:rotate(45deg);transform:rotate(45deg);border-radius:50%}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.25em;left:-.9375em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:2em 2em;transform-origin:2em 2em;border-radius:4em 0 0 4em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.25em;left:.9375em;-webkit-transform-origin:0 2em;transform-origin:0 2em;border-radius:0 4em 4em 0}.swal2-popup.swal2-toast .swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-success .swal2-success-fix{top:0;left:.4375em;width:.4375em;height:2.6875em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line]{height:.3125em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=tip]{top:1.125em;left:.1875em;width:.75em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=long]{top:.9375em;right:.1875em;width:1.375em}.swal2-popup.swal2-toast.swal2-show{-webkit-animation:showSweetToast .5s;animation:showSweetToast .5s}.swal2-popup.swal2-toast.swal2-hide{-webkit-animation:hideSweetToast .2s forwards;animation:hideSweetToast .2s forwards}.swal2-popup.swal2-toast .swal2-animate-success-icon .swal2-success-line-tip{-webkit-animation:animate-toast-success-tip .75s;animation:animate-toast-success-tip .75s}.swal2-popup.swal2-toast .swal2-animate-success-icon .swal2-success-line-long{-webkit-animation:animate-toast-success-long .75s;animation:animate-toast-success-long .75s}@-webkit-keyframes showSweetToast{0%{-webkit-transform:translateY(-.625em) rotateZ(2deg);transform:translateY(-.625em) rotateZ(2deg);opacity:0}33%{-webkit-transform:translateY(0) rotateZ(-2deg);transform:translateY(0) rotateZ(-2deg);opacity:.5}66%{-webkit-transform:translateY(.3125em) rotateZ(2deg);transform:translateY(.3125em) rotateZ(2deg);opacity:.7}100%{-webkit-transform:translateY(0) rotateZ(0);transform:translateY(0) rotateZ(0);opacity:1}}@keyframes showSweetToast{0%{-webkit-transform:translateY(-.625em) rotateZ(2deg);transform:translateY(-.625em) rotateZ(2deg);opacity:0}33%{-webkit-transform:translateY(0) rotateZ(-2deg);transform:translateY(0) rotateZ(-2deg);opacity:.5}66%{-webkit-transform:translateY(.3125em) rotateZ(2deg);transform:translateY(.3125em) rotateZ(2deg);opacity:.7}100%{-webkit-transform:translateY(0) rotateZ(0);transform:translateY(0) rotateZ(0);opacity:1}}@-webkit-keyframes hideSweetToast{0%{opacity:1}33%{opacity:.5}100%{-webkit-transform:rotateZ(1deg);transform:rotateZ(1deg);opacity:0}}@keyframes hideSweetToast{0%{opacity:1}33%{opacity:.5}100%{-webkit-transform:rotateZ(1deg);transform:rotateZ(1deg);opacity:0}}@-webkit-keyframes animate-toast-success-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@keyframes animate-toast-success-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@-webkit-keyframes animate-toast-success-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}@keyframes animate-toast-success-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow:hidden}body.swal2-height-auto{height:auto!important}body.swal2-no-backdrop .swal2-shown{top:auto;right:auto;bottom:auto;left:auto;background-color:transparent}body.swal2-no-backdrop .swal2-shown>.swal2-modal{box-shadow:0 0 10px rgba(0,0,0,.4)}body.swal2-no-backdrop .swal2-shown.swal2-top{top:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-top-left,body.swal2-no-backdrop .swal2-shown.swal2-top-start{top:0;left:0}body.swal2-no-backdrop .swal2-shown.swal2-top-end,body.swal2-no-backdrop .swal2-shown.swal2-top-right{top:0;right:0}body.swal2-no-backdrop .swal2-shown.swal2-center{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}body.swal2-no-backdrop .swal2-shown.swal2-center-left,body.swal2-no-backdrop .swal2-shown.swal2-center-start{top:50%;left:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-center-end,body.swal2-no-backdrop .swal2-shown.swal2-center-right{top:50%;right:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-bottom{bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-bottom-left,body.swal2-no-backdrop .swal2-shown.swal2-bottom-start{bottom:0;left:0}body.swal2-no-backdrop .swal2-shown.swal2-bottom-end,body.swal2-no-backdrop .swal2-shown.swal2-bottom-right{right:0;bottom:0}.swal2-container{display:flex;position:fixed;top:0;right:0;bottom:0;left:0;flex-direction:row;align-items:center;justify-content:center;padding:10px;background-color:transparent;z-index:1060;overflow-x:hidden;-webkit-overflow-scrolling:touch}.swal2-container.swal2-top{align-items:flex-start}.swal2-container.swal2-top-left,.swal2-container.swal2-top-start{align-items:flex-start;justify-content:flex-start}.swal2-container.swal2-top-end,.swal2-container.swal2-top-right{align-items:flex-start;justify-content:flex-end}.swal2-container.swal2-center{align-items:center}.swal2-container.swal2-center-left,.swal2-container.swal2-center-start{align-items:center;justify-content:flex-start}.swal2-container.swal2-center-end,.swal2-container.swal2-center-right{align-items:center;justify-content:flex-end}.swal2-container.swal2-bottom{align-items:flex-end}.swal2-container.swal2-bottom-left,.swal2-container.swal2-bottom-start{align-items:flex-end;justify-content:flex-start}.swal2-container.swal2-bottom-end,.swal2-container.swal2-bottom-right{align-items:flex-end;justify-content:flex-end}.swal2-container.swal2-bottom-end>:first-child,.swal2-container.swal2-bottom-left>:first-child,.swal2-container.swal2-bottom-right>:first-child,.swal2-container.swal2-bottom-start>:first-child,.swal2-container.swal2-bottom>:first-child{margin-top:auto}.swal2-container.swal2-grow-fullscreen>.swal2-modal{display:flex!important;flex:1;align-self:stretch;justify-content:center}.swal2-container.swal2-grow-row>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container.swal2-grow-column{flex:1;flex-direction:column}.swal2-container.swal2-grow-column.swal2-bottom,.swal2-container.swal2-grow-column.swal2-center,.swal2-container.swal2-grow-column.swal2-top{align-items:center}.swal2-container.swal2-grow-column.swal2-bottom-left,.swal2-container.swal2-grow-column.swal2-bottom-start,.swal2-container.swal2-grow-column.swal2-center-left,.swal2-container.swal2-grow-column.swal2-center-start,.swal2-container.swal2-grow-column.swal2-top-left,.swal2-container.swal2-grow-column.swal2-top-start{align-items:flex-start}.swal2-container.swal2-grow-column.swal2-bottom-end,.swal2-container.swal2-grow-column.swal2-bottom-right,.swal2-container.swal2-grow-column.swal2-center-end,.swal2-container.swal2-grow-column.swal2-center-right,.swal2-container.swal2-grow-column.swal2-top-end,.swal2-container.swal2-grow-column.swal2-top-right{align-items:flex-end}.swal2-container.swal2-grow-column>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container:not(.swal2-top):not(.swal2-top-start):not(.swal2-top-end):not(.swal2-top-left):not(.swal2-top-right):not(.swal2-center-start):not(.swal2-center-end):not(.swal2-center-left):not(.swal2-center-right):not(.swal2-bottom):not(.swal2-bottom-start):not(.swal2-bottom-end):not(.swal2-bottom-left):not(.swal2-bottom-right):not(.swal2-grow-fullscreen)>.swal2-modal{margin:auto}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-container .swal2-modal{margin:0!important}}.swal2-container.swal2-fade{transition:background-color .1s}.swal2-container.swal2-shown{background-color:rgba(0,0,0,.4)}.swal2-popup{display:none;position:relative;flex-direction:column;justify-content:center;width:32em;max-width:100%;padding:1.25em;border-radius:.3125em;background:#fff;font-family:inherit;font-size:1rem;box-sizing:border-box}.swal2-popup:focus{outline:0}.swal2-popup.swal2-loading{overflow-y:hidden}.swal2-popup .swal2-header{display:flex;flex-direction:column;align-items:center}.swal2-popup .swal2-title{display:block;position:relative;max-width:100%;margin:0 0 .4em;padding:0;color:#595959;font-size:1.875em;font-weight:600;text-align:center;text-transform:none;word-wrap:break-word}.swal2-popup .swal2-actions{flex-wrap:wrap;align-items:center;justify-content:center;margin:1.25em auto 0;z-index:1}.swal2-popup .swal2-actions:not(.swal2-loading) .swal2-styled[disabled]{opacity:.4}.swal2-popup .swal2-actions:not(.swal2-loading) .swal2-styled:hover{background-image:linear-gradient(rgba(0,0,0,.1),rgba(0,0,0,.1))}.swal2-popup .swal2-actions:not(.swal2-loading) .swal2-styled:active{background-image:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,.2))}.swal2-popup .swal2-actions.swal2-loading .swal2-styled.swal2-confirm{width:2.5em;height:2.5em;margin:.46875em;padding:0;border:.25em solid transparent;border-radius:100%;border-color:transparent;background-color:transparent!important;color:transparent;cursor:default;box-sizing:border-box;-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.swal2-popup .swal2-actions.swal2-loading .swal2-styled.swal2-cancel{margin-right:30px;margin-left:30px}.swal2-popup .swal2-actions.swal2-loading :not(.swal2-styled).swal2-confirm::after{display:inline-block;width:15px;height:15px;margin-left:5px;border:3px solid #999;border-radius:50%;border-right-color:transparent;box-shadow:1px 1px 1px #fff;content:'';-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal}.swal2-popup .swal2-styled{margin:.3125em;padding:.625em 2em;font-weight:500;box-shadow:none}.swal2-popup .swal2-styled:not([disabled]){cursor:pointer}.swal2-popup .swal2-styled.swal2-confirm{border:0;border-radius:.25em;background:initial;background-color:#3085d6;color:#fff;font-size:1.0625em}.swal2-popup .swal2-styled.swal2-cancel{border:0;border-radius:.25em;background:initial;background-color:#aaa;color:#fff;font-size:1.0625em}.swal2-popup .swal2-styled:focus{outline:0;box-shadow:0 0 0 2px #fff,0 0 0 4px rgba(50,100,150,.4)}.swal2-popup .swal2-styled::-moz-focus-inner{border:0}.swal2-popup .swal2-footer{justify-content:center;margin:1.25em 0 0;padding:1em 0 0;border-top:1px solid #eee;color:#545454;font-size:1em}.swal2-popup .swal2-image{max-width:100%;margin:1.25em auto}.swal2-popup .swal2-close{position:absolute;top:0;right:0;justify-content:center;width:1.2em;height:1.2em;padding:0;transition:color .1s ease-out;border:none;border-radius:0;outline:initial;background:0 0;color:#ccc;font-family:serif;font-size:2.5em;line-height:1.2;cursor:pointer;overflow:hidden}.swal2-popup .swal2-close:hover{-webkit-transform:none;transform:none;color:#f27474}.swal2-popup>.swal2-checkbox,.swal2-popup>.swal2-file,.swal2-popup>.swal2-input,.swal2-popup>.swal2-radio,.swal2-popup>.swal2-select,.swal2-popup>.swal2-textarea{display:none}.swal2-popup .swal2-content{justify-content:center;margin:0;padding:0;color:#545454;font-size:1.125em;font-weight:300;line-height:normal;z-index:1;word-wrap:break-word}.swal2-popup #swal2-content{text-align:center}.swal2-popup .swal2-checkbox,.swal2-popup .swal2-file,.swal2-popup .swal2-input,.swal2-popup .swal2-radio,.swal2-popup .swal2-select,.swal2-popup .swal2-textarea{margin:1em auto}.swal2-popup .swal2-file,.swal2-popup .swal2-input,.swal2-popup .swal2-textarea{width:100%;transition:border-color .3s,box-shadow .3s;border:1px solid #d9d9d9;border-radius:.1875em;background:inherit;font-size:1.125em;box-shadow:inset 0 1px 1px rgba(0,0,0,.06);box-sizing:border-box}.swal2-popup .swal2-file.swal2-inputerror,.swal2-popup .swal2-input.swal2-inputerror,.swal2-popup .swal2-textarea.swal2-inputerror{border-color:#f27474!important;box-shadow:0 0 2px #f27474!important}.swal2-popup .swal2-file:focus,.swal2-popup .swal2-input:focus,.swal2-popup .swal2-textarea:focus{border:1px solid #b4dbed;outline:0;box-shadow:0 0 3px #c4e6f5}.swal2-popup .swal2-file::-webkit-input-placeholder,.swal2-popup .swal2-input::-webkit-input-placeholder,.swal2-popup .swal2-textarea::-webkit-input-placeholder{color:#ccc}.swal2-popup .swal2-file:-ms-input-placeholder,.swal2-popup .swal2-input:-ms-input-placeholder,.swal2-popup .swal2-textarea:-ms-input-placeholder{color:#ccc}.swal2-popup .swal2-file::-ms-input-placeholder,.swal2-popup .swal2-input::-ms-input-placeholder,.swal2-popup .swal2-textarea::-ms-input-placeholder{color:#ccc}.swal2-popup .swal2-file::placeholder,.swal2-popup .swal2-input::placeholder,.swal2-popup .swal2-textarea::placeholder{color:#ccc}.swal2-popup .swal2-range{margin:1em auto;background:inherit}.swal2-popup .swal2-range input{width:80%}.swal2-popup .swal2-range output{width:20%;font-weight:600;text-align:center}.swal2-popup .swal2-range input,.swal2-popup .swal2-range output{height:2.625em;padding:0;font-size:1.125em;line-height:2.625em}.swal2-popup .swal2-input{height:2.625em;padding:0 .75em}.swal2-popup .swal2-input[type=number]{max-width:10em}.swal2-popup .swal2-file{background:inherit;font-size:1.125em}.swal2-popup .swal2-textarea{height:6.75em;padding:.75em}.swal2-popup .swal2-select{min-width:50%;max-width:100%;padding:.375em .625em;background:inherit;color:#545454;font-size:1.125em}.swal2-popup .swal2-checkbox,.swal2-popup .swal2-radio{align-items:center;justify-content:center;background:inherit}.swal2-popup .swal2-checkbox label,.swal2-popup .swal2-radio label{margin:0 .6em;font-size:1.125em}.swal2-popup .swal2-checkbox input,.swal2-popup .swal2-radio input{margin:0 .4em}.swal2-popup .swal2-validation-message{display:none;align-items:center;justify-content:center;padding:.625em;background:#f0f0f0;color:#666;font-size:1em;font-weight:300;overflow:hidden}.swal2-popup .swal2-validation-message::before{display:inline-block;width:1.5em;min-width:1.5em;height:1.5em;margin:0 .625em;border-radius:50%;background-color:#f27474;color:#fff;font-weight:600;line-height:1.5em;text-align:center;content:'!';zoom:normal}@supports (-ms-accelerator:true){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@-moz-document url-prefix(){.swal2-close:focus{outline:2px solid rgba(50,100,150,.4)}}.swal2-icon{position:relative;justify-content:center;width:5em;height:5em;margin:1.25em auto 1.875em;border:.25em solid transparent;border-radius:50%;line-height:5em;cursor:default;box-sizing:content-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;zoom:normal}.swal2-icon-text{font-size:3.75em}.swal2-icon.swal2-error{border-color:#f27474}.swal2-icon.swal2-error .swal2-x-mark{position:relative;flex-grow:1}.swal2-icon.swal2-error [class^=swal2-x-mark-line]{display:block;position:absolute;top:2.3125em;width:2.9375em;height:.3125em;border-radius:.125em;background-color:#f27474}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:1.0625em;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:1em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.swal2-icon.swal2-warning{border-color:#facea8;color:#f8bb86}.swal2-icon.swal2-info{border-color:#9de0f6;color:#3fc3ee}.swal2-icon.swal2-question{border-color:#c9dae1;color:#87adbd}.swal2-icon.swal2-success{border-color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-circular-line]{position:absolute;width:3.75em;height:7.5em;-webkit-transform:rotate(45deg);transform:rotate(45deg);border-radius:50%}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.4375em;left:-2.0635em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:3.75em 3.75em;transform-origin:3.75em 3.75em;border-radius:7.5em 0 0 7.5em}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.6875em;left:1.875em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:0 3.75em;transform-origin:0 3.75em;border-radius:0 7.5em 7.5em 0}.swal2-icon.swal2-success .swal2-success-ring{position:absolute;top:-.25em;left:-.25em;width:100%;height:100%;border:.25em solid rgba(165,220,134,.3);border-radius:50%;z-index:2;box-sizing:content-box}.swal2-icon.swal2-success .swal2-success-fix{position:absolute;top:.5em;left:1.625em;width:.4375em;height:5.625em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);z-index:1}.swal2-icon.swal2-success [class^=swal2-success-line]{display:block;position:absolute;height:.3125em;border-radius:.125em;background-color:#a5dc86;z-index:2}.swal2-icon.swal2-success [class^=swal2-success-line][class$=tip]{top:2.875em;left:.875em;width:1.5625em;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.swal2-icon.swal2-success [class^=swal2-success-line][class$=long]{top:2.375em;right:.5em;width:2.9375em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.swal2-progress-steps{align-items:center;margin:0 0 1.25em;padding:0;background:inherit;font-weight:600}.swal2-progress-steps li{display:inline-block;position:relative}.swal2-progress-steps .swal2-progress-step{width:2em;height:2em;border-radius:2em;background:#3085d6;color:#fff;line-height:2em;text-align:center;z-index:20}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step{background:#3085d6}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step{background:#add8e6;color:#fff}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step-line{background:#add8e6}.swal2-progress-steps .swal2-progress-step-line{width:2.5em;height:.4em;margin:0 -1px;background:#3085d6;z-index:10}[class^=swal2]{-webkit-tap-highlight-color:transparent}.swal2-show{-webkit-animation:swal2-show .3s;animation:swal2-show .3s}.swal2-show.swal2-noanimation{-webkit-animation:none;animation:none}.swal2-hide{-webkit-animation:swal2-hide .15s forwards;animation:swal2-hide .15s forwards}.swal2-hide.swal2-noanimation{-webkit-animation:none;animation:none}.swal2-rtl .swal2-close{right:auto;left:0}.swal2-animate-success-icon .swal2-success-line-tip{-webkit-animation:swal2-animate-success-line-tip .75s;animation:swal2-animate-success-line-tip .75s}.swal2-animate-success-icon .swal2-success-line-long{-webkit-animation:swal2-animate-success-line-long .75s;animation:swal2-animate-success-line-long .75s}.swal2-animate-success-icon .swal2-success-circular-line-right{-webkit-animation:swal2-rotate-success-circular-line 4.25s ease-in;animation:swal2-rotate-success-circular-line 4.25s ease-in}.swal2-animate-error-icon{-webkit-animation:swal2-animate-error-icon .5s;animation:swal2-animate-error-icon .5s}.swal2-animate-error-icon .swal2-x-mark{-webkit-animation:swal2-animate-error-x-mark .5s;animation:swal2-animate-error-x-mark .5s}@-webkit-keyframes swal2-rotate-loading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes swal2-rotate-loading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@media print{body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow-y:scroll!important}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown)>[aria-hidden=true]{display:none}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) .swal2-container{position:initial!important}}
\ No newline at end of file
diff --git a/download.php b/download.php
new file mode 100644
index 0000000..b32e628
--- /dev/null
+++ b/download.php
@@ -0,0 +1,122 @@
+ 'text/plain',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'php' => 'text/html',
+ 'css' => 'text/css',
+ 'js' => 'application/javascript',
+ 'json' => 'application/json',
+ 'xml' => 'application/xml',
+ 'swf' => 'application/x-shockwave-flash',
+ 'flv' => 'video/x-flv',
+
+ /* Images */
+ 'png' => 'image/png',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'gif' => 'image/gif',
+ 'bmp' => 'image/bmp',
+ 'ico' => 'image/vnd.microsoft.icon',
+ 'tiff' => 'image/tiff',
+ 'tif' => 'image/tiff',
+ 'svg' => 'image/svg+xml',
+ 'svgz' => 'image/svg+xml',
+
+ /* Archives */
+ 'zip' => 'application/zip',
+ 'rar' => 'application/x-rar-compressed',
+ 'exe' => 'application/x-msdownload',
+ 'msi' => 'application/x-msdownload',
+ 'cab' => 'application/vnd.ms-cab-compressed',
+
+ /* Audio and video */
+ 'mp3' => 'audio/mpeg',
+ 'qt' => 'video/quicktime',
+ 'mov' => 'video/quicktime',
+
+ /* Adobe */
+ 'pdf' => 'application/pdf',
+ 'psd' => 'image/vnd.adobe.photoshop',
+ 'ai' => 'application/postscript',
+ 'eps' => 'application/postscript',
+ 'ps' => 'application/postscript',
+
+ /* Microsoft Office */
+ 'doc' => 'application/msword',
+ 'rtf' => 'application/rtf',
+ 'xls' => 'application/vnd.ms-excel',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'docx' => 'application/msword',
+ 'xlsx' => 'application/vnd.ms-excel',
+ 'pptx' => 'application/vnd.ms-powerpoint',
+
+ /* Open Office */
+ 'odt' => 'application/vnd.oasis.opendocument.text',
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+ );
+
+ if (isset($mimetypes[$idx])) {
+ return $mimetypes[$idx];
+ } else {
+ return 'application/octet-stream';
+ }
+}
+if (!isset($_SESSION['token'])) { /* Additional check as access is only allowed with a token */
+ header('Location: index.php');
+} else {
+ if (isset($_POST['ext'])) { $ext = $_POST['ext']; }
+ if (isset($_POST['name'])) { $name = $_POST['name']; }
+ if (isset($_POST['file'])) {
+ $file = str_replace('..', '', isset($_POST['file'])?$_POST['file']:'');
+
+ $filename = basename($name);
+
+ /* Check if we have a MSG/PST/ZIP file */
+ if ($ext != 'plain')
+ $filename .= '.' . $ext;
+
+ if(!is_file($file))
+ exit();
+
+ header('Pragma: public');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+
+ if ($ext == "msg" || $ext == "pst") {
+ header('Content-Encoding: UTF-8');
+ header('Content-Type: application/vnd.ms-outlook;charset=UTF-8');
+ header('Content-Type: application/octet-stream');
+ header('Content-Transfer-Encoding: binary ');
+ header('Content-Length: ' . filesize($file));
+ header('Content-Disposition: attachment; filename="' . $filename . '"');
+ } else {
+ header('Last-Modified: ' . gmdate ('D, d M Y H:i:s', filemtime ($file)).' GMT');
+ header('Cache-Control: private', false);
+ if ($ext == "plain") {
+ $mime = get_mime_type($filename);
+ header('Content-Type: ' . $mime);
+ } else {
+ header('Content-Type: application/zip');
+ }
+ header('Content-Transfer-Encoding: binary');
+ header('Content-Length: ' . filesize($file));
+ header('Content-Disposition: attachment; filename=" ' . $filename . '"');
+ header('Connection: close');
+ }
+
+ readfile($file);
+ unlink($file);
+ } else {
+ header('Location: index.php');
+ }
+}
+?>
\ No newline at end of file
diff --git a/exchange.php b/exchange.php
new file mode 100644
index 0000000..d1f8e97
--- /dev/null
+++ b/exchange.php
@@ -0,0 +1,1028 @@
+setToken($_SESSION['token']);
+ $veeam->refreshToken($_SESSION['refreshtoken']);
+}
+?>
+
+
+
+
+
+ Project Martini
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Exchange
+
+ getOrganizationByID($oid);
+
+
+ $users = $veeam->getLicensedUsers($oid);
+ $repo = $veeam->getOrganizationRepository($oid);
+ $usersarray = array();
+
+ for ($i = 0; $i < count($users['results']); $i++) {
+ array_push($usersarray, array(
+ 'id' => $users['results'][$i]['id'],
+ 'isBackedUp' => $users['results'][$i]['isBackedUp'],
+ 'lastBackupDate' => $users['results'][$i]['lastBackupDate']
+ ));
+ }
+
+ if (count($users['results']) != '0') { /* Gather the backed up users from the repositories related to the organization */
+ $repousersarray = array(); /* Array used to sort the users in case of double data on the repositories */
+
+ for ($i = 0; $i < count($repo); $i++) {
+ $id = explode('/', $repo[$i]['_links']['backupRepository']['href']); /* Get the organization ID */
+ $repoid = end($id);
+
+ for ($j = 0; $j < count($users['results']); $j++) {
+ $combinedid = $users['results'][$j]['backedUpOrganizationId'] . $users['results'][$j]['id'];
+ $userdata = $veeam->getUserData($repoid, $combinedid);
+
+ /* Only store data when the Mailbox or Archive data is backed up */
+ if (!is_null($userdata) && ($userdata['isMailboxBackedUp'] || $userdata['isArchiveBackedUp'])) {
+ array_push($repousersarray, array(
+ 'id' => $userdata['accountId'],
+ 'email' => $userdata['email'],
+ 'name' => $userdata['displayName'],
+ 'isMailboxBackedUp' => $userdata['isMailboxBackedUp'],
+ 'isArchiveBackedUp' => $userdata['isArchiveBackedUp']
+ ));
+ }
+ }
+ }
+
+ $usersort = array_values(array_column($repousersarray , null, 'name')); /* Sort the array and make sure every value is unique */
+ }
+
+ if (count($usersort) != '0') {
+ ?>
+
+
+ Explore last backup
+
+
+
+ Start Restore
+
+
+
+
The following is an overview on all backed up accounts and their objects within the organization.
+
+
+
+ Account
+ Objects in backup
+ Last backup
+
+
+
+ ';
+ echo '' . $usersort[$i]['name'] . ' (' . $usersort[$i]['email'] . ') ';
+ echo '';
+ if ($usersort[$i]['isMailboxBackedUp']) {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+ if ($usersort[$i]['isArchiveBackedUp']) {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+ echo ' ';
+ echo '' . date('d/m/Y H:i T', strtotime($usersarray[$licinfo]['lastBackupDate'])) . ' ';
+ echo '';
+ }
+ ?>
+
+
+ No users found for this organization.';
+ }
+ } else { /* No organization has been selected */
+ echo '
Select an organization to start a restore session.
';
+ }
+ } else { /* Restore session is running */
+ if (isset($uid) && !empty($uid)) {
+ $owner = $veeam->getMailboxID($rid, $uid);
+ $folders = $veeam->getMailboxFolders($uid, $rid);
+ $items = $veeam->getMailboxItems($uid, $rid);
+
+ if (count($items['results']) != '0') {
+ ?>
+
+
+ Restore selected
+
+
+
+
+
+ -- Filter by folder --
+
+
+
+
+
+
+
+
+
+
+ No items available in this mailbox.';
+ }
+ } else { /* List all mailboxes */
+ ?>
+
+
+
+ Name
+ E-mail
+ Options
+
+
+
+ $users['results'][$i]['name'], 'email'=> $users['results'][$i]['email'], 'id' => $users['results'][$i]['id']));
+ }
+
+ uasort($mailboxes, function($a, $b) {
+ return strcasecmp($a['name'], $b['name']);
+ });
+
+ foreach ($mailboxes as $key => $value) {
+ ?>
+
+
+
+
+
+ Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/images/bg.jpg b/images/bg.jpg
new file mode 100644
index 0000000..1428116
Binary files /dev/null and b/images/bg.jpg differ
diff --git a/images/favicon.ico b/images/favicon.ico
new file mode 100644
index 0000000..9565dad
Binary files /dev/null and b/images/favicon.ico differ
diff --git a/images/logo.svg b/images/logo.svg
new file mode 100644
index 0000000..7344049
--- /dev/null
+++ b/images/logo.svg
@@ -0,0 +1 @@
+Veeam_logo_2017_white
\ No newline at end of file
diff --git a/includes/activity.php b/includes/activity.php
new file mode 100644
index 0000000..477a64d
--- /dev/null
+++ b/includes/activity.php
@@ -0,0 +1,213 @@
+setToken($_SESSION['token']);
+ $veeam->refreshToken($_SESSION['refreshtoken']);
+
+ $sessions = $veeam->getRestoreSessions();
+ $time = array();
+
+ for ($i = 0; $i < count($sessions['results']); $i++) {
+ array_push($time, array('name'=> $sessions['results'][$i]['name'], 'organization' => $sessions['results'][$i]['organization'], 'result' => $sessions['results'][$i]['result'], 'creationTime' => $sessions['results'][$i]['creationTime'], 'endTime' => $sessions['results'][$i]['endTime'], 'id' => $sessions['results'][$i]['id']));
+ }
+
+ usort($time, function($a, $b) { /* Sort the default list by start time (last one first) */
+ $ad = new DateTime($a['endTime']);
+ $bd = new DateTime($b['endTime']);
+
+ if ($ad == $bd)
+ return 0;
+
+ return $ad > $bd ? -1 : 1;
+ });
+?>
+
+
Activity log
+
+
+
+
+
+ Name
+ Organization
+ Status
+ Start Time
+ End Time
+ Session Log
+
+
+
+ $value) {
+ ?>
+
+
+
+
+ ' . $value['result'] . '';
+ } else {
+ echo '' . $value['result'] . ' ';
+ }
+ ?>
+
+
+
+ View
+
+
+
+
+No restore sessions found.';
+}
+
+/* If we have 30 items from the first request, show message to load additional items */
+if (count($sessions['results']) == '30') {
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+ Action
+ Duration
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/addinstance.php b/includes/addinstance.php
new file mode 100644
index 0000000..519a97b
--- /dev/null
+++ b/includes/addinstance.php
@@ -0,0 +1,246 @@
+
+
+
Create a new instance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/addtenant.php b/includes/addtenant.php
new file mode 100644
index 0000000..d1ee2ef
--- /dev/null
+++ b/includes/addtenant.php
@@ -0,0 +1,45 @@
+
+
Create a new tenant
+
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/configuration.php b/includes/configuration.php
new file mode 100644
index 0000000..ec7291c
--- /dev/null
+++ b/includes/configuration.php
@@ -0,0 +1,205 @@
+
+
+
Global configuration
+
+
+
+ Configuration for AWS:
+
+
+
+
+
+
Select an AWS region to change settings per region.
+
+
+
+
+
+
+ Configuration for Azure:
+
+
Select a location to create or update your Azure settings.
+
+
+
\ No newline at end of file
diff --git a/includes/dashboard.php b/includes/dashboard.php
new file mode 100644
index 0000000..daaf710
--- /dev/null
+++ b/includes/dashboard.php
@@ -0,0 +1,215 @@
+
+
+
Dashboard
+
+ setToken($_SESSION['token']);
+ $veeam->refreshToken($_SESSION['refreshtoken']);
+
+ /* Create dashboard stats */
+ $org = $veeam->getOrganizations();
+ $jobs = $veeam->getJobs();
+ $proxies = $veeam->getProxies();
+ $repos = $veeam->getBackupRepositories();
+ $licensetotal = 0;
+ $newlicensetotal = 0;
+
+ for ($i = 0; $i < count($org); $i++) {
+ $license = $veeam->getLicenseInfo($org[$i]['id']);
+ $licensetotal += $license['licensedUsers'];
+ $newlicensetotal += $license['newUsers'];
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
licenses used extra licenses
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/instances.php b/includes/instances.php
new file mode 100644
index 0000000..343ddae
--- /dev/null
+++ b/includes/instances.php
@@ -0,0 +1,417 @@
+
+
+
Instance overview
+
+Instances for: ' . $tenants[$i]->name . '';
+ echo '
';
+
+ $total = getTenantInstanceCounter($tenants[$i]->id);
+
+ if ($total != '0') {
+ try {
+ $instances = getTenantAllInstances($tenants[$i]->id);
+ } catch (Exception $e) {
+ }
+
+ if (isset($instances) && !is_null($instances)) {
+ ?>
+
+
+
+ Name
+ Hostname
+ Provider
+ Deployment status
+ Options
+
+
+
+
+
+
+
+
+
+
+ ';
+ } elseif ($instances[$x]['type'] == 'azure') {
+ echo ' ';
+ } elseif ($instances[$x]['type'] == 'gcp') {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+ ?>
+
+
+ Scheduled for deployment';
+ } elseif ($instances[$x]['status'] == 1) {
+ echo 'Deployed ';
+ } elseif ($instances[$x]['status'] == 2) {
+ echo 'Deployment in progress ';
+ } elseif ($instances[$x]['status'] == -1) {
+ echo 'Unmanaged ';
+ } elseif ($instances[$x]['status'] == -100) {
+ echo 'Marked for removal ';
+ } else {
+ echo 'N/A';
+ }
+ ?>
+
+
+
+ Connect
+
+ Edit
+ Delete
+
+
+
+
+
+
+
+
';
+ }
+ }
+
+ if (count($orphaned) != 0) {
+ echo '
';
+ echo '
Orphaned instances found: ';
+ ?>
+
+
+
+
+ Name
+ Hostname
+ Provider
+ Deployment status
+ Options
+
+
+
+
+
+
+
+
+
+
+ ';
+ } elseif ($orphaned[$x]['type'] == 'azure') {
+ echo ' ';
+ } elseif ($orphaned[$x]['type'] == 'gcp') {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+ ?>
+
+
+ Scheduled';
+ } elseif ($orphaned[$x]['status'] == 1) {
+ echo 'Deployed ';
+ } elseif ($orphaned[$x]['status'] == 2) {
+ echo 'Deployment in progress ';
+ } elseif ($orphaned[$x]['status'] == -1) {
+ echo 'Unmanaged ';
+ } elseif ($orphaned[$x]['status'] == -100) {
+ echo 'Marked for removal ';
+ } else {
+ echo 'N/A';
+ }
+ ?>
+
+
+
+ Connect
+
+ Edit
+ Delete
+
+
+
+
+
+
+
+
+
+
+ Name
+ Hostname
+ Provider
+ Deployment status
+ Options
+
+
+
+
+
+
+
+
+
+
+ ';
+ } elseif ($instances[$x]['type'] == 'azure') {
+ echo ' ';
+ } elseif ($instances[$x]['type'] == 'gcp') {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+ ?>
+
+
+ Scheduled for deployment';
+ } elseif ($instances[$x]['status'] == 1) {
+ echo 'Deployed ';
+ } elseif ($instances[$x]['status'] == 2) {
+ echo 'Deployment in progress ';
+ } elseif ($instances[$x]['status'] == -1) {
+ echo 'Unmanaged ';
+ } elseif ($instances[$x]['status'] == -100) {
+ echo 'Marked for removal ';
+ } else {
+ echo 'N/A';
+ }
+ ?>
+
+
+
+ Connect
+
+ Edit
+ Delete
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/jobs.php b/includes/jobs.php
new file mode 100644
index 0000000..571d5ae
--- /dev/null
+++ b/includes/jobs.php
@@ -0,0 +1,283 @@
+setToken($_SESSION['token']);
+ $veeam->refreshToken($_SESSION['refreshtoken']);
+
+ $jobs = $veeam->getJobs();
+ $org = $veeam->getOrganizations();
+ $proxies = $veeam->getProxies();
+?>
+
+
Jobs
+
+
+
+
+
+
+ Job Name
+ Organization
+ Status
+ Next Run
+ Schedule
+ Restore Points
+ Options
+
+
+
+ ';
+ echo '' . $jobs[$i]['name'] . ' ';
+
+ $id = explode('/', $jobs[$i]['_links']['organization']['href']); // Get the organization ID
+
+ for ($j = 0; $j < count($org); $j++) {
+ if ($org[$j]['id'] === end($id)) {
+ echo '' . $org[$j]['name'] . ' ';
+ }
+ }
+
+ echo '' . (isset($jobs[$i]['lastRun']) ? $jobs[$i]['lastStatus'] . ' (' . date('d/m/Y H:i T', strtotime($jobs[$i]['lastRun'])) . ')' : $jobs[$i]['lastStatus']) . ' ';
+ echo '' . (isset($jobs[$i]['nextRun']) ? date('d/m/Y H:i T', strtotime($jobs[$i]['nextRun'])) : 'Not scheduled') . ' ';
+ echo 'View ';
+ echo 'View ';
+ echo '';
+
+ if ($jobs[$i]['isEnabled'] != 'true') {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+
+ echo ' ';
+ echo ' ';
+ echo '';
+
+ echo ''; /* Start of table for job schedule */
+ echo '';
+ echo '';
+ ?>
+
+
+
+ Schedule Policy
+ Periodically Run
+ Daily Type
+ Run At
+ Retry Enabled
+ Retry Number
+ Retry Wait Interval
+
+
+
+
+ ' . $jobs[$i]['schedulePolicy']['type'] . '';
+ echo '' . (isset($jobs[$i]['schedulePolicy']['periodicallyEvery']) ? $jobs[$i]['schedulePolicy']['periodicallyEvery'] : 'N/A') . ' ';
+ echo '' . (isset($jobs[$i]['schedulePolicy']['dailyType']) ? $jobs[$i]['schedulePolicy']['dailyType'] : 'N/A') . ' ';
+ echo '' . (isset($jobs[$i]['schedulePolicy']['dailyTime']) ? $jobs[$i]['schedulePolicy']['dailyTime'] : 'N/A') . ' ';
+ echo '';
+ if ($jobs[$i]['schedulePolicy']['retryEnabled'] == 'true') { echo 'Yes '; } else { echo 'No '; }
+ echo ' ';
+ echo '' . (isset($jobs[$i]['schedulePolicy']['retryNumber']) ? $jobs[$i]['schedulePolicy']['retryNumber'] : 'N/A') . ' ';
+ echo '' . (isset($jobs[$i]['schedulePolicy']['retryWaitInterval']) ? $jobs[$i]['schedulePolicy']['retryWaitInterval'] . 'm' : 'N/A') . ' ';
+ ?>
+
+
+
+ ';
+ echo '
';
+ echo ' ';
+
+ echo ''; /* Start of table for job restore points */
+ echo '';
+ ?>
+
+
+
+ Point In Time
+ Status
+ Bottleneck
+ Transferred
+ Session Log
+
+
+
+ getJobSession($jobs[$i]['id']);
+
+ for ($j = 0; $j < count($jobsession); $j++) {
+ echo '';
+ echo '' . (isset($jobsession[$j]['endTime']) ? date('d/m/Y H:i T', strtotime($jobsession[$j]['endTime'])) : 'N/A') . ' ';
+ if (strcmp($jobsession[$j]['status'], 'Success') === 0) {
+ echo '' . $jobsession[$j]['status'] . ' ';
+ } else if (strcmp($jobsession[$j]['status'], 'Warning') === 0) {
+ echo '' . $jobsession[$j]['status'] . ' ';
+ } else {
+ echo '' . $jobsession[$j]['status'] . ' ';
+ }
+ echo '' . $jobsession[$j]['statistics']['bottleneck'] . ' ';
+ echo '' . $jobsession[$j]['statistics']['processedObjects'] . ' items processed ';
+ echo 'View ';
+ echo ' ';
+ }
+ ?>
+
+
+ ';
+ echo '
';
+ echo ' ';
+ }
+ ?>
+
+
+ No backup jobs have been configured.';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+ Action
+ Duration
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/licensing.php b/includes/licensing.php
new file mode 100644
index 0000000..39c61b9
--- /dev/null
+++ b/includes/licensing.php
@@ -0,0 +1,162 @@
+setToken($_SESSION['token']);
+ $veeam->refreshToken($_SESSION['refreshtoken']);
+
+ $org = $veeam->getOrganizations();
+?>
+
+
Licensing
+
+
+
+
+
+ Organization
+ Licenses used
+ Licenses exceeded
+ Licensed users
+
+
+
+ getLicenseInfo($org[$i]['id']);
+
+ $users = $veeam->getLicensedUsers($org[$i]['id']);
+ $repo = $veeam->getOrganizationRepository($org[$i]['id']);
+ $usersarray = array();
+
+ for ($x = 0; $x < count($users['results']); $x++) {
+ array_push($usersarray, array(
+ 'email' => $users['results'][$x]['name'],
+ 'isBackedUp' => $users['results'][$x]['isBackedUp'],
+ 'lastBackupDate' => $users['results'][$x]['lastBackupDate'],
+ 'licenseState' => $users['results'][$x]['licenseState']
+ ));
+ }
+
+ if (count($users['results']) != '0') { /* Gather the backed up users from the repositories related to the organization */
+ $repousersarray = array(); /* Array used to sort the users in case of double data on the repositories */
+
+ for ($j = 0; $j < count($repo); $j++) {
+ $id = explode('/', $repo[$j]['_links']['backupRepository']['href']); /* Get the organization ID */
+ $repoid = end($id);
+
+ for ($k = 0; $k < count($users['results']); $k++) {
+ $combinedid = $users['results'][$k]['backedUpOrganizationId'] . $users['results'][$k]['id'];
+ $userdata = $veeam->getUserData($repoid, $combinedid);
+
+ /* Only store data when the SharePoint data is backed up */
+ if (!is_null($userdata)) {
+ array_push($repousersarray, array(
+ 'id' => $userdata['id'],
+ 'email' => $userdata['email'],
+ 'name' => $userdata['displayName'],
+ 'isMailboxBackedUp' => $userdata['isMailboxBackedUp'],
+ 'isOneDriveBackedUp' => $userdata['isOneDriveBackedUp'],
+ 'isArchiveBackedUp' => $userdata['isArchiveBackedUp'],
+ 'isPersonalSiteBackedUp' => $userdata['isPersonalSiteBackedUp']
+ ));
+ }
+ }
+ }
+
+ $usersort = array_values(array_column($repousersarray , null, 'id')); /* Sort the array and make sure every value is unique */
+ }
+ ?>
+
+
+
+
+ View
+
+
+
+
+
+
+
+ Name
+ Licensed
+ Last Backup
+ Objects in backup
+
+
+
+ ';
+ echo '' . $usersort[$y]['name'] . ' (' . $usersort[$y]['email'] . ') ';
+ echo '';
+ if (strtolower($usersarray[$licinfo]['licenseState']) == 'licensed') { echo 'Yes '; } else { echo 'No '; }
+ echo ' ';
+ echo '' . date('d/m/Y H:i T', strtotime($usersarray[$licinfo]['lastBackupDate'])) . ' ';
+ echo '';
+ if ($usersort[$y]['isMailboxBackedUp']) {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+ if ($usersort[$y]['isArchiveBackedUp']) {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+ if ($usersort[$y]['isOneDriveBackedUp']) {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+ if ($usersort[$y]['isPersonalSiteBackedUp']) {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+ echo ' ';
+ echo '';
+ }
+ ?>
+
+
+
+
+
+
+
+
+ No organizations have been added.';
+ }
+ ?>
+
+
+
+
\ No newline at end of file
diff --git a/includes/organizations.php b/includes/organizations.php
new file mode 100644
index 0000000..6a9f747
--- /dev/null
+++ b/includes/organizations.php
@@ -0,0 +1,113 @@
+setToken($_SESSION['token']);
+ $veeam->refreshToken($_SESSION['refreshtoken']);
+
+ $org = $veeam->getOrganizations();
+?>
+
+
Organizations
+
+
+
+
+
+ Name
+ Region
+ First backup
+ Last backup
+ Backup size
+ Licensed users
+
+
+
+
+
+
+
+
+
+
+ View
+
+
+
+
+
+
+
+ Name
+ License State
+ Last Backup
+ Backed Up
+
+
+
+ getLicensedUsers($org[$i]['id']);
+
+ for ($j = 0; $j < count($users['results']); $j++) {
+ echo '';
+ echo '' . $users['results'][$j]['name'] . ' ';
+ echo '' . $users['results'][$j]['licenseState'] . ' ';
+ echo '' . date('d/m/Y H:i T', strtotime($users['results'][$j]['lastBackupDate'])) . ' ';
+ echo '';
+ if ($users['results'][$j]['isBackedUp'] == 'true') { echo 'Yes '; } else { echo 'No '; }
+ echo ' ';
+ echo ' ';
+ }
+ ?>
+
+
+
+
+
+
+
+
+ getOrganizationRepository($org[$i]['id']);
+ ?>
+
+ No organizations have been added.';
+ }
+ ?>
+
+
+
+
\ No newline at end of file
diff --git a/includes/proxies.php b/includes/proxies.php
new file mode 100644
index 0000000..a390505
--- /dev/null
+++ b/includes/proxies.php
@@ -0,0 +1,75 @@
+setToken($_SESSION['token']);
+ $veeam->refreshToken($_SESSION['refreshtoken']);
+
+ $proxies = $veeam->getProxies();
+?>
+
+
Proxies
+
+
+
+
+
+ Name
+ Port
+ Description
+ Status
+
+
+
+
+
+
+
+
+
+ '.$proxies[$i]['status'].'';
+ } else {
+ echo ''.$proxies[$i]['status'].' ';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/repositories.php b/includes/repositories.php
new file mode 100644
index 0000000..521f6b7
--- /dev/null
+++ b/includes/repositories.php
@@ -0,0 +1,88 @@
+setToken($_SESSION['token']);
+ $veeam->refreshToken($_SESSION['refreshtoken']);
+
+ $repos = $veeam->getBackupRepositories();
+?>
+
+
Repositories
+
+
+
+
+
+ Name
+ Host
+ Retention Type
+ Capacity
+ Description
+
+
+
+ getProxy($repos[$i]['proxyId']);
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No backup repositories available.';
+ }
+ ?>
+
+
+
+
\ No newline at end of file
diff --git a/includes/tenants.php b/includes/tenants.php
new file mode 100644
index 0000000..701c210
--- /dev/null
+++ b/includes/tenants.php
@@ -0,0 +1,177 @@
+
+
+
Tenant overview
+
+
+
+
+
+ Name
+ E-mail
+ Options
+
+
+
+
+
+ name; ?>
+ email; ?>
+
+ Edit
+ Delete
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Password:
+
+ Generate new password
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..0339ff3
--- /dev/null
+++ b/index.php
@@ -0,0 +1,238 @@
+auth;
+
+ if ($auth == 1) {
+ $_SESSION['name'] = $login->name;
+ $_SESSION['lifetime'] = $login->token->lifetime;
+ $_SESSION['portaltoken'] = $login->token->token;
+ $_SESSION['portalrenewtoken'] = $login->token->renew;
+ }
+ } else {
+ $login = authenticate($username, $password);
+ $auth = $login->auth;
+
+ if ($auth == 1) {
+ $_SESSION['name'] = $login->name;
+ $_SESSION['tenantid'] = $login->tenantid;
+ $_SESSION['portaltoken'] = 'tenant';
+ }
+ }
+}
+
+if (isset($_POST['logout']) && $_POST['logout'] != "") {
+ unset($_SESSION);
+ session_destroy();
+}
+?>
+
+
+
+
+
+
Project Martini
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/js/filesize.min.js b/js/filesize.min.js
new file mode 100644
index 0000000..caaa083
--- /dev/null
+++ b/js/filesize.min.js
@@ -0,0 +1,5 @@
+/*
+ 2018 Jason Mulligan
+ @version 3.6.1
+*/
+"use strict";!function(e){var i=/^(b|B)$/,t={iec:{bits:["b","Kib","Mib","Gib","Tib","Pib","Eib","Zib","Yib"],bytes:["B","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"]},jedec:{bits:["b","Kb","Mb","Gb","Tb","Pb","Eb","Zb","Yb"],bytes:["B","KB","MB","GB","TB","PB","EB","ZB","YB"]}},o={iec:["","kibi","mebi","gibi","tebi","pebi","exbi","zebi","yobi"],jedec:["","kilo","mega","giga","tera","peta","exa","zetta","yotta"]};function b(e){var b,n,r,a,s,f,d,u,l,B,c,p,y,g=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},m=[],v=0,x=void 0,h=void 0;if(isNaN(e))throw new Error("Invalid arguments");return n=!0===g.bits,l=!0===g.unix,b=g.base||2,u=void 0!==g.round?g.round:l?1:2,B=void 0!==g.separator&&g.separator||"",c=void 0!==g.spacer?g.spacer:l?"":" ",y=g.symbols||g.suffixes||{},p=2===b&&g.standard||"jedec",d=g.output||"string",a=!0===g.fullform,s=g.fullforms instanceof Array?g.fullforms:[],x=void 0!==g.exponent?g.exponent:-1,f=(h=Number(e))<0,r=b>2?1e3:1024,f&&(h=-h),(-1===x||isNaN(x))&&(x=Math.floor(Math.log(h)/Math.log(r)))<0&&(x=0),x>8&&(x=8),0===h?(m[0]=0,m[1]=l?"":t[p][n?"bits":"bytes"][x]):(v=h/(2===b?Math.pow(2,10*x):Math.pow(1e3,x)),n&&(v*=8)>=r&&x<8&&(v/=r,x++),m[0]=Number(v.toFixed(x>0?u:0)),m[1]=10===b&&1===x?n?"kb":"kB":t[p][n?"bits":"bytes"][x],l&&(m[1]="jedec"===p?m[1].charAt(0):x>0?m[1].replace(/B$/,""):m[1],i.test(m[1])&&(m[0]=Math.floor(m[0]),m[1]=""))),f&&(m[0]=-m[0]),m[1]=y[m[1]]||m[1],"array"===d?m:"exponent"===d?x:"object"===d?{value:m[0],suffix:m[1],symbol:m[1]}:(a&&(m[1]=s[x]?s[x]:o[p][x]+(n?"bit":"byte")+(1===m[0]?"":"s")),B.length>0&&(m[0]=m[0].toString().replace(".",B)),m.join(c))}b.partial=function(e){return function(i){return b(i,e)}},"undefined"!=typeof exports?module.exports=b:"function"==typeof define&&define.amd?define(function(){return b}):e.filesize=b}("undefined"!=typeof window?window:global);
\ No newline at end of file
diff --git a/js/flatpickr.js b/js/flatpickr.js
new file mode 100644
index 0000000..20ece63
--- /dev/null
+++ b/js/flatpickr.js
@@ -0,0 +1,2 @@
+/* flatpickr v4.5.1,, @license MIT */
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.flatpickr=t()}(this,function(){"use strict";var Q=function(e){return("0"+e).slice(-2)},X=function(e){return!0===e?1:0};function ee(n,a,i){var o;return void 0===i&&(i=!1),function(){var e=this,t=arguments;null!==o&&clearTimeout(o),o=window.setTimeout(function(){o=null,i||n.apply(e,t)},a),i&&!o&&n.apply(e,t)}}var te=function(e){return e instanceof Array?e:[e]},e=function(){},ne=function(e,t,n){return n.months[t?"shorthand":"longhand"][e]},b={D:e,F:function(e,t,n){e.setMonth(n.months.longhand.indexOf(t))},G:function(e,t){e.setHours(parseFloat(t))},H:function(e,t){e.setHours(parseFloat(t))},J:function(e,t){e.setDate(parseFloat(t))},K:function(e,t,n){e.setHours(e.getHours()%12+12*X(new RegExp(n.amPM[1],"i").test(t)))},M:function(e,t,n){e.setMonth(n.months.shorthand.indexOf(t))},S:function(e,t){e.setSeconds(parseFloat(t))},U:function(e,t){return new Date(1e3*parseFloat(t))},W:function(e,t){var n=parseInt(t);return new Date(e.getFullYear(),0,2+7*(n-1),0,0,0,0)},Y:function(e,t){e.setFullYear(parseFloat(t))},Z:function(e,t){return new Date(t)},d:function(e,t){e.setDate(parseFloat(t))},h:function(e,t){e.setHours(parseFloat(t))},i:function(e,t){e.setMinutes(parseFloat(t))},j:function(e,t){e.setDate(parseFloat(t))},l:e,m:function(e,t){e.setMonth(parseFloat(t)-1)},n:function(e,t){e.setMonth(parseFloat(t)-1)},s:function(e,t){e.setSeconds(parseFloat(t))},w:e,y:function(e,t){e.setFullYear(2e3+parseFloat(t))}},ae={D:"(\\w+)",F:"(\\w+)",G:"(\\d\\d|\\d)",H:"(\\d\\d|\\d)",J:"(\\d\\d|\\d)\\w+",K:"",M:"(\\w+)",S:"(\\d\\d|\\d)",U:"(.+)",W:"(\\d\\d|\\d)",Y:"(\\d{4})",Z:"(.+)",d:"(\\d\\d|\\d)",h:"(\\d\\d|\\d)",i:"(\\d\\d|\\d)",j:"(\\d\\d|\\d)",l:"(\\w+)",m:"(\\d\\d|\\d)",n:"(\\d\\d|\\d)",s:"(\\d\\d|\\d)",w:"(\\d\\d|\\d)",y:"(\\d{2})"},l={Z:function(e){return e.toISOString()},D:function(e,t,n){return t.weekdays.shorthand[l.w(e,t,n)]},F:function(e,t,n){return ne(l.n(e,t,n)-1,!1,t)},G:function(e,t,n){return Q(l.h(e,t,n))},H:function(e){return Q(e.getHours())},J:function(e,t){return void 0!==t.ordinal?e.getDate()+t.ordinal(e.getDate()):e.getDate()},K:function(e,t){return t.amPM[X(11Math.min(t,n)&&e ",noCalendar:!1,now:new Date,onChange:[],onClose:[],onDayCreate:[],onDestroy:[],onKeyDown:[],onMonthChange:[],onOpen:[],onParseConfig:[],onReady:[],onValueUpdate:[],onYearChange:[],onPreCalendarPosition:[],plugins:[],position:"auto",positionElement:void 0,prevArrow:" ",shorthandCurrentMonth:!1,showMonths:1,static:!1,time_24hr:!1,weekNumbers:!1,wrap:!1};function se(e,t,n){if(!0===n)return e.classList.add(t);e.classList.remove(t)}function ue(e,t,n){var a=window.document.createElement(e);return t=t||"",n=n||"",a.className=t,void 0!==n&&(a.textContent=n),a}function fe(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function me(e,t){var n=ue("div","numInputWrapper"),a=ue("input","numInput "+e),i=ue("span","arrowUp"),o=ue("span","arrowDown");if(a.type="text",a.pattern="\\d*",void 0!==t)for(var r in t)a.setAttribute(r,t[r]);return n.appendChild(a),n.appendChild(i),n.appendChild(o),n}"function"!=typeof Object.assign&&(Object.assign=function(n){if(!n)throw TypeError("Cannot convert undefined or null to object");for(var e=arguments.length,a=new Array(1o)&&(h.amPM.textContent=h.l10n.amPM[X(h.amPM.textContent===h.l10n.amPM[0])]),n.value=Q(c)}}(e);var t=h._input.value;p(),V(),h._input.value!==t&&h._debouncedChange()}}function p(){if(void 0!==h.hourElement&&void 0!==h.minuteElement){var e,t,n=(parseInt(h.hourElement.value.slice(-2),10)||0)%24,a=(parseInt(h.minuteElement.value,10)||0)%60,i=void 0!==h.secondElement?(parseInt(h.secondElement.value,10)||0)%60:0;void 0!==h.amPM&&(e=n,t=h.amPM.textContent,n=e%12+12*X(t===h.l10n.amPM[1]));var o=void 0!==h.config.minTime||h.config.minDate&&h.minDateHasTime&&h.latestSelectedDateObj&&0===le(h.latestSelectedDateObj,h.config.minDate,!0);if(void 0!==h.config.maxTime||h.config.maxDate&&h.maxDateHasTime&&h.latestSelectedDateObj&&0===le(h.latestSelectedDateObj,h.config.maxDate,!0)){var r=void 0!==h.config.maxTime?h.config.maxTime:h.config.maxDate;(n=Math.min(n,r.getHours()))===r.getHours()&&(a=Math.min(a,r.getMinutes())),a===r.getMinutes()&&(i=Math.min(i,r.getSeconds()))}if(o){var l=void 0!==h.config.minTime?h.config.minTime:h.config.minDate;(n=Math.max(n,l.getHours()))===l.getHours()&&(a=Math.max(a,l.getMinutes())),a===l.getMinutes()&&(i=Math.max(i,l.getSeconds()))}c(n,a,i)}}function i(e){var t=e||h.latestSelectedDateObj;t&&c(t.getHours(),t.getMinutes(),t.getSeconds())}function a(){var e=h.config.defaultHour,t=h.config.defaultMinute,n=h.config.defaultSeconds;if(void 0!==h.config.minDate){var a=h.config.minDate.getHours(),i=h.config.minDate.getMinutes();(e=Math.max(e,a))===a&&(t=Math.max(i,t)),e===a&&t===i&&(n=h.config.minDate.getSeconds())}if(void 0!==h.config.maxDate){var o=h.config.maxDate.getHours(),r=h.config.maxDate.getMinutes();(e=Math.min(e,o))===o&&(t=Math.min(r,t)),e===o&&t===r&&(n=h.config.maxDate.getSeconds())}c(e,t,n)}function c(e,t,n){void 0!==h.latestSelectedDateObj&&h.latestSelectedDateObj.setHours(e%24,t,n||0,0),h.hourElement&&h.minuteElement&&!h.isMobile&&(h.hourElement.value=Q(h.config.time_24hr?e:(12+e)%12+12*X(e%12==0)),h.minuteElement.value=Q(t),void 0!==h.amPM&&(h.amPM.textContent=h.l10n.amPM[X(12<=e)]),void 0!==h.secondElement&&(h.secondElement.value=Q(n)))}function n(e){var t=parseInt(e.target.value)+(e.delta||0);(1h.now?h.config.minDate:h.config.maxDate&&h.config.maxDate"+h.config.getWeek(t)+""),q("onDayCreate",r),r}function w(e){e.focus(),"range"===h.config.mode&&P(e)}function b(e){for(var t=0=Math.abs(t))return w(s)}h.changeMonth(i),C(b(i),0)}(a,t):w(a)}function M(e,t){for(var n=(new Date(e,t,1).getDay()-h.l10n.firstDayOfWeek+7)%7,a=h.utils.getDaysInMonth((t-1+12)%12),i=h.utils.getDaysInMonth(t),o=window.document.createDocumentFragment(),r=1\n "+t.join("")+"\n \n "}function I(e,t){void 0===t&&(t=!0);var n=t?e:e-h.currentMonth;n<0&&!0===h._hidePrevMonthArrow||0h.config.maxDate.getFullYear())){var t=e,n=h.currentYear!==t;h.currentYear=t||h.currentYear,h.config.maxDate&&h.currentYear===h.config.maxDate.getFullYear()?h.currentMonth=Math.min(h.config.maxDate.getMonth(),h.currentMonth):h.config.minDate&&h.currentYear===h.config.minDate.getFullYear()&&(h.currentMonth=Math.max(h.config.minDate.getMonth(),h.currentMonth)),n&&(h.redraw(),q("onYearChange"))}}function N(e,t){void 0===t&&(t=!0);var n=h.parseDate(e,void 0,t);if(h.config.minDate&&n&&le(n,h.config.minDate,void 0!==t?t:!h.minDateHasTime)<0||h.config.maxDate&&n&&0=a.from.getTime()&&n.getTime()<=a.to.getTime())return i}return!i}function F(e){return void 0!==h.daysContainer&&(-1===e.className.indexOf("hidden")&&h.daysContainer.contains(e))}function A(e){var t=e.target===h._input,n=h.config.allowInput,a=h.isOpen&&(!n||!t),i=h.config.inline&&t&&!n;if(13===e.keyCode&&t){if(n)return h.setDate(h._input.value,!0,e.target===h.altInput?h.config.altFormat:h.config.dateFormat),e.target.blur();h.open()}else if(O(e.target)||a||i){var o=!!h.timeContainer&&h.timeContainer.contains(e.target);switch(e.keyCode){case 13:o?g():K(e);break;case 27:e.preventDefault(),R();break;case 8:case 46:t&&!h.config.allowInput&&(e.preventDefault(),h.clear());break;case 37:case 39:if(o)h.hourElement&&h.hourElement.focus();else if(e.preventDefault(),void 0!==h.daysContainer&&(!1===n||F(document.activeElement))){var r=39===e.keyCode?1:-1;e.ctrlKey?(I(r),C(b(1),0)):C(void 0,r)}break;case 38:case 40:e.preventDefault();var l=40===e.keyCode?1:-1;h.daysContainer?e.ctrlKey?(_(h.currentYear-l),C(b(1),0)):o||C(void 0,7*l):h.config.enableTime&&(!o&&h.hourElement&&h.hourElement.focus(),g(e),h._debouncedChange());break;case 9:if(!o)break;var c=[h.hourElement,h.minuteElement,h.secondElement,h.amPM].filter(function(e){return e}),d=c.indexOf(e.target);if(-1!==d){var s=c[d+(e.shiftKey?-1:1)];void 0!==s&&(e.preventDefault(),s.focus())}}}if(void 0!==h.amPM&&e.target===h.amPM)switch(e.key){case h.l10n.amPM[0].charAt(0):case h.l10n.amPM[0].charAt(0).toLowerCase():h.amPM.textContent=h.l10n.amPM[0],p(),V();break;case h.l10n.amPM[1].charAt(0):case h.l10n.amPM[1].charAt(0).toLowerCase():h.amPM.textContent=h.l10n.amPM[1],p(),V()}q("onKeyDown",e)}function P(o){if(1===h.selectedDates.length&&(!o||o.classList.contains("flatpickr-day")&&!o.classList.contains("disabled"))){for(var r=o?o.dateObj.getTime():h.days.firstElementChild.dateObj.getTime(),l=h.parseDate(h.selectedDates[0],void 0,!0).getTime(),e=Math.min(r,h.selectedDates[0].getTime()),t=Math.max(r,h.selectedDates[0].getTime()),n=h.daysContainer.lastChild.lastChild.dateObj.getTime(),c=!1,d=0,s=0,a=e;a=a||(ln,s=window.pageYOffset+l.top+(d?-n-2:t.offsetHeight+2);if(se(h.calendarContainer,"arrowTop",!d),se(h.calendarContainer,"arrowBottom",d),!h.config.inline){var u=window.pageXOffset+l.left-(null!=r&&"center"===r?(a-l.width)/2:0),f=window.document.body.offsetWidth-l.right,m=u+a>window.document.body.offsetWidth;se(h.calendarContainer,"rightMost",m),h.config.static||(h.calendarContainer.style.top=s+"px",m?(h.calendarContainer.style.left="auto",h.calendarContainer.style.right=f+"px"):(h.calendarContainer.style.left=u+"px",h.calendarContainer.style.right="auto"))}}}function W(){h.config.noCalendar||h.isMobile||(G(),y())}function R(){h._input.focus(),-1!==window.navigator.userAgent.indexOf("MSIE")||void 0!==navigator.msMaxTouchPoints?setTimeout(h.close,0):h.close()}function K(e){e.preventDefault(),e.stopPropagation();var t=function e(t,n){return n(t)?t:t.parentNode?e(t.parentNode,n):void 0}(e.target,function(e){return e.classList&&e.classList.contains("flatpickr-day")&&!e.classList.contains("disabled")&&!e.classList.contains("notAllowed")});if(void 0!==t){var n=t,a=h.latestSelectedDateObj=new Date(n.dateObj.getTime()),i=(a.getMonth()h.currentMonth+h.config.showMonths-1)&&"range"!==h.config.mode;if(h.selectedDateElem=n,"single"===h.config.mode)h.selectedDates=[a];else if("multiple"===h.config.mode){var o=z(a);o?h.selectedDates.splice(parseInt(o),1):h.selectedDates.push(a)}else"range"===h.config.mode&&(2===h.selectedDates.length&&h.clear(!1),h.selectedDates.push(a),0!==le(a,h.selectedDates[0],!0)&&h.selectedDates.sort(function(e,t){return e.getTime()-t.getTime()}));if(p(),i){var r=h.currentYear!==a.getFullYear();h.currentYear=a.getFullYear(),h.currentMonth=a.getMonth(),r&&q("onYearChange"),q("onMonthChange")}if(G(),y(),V(),h.config.enableTime&&setTimeout(function(){return h.showTimeInput=!0},50),i||"range"===h.config.mode||1!==h.config.showMonths?h.selectedDateElem&&h.selectedDateElem.focus():w(n),void 0!==h.hourElement&&setTimeout(function(){return void 0!==h.hourElement&&h.hourElement.select()},451),h.config.closeOnSelect){var l="single"===h.config.mode&&!h.config.enableTime,c="range"===h.config.mode&&2===h.selectedDates.length&&!h.config.enableTime;(l||c)&&R()}d()}}h.parseDate=re({config:h.config,l10n:h.l10n}),h._handlers=[],h._bind=o,h._setHoursFromDate=i,h._positionCalendar=L,h.changeMonth=I,h.changeYear=_,h.clear=function(e){void 0===e&&(e=!0);h.input.value="",void 0!==h.altInput&&(h.altInput.value="");void 0!==h.mobileInput&&(h.mobileInput.value="");h.selectedDates=[],h.latestSelectedDateObj=void 0,!(h.showTimeInput=!1)===h.config.enableTime&&a();h.redraw(),e&&q("onChange")},h.close=function(){h.isOpen=!1,h.isMobile||(h.calendarContainer.classList.remove("open"),h._input.classList.remove("active"));q("onClose")},h._createElement=ue,h.destroy=function(){void 0!==h.config&&q("onDestroy");for(var e=h._handlers.length;e--;){var t=h._handlers[e];t.element.removeEventListener(t.event,t.handler,t.options)}if(h._handlers=[],h.mobileInput)h.mobileInput.parentNode&&h.mobileInput.parentNode.removeChild(h.mobileInput),h.mobileInput=void 0;else if(h.calendarContainer&&h.calendarContainer.parentNode)if(h.config.static&&h.calendarContainer.parentNode){var n=h.calendarContainer.parentNode;for(n.lastChild&&n.removeChild(n.lastChild);n.firstChild;)n.parentNode.insertBefore(n.firstChild,n);n.parentNode.removeChild(n)}else h.calendarContainer.parentNode.removeChild(h.calendarContainer);h.altInput&&(h.input.type="text",h.altInput.parentNode&&h.altInput.parentNode.removeChild(h.altInput),delete h.altInput);h.input&&(h.input.type=h.input._type,h.input.classList.remove("flatpickr-input"),h.input.removeAttribute("readonly"),h.input.value="");["_showTimeInput","latestSelectedDateObj","_hideNextMonthArrow","_hidePrevMonthArrow","__hideNextMonthArrow","__hidePrevMonthArrow","isMobile","isOpen","selectedDateElem","minDateHasTime","maxDateHasTime","days","daysContainer","_input","_positionElement","innerContainer","rContainer","monthNav","todayDateElem","calendarContainer","weekdayContainer","prevMonthNav","nextMonthNav","currentMonthElement","currentYearElement","navigationCurrentMonth","selectedDateElem","config"].forEach(function(e){try{delete h[e]}catch(e){}})},h.isEnabled=N,h.jumpToDate=l,h.open=function(e,t){void 0===t&&(t=h._positionElement);if(!0===h.isMobile)return e&&(e.preventDefault(),e.target&&e.target.blur()),setTimeout(function(){void 0!==h.mobileInput&&h.mobileInput.focus()},0),void q("onOpen");if(h._input.disabled||h.config.inline)return;var n=h.isOpen;h.isOpen=!0,n||(h.calendarContainer.classList.add("open"),h._input.classList.add("active"),q("onOpen"),L(t));!0===h.config.enableTime&&!0===h.config.noCalendar&&(0===h.selectedDates.length&&(h.setDate(void 0!==h.config.minDate?new Date(h.config.minDate.getTime()):new Date,!1),a(),V()),!1!==h.config.allowInput||void 0!==e&&h.timeContainer.contains(e.relatedTarget)||setTimeout(function(){return h.hourElement.select()},50))},h.redraw=W,h.set=function(e,t){null!==e&&"object"==typeof e?Object.assign(h.config,e):(h.config[e]=t,void 0!==J[e]&&J[e].forEach(function(e){return e()}));h.redraw(),l()},h.setDate=function(e,t,n){void 0===t&&(t=!1);void 0===n&&(n=h.config.dateFormat);if(0!==e&&!e||e instanceof Array&&0===e.length)return h.clear(t);B(e,n),h.showTimeInput=0h.config.maxDate.getMonth():h.currentYear>h.config.maxDate.getFullYear()))}function V(e){if(void 0===e&&(e=!0),0===h.selectedDates.length)return h.clear(e);void 0!==h.mobileInput&&h.mobileFormatStr&&(h.mobileInput.value=void 0!==h.latestSelectedDateObj?h.formatDate(h.latestSelectedDateObj,h.mobileFormatStr):"");var t="range"!==h.config.mode?h.config.conjunction:h.l10n.rangeSeparator;h.input.value=h.selectedDates.map(function(e){return h.formatDate(e,h.config.dateFormat)}).join(t),void 0!==h.altInput&&(h.altInput.value=h.selectedDates.map(function(e){return h.formatDate(e,h.config.altFormat)}).join(t)),!1!==e&&q("onValueUpdate")}function Z(e){e.preventDefault();var t=h.prevMonthNav.contains(e.target),n=h.nextMonthNav.contains(e.target);t||n?I(t?-1:1):0<=h.yearElements.indexOf(e.target)?e.target.select():e.target.classList.contains("arrowUp")?h.changeYear(h.currentYear+1):e.target.classList.contains("arrowDown")&&h.changeYear(h.currentYear-1)}return function(){h.element=h.input=u,h.isOpen=!1,function(){var e=["wrap","weekNumbers","allowInput","clickOpens","time_24hr","enableTime","noCalendar","altInput","shorthandCurrentMonth","inline","static","enableSeconds","disableMobile"],t=["onChange","onClose","onDayCreate","onDestroy","onKeyDown","onMonthChange","onOpen","onParseConfig","onReady","onValueUpdate","onYearChange","onPreCalendarPosition"],n=Object.assign({},f,JSON.parse(JSON.stringify(u.dataset||{}))),a={};h.config.parseDate=n.parseDate,h.config.formatDate=n.formatDate,Object.defineProperty(h.config,"enable",{get:function(){return h.config._enable},set:function(e){h.config._enable=U(e)}}),Object.defineProperty(h.config,"disable",{get:function(){return h.config._disable},set:function(e){h.config._disable=U(e)}});var i="time"===n.mode;n.dateFormat||!n.enableTime&&!i||(a.dateFormat=n.noCalendar||i?"H:i"+(n.enableSeconds?":S":""):pe.defaultConfig.dateFormat+" H:i"+(n.enableSeconds?":S":"")),n.altInput&&(n.enableTime||i)&&!n.altFormat&&(a.altFormat=n.noCalendar||i?"h:i"+(n.enableSeconds?":S K":" K"):pe.defaultConfig.altFormat+" h:i"+(n.enableSeconds?":S":"")+" K"),Object.defineProperty(h.config,"minDate",{get:function(){return h.config._minDate},set:Y("min")}),Object.defineProperty(h.config,"maxDate",{get:function(){return h.config._maxDate},set:Y("max")});var o=function(t){return function(e){h.config["min"===t?"_minTime":"_maxTime"]=h.parseDate(e,"H:i")}};Object.defineProperty(h.config,"minTime",{get:function(){return h.config._minTime},set:o("min")}),Object.defineProperty(h.config,"maxTime",{get:function(){return h.config._maxTime},set:o("max")}),"time"===n.mode&&(h.config.noCalendar=!0,h.config.enableTime=!0),Object.assign(h.config,a,n);for(var r=0;rh.now.getTime()?h.config.minDate:h.config.maxDate&&h.config.maxDate.getTime()>>0;r--;)e[r]=t[r];return e}function V(t){return t.classList?U(t.classList):(t.getAttribute("class")||"").split(" ").filter(function(t){return t})}function K(t,e){var r,a=e.split("-"),n=a[0],i=a.slice(1).join("-");return n!==t||""===i||(r=i,~M.indexOf(r))?null:i}function G(t){return(""+t).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function J(r){return Object.keys(r||{}).reduce(function(t,e){return t+(e+": ")+r[e]+";"},"")}function Q(t){return t.size!==D.size||t.x!==D.x||t.y!==D.y||t.rotate!==D.rotate||t.flipX||t.flipY}function Z(t){var e=t.transform,r=t.containerWidth,a=t.iconWidth;return{outer:{transform:"translate("+r/2+" 256)"},inner:{transform:"translate("+32*e.x+", "+32*e.y+") "+" "+("scale("+e.size/16*(e.flipX?-1:1)+", "+e.size/16*(e.flipY?-1:1)+") ")+" "+("rotate("+e.rotate+" 0 0)")},path:{transform:"translate("+a/2*-1+" -256)"}}}var $={x:0,y:0,width:"100%",height:"100%"},tt=function(t){var e=t.children,r=t.attributes,a=t.main,n=t.mask,i=t.transform,o=a.width,s=a.icon,l=n.width,f=n.icon,c=Z({transform:i,containerWidth:l,iconWidth:o}),u={tag:"rect",attributes:E({},$,{fill:"white"})},m={tag:"g",attributes:E({},c.inner),children:[{tag:"path",attributes:E({},s.attributes,c.path,{fill:"black"})}]},d={tag:"g",attributes:E({},c.outer),children:[m]},g="mask-"+q(),h="clip-"+q(),p={tag:"defs",children:[{tag:"clipPath",attributes:{id:h},children:[f]},{tag:"mask",attributes:E({},$,{id:g,maskUnits:"userSpaceOnUse",maskContentUnits:"userSpaceOnUse"}),children:[u,d]}]};return e.push(p,{tag:"rect",attributes:E({fill:"currentColor","clip-path":"url(#"+h+")",mask:"url(#"+g+")"},$)}),{children:e,attributes:r}},et=function(t){var e=t.children,r=t.attributes,a=t.main,n=t.transform,i=J(t.styles);if(0"+o.map(Ct).join("")+""+e+">"}var At=function(){};function Nt(t){return"string"==typeof(t.getAttribute?t.getAttribute(A):null)}var zt={replace:function(t){var e=t[0],r=t[1].map(function(t){return Ct(t)}).join("\n");if(e.parentNode&&e.outerHTML)e.outerHTML=r+(T.keepOriginalSource&&"svg"!==e.tagName.toLowerCase()?"\x3c!-- "+e.outerHTML+" --\x3e":"");else if(e.parentNode){var a=document.createElement("span");e.parentNode.replaceChild(a,e),a.outerHTML=r}},nest:function(t){var e=t[0],r=t[1];if(~V(e).indexOf(T.replacementClass))return zt.replace(t);var a=new RegExp(T.familyPrefix+"-.*");delete r[0].attributes.style;var n=r[0].attributes.class.split(" ").reduce(function(t,e){return e===T.replacementClass||e.match(a)?t.toSvg.push(e):t.toNode.push(e),t},{toNode:[],toSvg:[]});r[0].attributes.class=n.toSvg.join(" ");var i=r.map(function(t){return Ct(t)}).join("\n");e.setAttribute("class",n.toNode.join(" ")),e.setAttribute(A,""),e.innerHTML=i}};function Mt(r,t){var a="function"==typeof t?t:At;0===r.length?a():(m.requestAnimationFrame||function(t){return t()})(function(){var t=!0===T.autoReplaceSvg?zt.replace:zt[T.autoReplaceSvg]||zt.replace,e=ct.begin("mutate");r.map(t),e(),a()})}var St=!1;var Lt=null;function Et(t){if(s&&T.observeMutations){var n=t.treeCallback,i=t.nodeCallback,o=t.pseudoElementsCallback,e=t.observeMutationsRoot,r=void 0===e?d.body:e;Lt=new s(function(t){St||U(t).forEach(function(t){if("childList"===t.type&&0li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-webkit-transform:scale(1,-1);transform:scale(1,-1)}.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1,-1);transform:scale(-1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1em}.svg-inline--fa.fa-stack-2x{height:2em;width:2em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}";if("fa"!==e||r!==t){var n=new RegExp("\\.fa\\-","g"),i=new RegExp("\\."+t,"g");a=a.replace(n,"."+e+"-").replace(i,"."+r)}return a};function ae(t){return{found:!0,width:t[0],height:t[1],icon:{tag:"path",attributes:{fill:"currentColor",d:t.slice(4)[0]}}}}function ne(){T.autoAddCss&&!fe&&(W(re()),fe=!0)}function ie(e,t){return Object.defineProperty(e,"abstract",{get:t}),Object.defineProperty(e,"html",{get:function(){return e.abstract.map(function(t){return Ct(t)})}}),Object.defineProperty(e,"node",{get:function(){if(c){var t=d.createElement("div");return t.innerHTML=e.html,t.children}}}),e}function oe(t){var e=t.prefix,r=void 0===e?"fa":e,a=t.iconName;if(a)return kt(le.definitions,r,a)||kt(I.styles,r,a)}var se,le=new(function(){function t(){S(this,t),this.definitions={}}return L(t,[{key:"add",value:function(){for(var e=this,t=arguments.length,r=Array(t),a=0;a')
+ .attr("method", method)
+ .attr("action", url + hash);
+
+
+ if (target) {
+ form.attr("target", target);
+ }
+
+ var submit = form[0].submit;
+ iterateValues(values, [], form, null, traditional);
+
+ return { form: form, submit: function () { submit.call(form[0]); } };
+ }
+
+ //Utility Functions
+ /**
+ * Url and QueryString Parser.
+ * @param {string} url - a Url to parse.
+ * @returns {object} an object with the parsed url with the following structure {url: URL, params:{ KEY: VALUE }}
+ */
+ $.parseUrl = function (url) {
+
+ if (url.indexOf('?') === -1) {
+ return {
+ url: url,
+ params: {}
+ };
+ }
+ var parts = url.split('?'),
+ query_string = parts[1],
+ elems = query_string.split('&');
+ url = parts[0];
+
+ var i, pair, obj = {};
+ for (i = 0; i < elems.length; i += 1) {
+ pair = elems[i].split('=');
+ obj[pair[0]] = pair[1];
+ }
+
+ return {
+ url: url,
+ params: obj
+ };
+ };
+
+ //Private Functions
+ var getInput = function (name, value, parent, array, traditional) {
+ var parentString;
+ if (parent.length > 0) {
+ parentString = parent[0];
+ var i;
+ for (i = 1; i < parent.length; i += 1) {
+ parentString += "[" + parent[i] + "]";
+ }
+
+ if (array) {
+ if (traditional)
+ name = parentString;
+ else
+ name = parentString + "[" + name + "]";
+ } else {
+ name = parentString + "[" + name + "]";
+ }
+ }
+
+ return $(" ").attr("type", "hidden")
+ .attr("name", name)
+ .attr("value", value);
+ };
+
+ var iterateValues = function (values, parent, form, isArray, traditional) {
+ var i, iterateParent = [];
+ Object.keys(values).forEach(function (i) {
+ if (typeof values[i] === "object") {
+ iterateParent = parent.slice();
+ iterateParent.push(i);
+ iterateValues(values[i], iterateParent, form, Array.isArray(values[i]), traditional);
+ } else {
+ form.append(getInput(i, values[i], parent, isArray, traditional));
+ }
+ });
+ };
+
+ var removeNulls = function (values) {
+ var propNames = Object.getOwnPropertyNames(values);
+ for (var i = 0; i < propNames.length; i++) {
+ var propName = propNames[i];
+ if (values[propName] === null || values[propName] === undefined) {
+ delete values[propName];
+ } else if (typeof values[propName] === 'object') {
+ values[propName] = removeNulls(values[propName]);
+ } else if (values[propName].length < 1) {
+ delete values[propName];
+ }
+ }
+ return values;
+ };
+}(window.jQuery || window.Zepto || window.jqlite));
diff --git a/js/moment.min.js b/js/moment.min.js
new file mode 100644
index 0000000..5787a40
--- /dev/null
+++ b/js/moment.min.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var e,i;function c(){return e.apply(null,arguments)}function o(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function u(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function l(e){return void 0===e}function h(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function d(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function f(e,t){var n,s=[];for(n=0;n>>0,s=0;sSe(e)?(r=e+1,o-Se(e)):(r=e,o),{year:r,dayOfYear:a}}function Ie(e,t,n){var s,i,r=Ve(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+Ae(i=e.year()-1,t,n):a>Ae(e.year(),t,n)?(s=a-Ae(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function Ae(e,t,n){var s=Ve(e,t,n),i=Ve(e+1,t,n);return(Se(e)-s+i)/7}I("w",["ww",2],"wo","week"),I("W",["WW",2],"Wo","isoWeek"),C("week","w"),C("isoWeek","W"),F("week",5),F("isoWeek",5),ue("w",B),ue("ww",B,z),ue("W",B),ue("WW",B,z),fe(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=D(e)});function je(e,t){return e.slice(t,7).concat(e.slice(0,t))}I("d",0,"do","day"),I("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),I("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),I("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),I("e",0,0,"weekday"),I("E",0,0,"isoWeekday"),C("day","d"),C("weekday","e"),C("isoWeekday","E"),F("day",11),F("weekday",11),F("isoWeekday",11),ue("d",B),ue("e",B),ue("E",B),ue("dd",function(e,t){return t.weekdaysMinRegex(e)}),ue("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ue("dddd",function(e,t){return t.weekdaysRegex(e)}),fe(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:g(n).invalidWeekday=e}),fe(["d","e","E"],function(e,t,n,s){t[s]=D(e)});var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_");var ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_");var $e="Su_Mo_Tu_We_Th_Fr_Sa".split("_");var qe=ae;var Je=ae;var Be=ae;function Qe(){function e(e,t){return t.length-e.length}var t,n,s,i,r,a=[],o=[],u=[],l=[];for(t=0;t<7;t++)n=y([2e3,1]).day(t),s=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),r=this.weekdays(n,""),a.push(s),o.push(i),u.push(r),l.push(s),l.push(i),l.push(r);for(a.sort(e),o.sort(e),u.sort(e),l.sort(e),t=0;t<7;t++)o[t]=he(o[t]),u[t]=he(u[t]),l[t]=he(l[t]);this._weekdaysRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function Xe(){return this.hours()%12||12}function Ke(e,t){I(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function et(e,t){return t._meridiemParse}I("H",["HH",2],0,"hour"),I("h",["hh",2],0,Xe),I("k",["kk",2],0,function(){return this.hours()||24}),I("hmm",0,0,function(){return""+Xe.apply(this)+L(this.minutes(),2)}),I("hmmss",0,0,function(){return""+Xe.apply(this)+L(this.minutes(),2)+L(this.seconds(),2)}),I("Hmm",0,0,function(){return""+this.hours()+L(this.minutes(),2)}),I("Hmmss",0,0,function(){return""+this.hours()+L(this.minutes(),2)+L(this.seconds(),2)}),Ke("a",!0),Ke("A",!1),C("hour","h"),F("hour",13),ue("a",et),ue("A",et),ue("H",B),ue("h",B),ue("k",B),ue("HH",B,z),ue("hh",B,z),ue("kk",B,z),ue("hmm",Q),ue("hmmss",X),ue("Hmm",Q),ue("Hmmss",X),ce(["H","HH"],ge),ce(["k","kk"],function(e,t,n){var s=D(e);t[ge]=24===s?0:s}),ce(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ce(["h","hh"],function(e,t,n){t[ge]=D(e),g(n).bigHour=!0}),ce("hmm",function(e,t,n){var s=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s)),g(n).bigHour=!0}),ce("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s,2)),t[pe]=D(e.substr(i)),g(n).bigHour=!0}),ce("Hmm",function(e,t,n){var s=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s))}),ce("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[ge]=D(e.substr(0,s)),t[ve]=D(e.substr(s,2)),t[pe]=D(e.substr(i))});var tt,nt=Te("Hours",!0),st={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:He,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:$e,weekdaysShort:ze,meridiemParse:/[ap]\.?m?\.?/i},it={},rt={};function at(e){return e?e.toLowerCase().replace("_","-"):e}function ot(e){var t=null;if(!it[e]&&"undefined"!=typeof module&&module&&module.exports)try{t=tt._abbr,require("./locale/"+e),ut(t)}catch(e){}return it[e]}function ut(e,t){var n;return e&&((n=l(t)?ht(e):lt(e,t))?tt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),tt._abbr}function lt(e,t){if(null===t)return delete it[e],null;var n,s=st;if(t.abbr=e,null!=it[e])T("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=it[e]._config;else if(null!=t.parentLocale)if(null!=it[t.parentLocale])s=it[t.parentLocale]._config;else{if(null==(n=ot(t.parentLocale)))return rt[t.parentLocale]||(rt[t.parentLocale]=[]),rt[t.parentLocale].push({name:e,config:t}),null;s=n._config}return it[e]=new P(x(s,t)),rt[e]&&rt[e].forEach(function(e){lt(e.name,e.config)}),ut(e),it[e]}function ht(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return tt;if(!o(e)){if(t=ot(e))return t;e=[e]}return function(e){for(var t,n,s,i,r=0;r=t&&a(i,n,!0)>=t-1)break;t--}r++}return tt}(e)}function dt(e){var t,n=e._a;return n&&-2===g(e).overflow&&(t=n[_e]<0||11Pe(n[me],n[_e])?ye:n[ge]<0||24Ae(n,r,a)?g(e)._overflowWeeks=!0:null!=u?g(e)._overflowWeekday=!0:(o=Ee(n,s,i,r,a),e._a[me]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(r=ct(e._a[me],s[me]),(e._dayOfYear>Se(r)||0===e._dayOfYear)&&(g(e)._overflowDayOfYear=!0),n=Ge(r,0,e._dayOfYear),e._a[_e]=n.getUTCMonth(),e._a[ye]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=a[t]=s[t];for(;t<7;t++)e._a[t]=a[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[ge]&&0===e._a[ve]&&0===e._a[pe]&&0===e._a[we]&&(e._nextDay=!0,e._a[ge]=0),e._d=(e._useUTC?Ge:function(e,t,n,s,i,r,a){var o;return e<100&&0<=e?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}).apply(null,a),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[ge]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(g(e).weekdayMismatch=!0)}}var mt=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,_t=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,yt=/Z|[+-]\d\d(?::?\d\d)?/,gt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],vt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],pt=/^\/?Date\((\-?\d+)/i;function wt(e){var t,n,s,i,r,a,o=e._i,u=mt.exec(o)||_t.exec(o);if(u){for(g(e).iso=!0,t=0,n=gt.length;tn.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},mn.isLocal=function(){return!!this.isValid()&&!this._isUTC},mn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},mn.isUtc=Et,mn.isUTC=Et,mn.zoneAbbr=function(){return this._isUTC?"UTC":""},mn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},mn.dates=n("dates accessor is deprecated. Use date instead.",un),mn.months=n("months accessor is deprecated. Use month instead",Ue),mn.years=n("years accessor is deprecated. Use year instead",Oe),mn.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),mn.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!l(this._isDSTShifted))return this._isDSTShifted;var e={};if(w(e,this),(e=Ot(e))._a){var t=e._isUTC?y(e._a):bt(e._a);this._isDSTShifted=this.isValid()&&0\n \n \n
\n
\n
\n
\n \n \n
\n
\n
\n
\n \n \n \n
\n
\n
\n \n OK \n Cancel \n
\n \n \n').replace(/(^|\n)\s*/g,""),$=function(e){var t=b();if(t&&(t.parentNode.removeChild(t),U([document.documentElement,document.body],[I["no-backdrop"],I["toast-shown"],I["has-column"]])),!O()){var n=document.createElement("div");n.className=I.container,n.innerHTML=M;var o="string"==typeof e.target?document.querySelector(e.target):e.target;o.appendChild(n);var i,r=w(),a=x(),s=_(a,I.input),c=_(a,I.file),u=a.querySelector(".".concat(I.range," input")),l=a.querySelector(".".concat(I.range," output")),d=_(a,I.select),p=a.querySelector(".".concat(I.checkbox," input")),f=_(a,I.textarea);r.setAttribute("role",e.toast?"alert":"dialog"),r.setAttribute("aria-live",e.toast?"polite":"assertive"),e.toast||r.setAttribute("aria-modal","true"),"rtl"===window.getComputedStyle(o).direction&&D(b(),I.rtl);var m=function(e){Le.isVisible()&&i!==e.target.value&&Le.resetValidationMessage(),i=e.target.value};return s.oninput=m,c.onchange=m,d.onchange=m,p.onchange=m,f.oninput=m,u.oninput=function(e){m(e),l.value=u.value},u.onchange=function(e){m(e),u.nextSibling.value=u.value},r}j("SweetAlert2 requires document to initialize")},J=function(e,t){if(!e)return W(t);if(e instanceof HTMLElement)t.appendChild(e);else if("object"===V(e))if(t.innerHTML="",0 in e)for(var n=0;n in e;n++)t.appendChild(e[n].cloneNode(!0));else t.appendChild(e.cloneNode(!0));else e&&(t.innerHTML=e);z(t)},X=function(){if(O())return!1;var e=document.createElement("div"),t={WebkitAnimation:"webkitAnimationEnd",OAnimation:"oAnimationEnd oanimationend",animation:"animationend"};for(var n in t)if(t.hasOwnProperty(n)&&void 0!==e.style[n])return t[n];return!1}(),G=function(e){var t=F(),n=S(),o=L();if(e.showConfirmButton||e.showCancelButton?z(t):W(t),e.showCancelButton?o.style.display="inline-block":W(o),e.showConfirmButton?n.style.removeProperty("display"):W(n),n.innerHTML=e.confirmButtonText,o.innerHTML=e.cancelButtonText,n.setAttribute("aria-label",e.confirmButtonAriaLabel),o.setAttribute("aria-label",e.cancelButtonAriaLabel),n.className=I.confirm,D(n,e.confirmButtonClass),o.className=I.cancel,D(o,e.cancelButtonClass),e.buttonsStyling){D([n,o],I.styled),e.confirmButtonColor&&(n.style.backgroundColor=e.confirmButtonColor),e.cancelButtonColor&&(o.style.backgroundColor=e.cancelButtonColor);var i=window.getComputedStyle(n).getPropertyValue("background-color");n.style.borderLeftColor=i,n.style.borderRightColor=i}else U([n,o],I.styled),n.style.backgroundColor=n.style.borderLeftColor=n.style.borderRightColor="",o.style.backgroundColor=o.style.borderLeftColor=o.style.borderRightColor=""},ee=function(e){var t=x().querySelector("#"+I.content);e.html?J(e.html,t):e.text?(t.textContent=e.text,z(t)):W(t)},te=function(e){for(var t=C(),n=0;n=i.progressSteps.length&&q("Invalid currentProgressStep parameter, it should be less than progressSteps.length (currentProgressStep like JS arrays starts from 0)"),i.progressSteps.forEach(function(e,t){var n=document.createElement("li");if(D(n,I["progress-step"]),n.innerHTML=e,t===a&&D(n,I["active-progress-step"]),r.appendChild(n),t!==i.progressSteps.length-1){var o=document.createElement("li");D(o,I["progress-step-line"]),i.progressStepsDistance&&(o.style.width=i.progressStepsDistance),r.appendChild(o)}})):W(r)},ie=function(e){var t=k();e.titleText?t.innerText=e.titleText:e.title&&("string"==typeof e.title&&(e.title=e.title.split("\n").join(" ")),J(e.title,t))};var re=[],ae=function(){var e=w();e||Le.fire(""),e=w();var t=F(),n=S(),o=L();z(t),z(n),D([e,t],I.loading),n.disabled=!0,o.disabled=!0,e.setAttribute("data-loading",!0),e.setAttribute("aria-busy",!0),e.focus()},se={},ce={title:"",titleText:"",text:"",html:"",footer:"",type:null,toast:!1,customClass:"",customContainerClass:"",target:"body",backdrop:!0,animation:!0,heightAuto:!0,allowOutsideClick:!0,allowEscapeKey:!0,allowEnterKey:!0,stopKeydownPropagation:!0,keydownListenerCapture:!1,showConfirmButton:!0,showCancelButton:!1,preConfirm:null,confirmButtonText:"OK",confirmButtonAriaLabel:"",confirmButtonColor:null,confirmButtonClass:"",cancelButtonText:"Cancel",cancelButtonAriaLabel:"",cancelButtonColor:null,cancelButtonClass:"",buttonsStyling:!0,reverseButtons:!1,focusConfirm:!0,focusCancel:!1,showCloseButton:!1,closeButtonAriaLabel:"Close this dialog",showLoaderOnConfirm:!1,imageUrl:null,imageWidth:null,imageHeight:null,imageAlt:"",imageClass:"",timer:null,width:null,padding:null,background:null,input:null,inputPlaceholder:"",inputValue:"",inputOptions:{},inputAutoTrim:!0,inputClass:"",inputAttributes:{},inputValidator:null,validationMessage:null,grow:!1,position:"center",progressSteps:[],currentProgressStep:null,progressStepsDistance:null,onBeforeOpen:null,onAfterClose:null,onOpen:null,onClose:null,scrollbarPadding:!0},ue=[],le=["allowOutsideClick","allowEnterKey","backdrop","focusConfirm","focusCancel","heightAuto","keydownListenerCapture"],de=function(e){return ce.hasOwnProperty(e)},pe=function(e){return-1!==ue.indexOf(e)},fe=Object.freeze({isValidParameter:de,isUpdatableParameter:function(e){return-1!==["title","titleText","text","html","type","showConfirmButton","showCancelButton","confirmButtonText","confirmButtonAriaLabel","confirmButtonColor","confirmButtonClass","cancelButtonText","cancelButtonAriaLabel","cancelButtonColor","cancelButtonClass","buttonsStyling","reverseButtons","imageUrl","imageWidth","imageHeigth","imageAlt","imageClass","progressSteps","currentProgressStep"].indexOf(e)},isDeprecatedParameter:pe,argsToParams:function(n){var o={};switch(V(n[0])){case"object":s(o,n[0]);break;default:["title","html","type"].forEach(function(e,t){switch(V(n[t])){case"string":o[e]=n[t];break;case"undefined":break;default:j("Unexpected type of ".concat(e,'! Expected "string", got ').concat(V(n[t])))}})}return o},isVisible:function(){return K(w())},clickConfirm:function(){return S().click()},clickCancel:function(){return L().click()},getContainer:b,getPopup:w,getTitle:k,getContent:x,getImage:B,getIcons:C,getCloseButton:Q,getActions:F,getConfirmButton:S,getCancelButton:L,getFooter:Z,getFocusableElements:Y,getValidationMessage:P,isLoading:function(){return w().hasAttribute("data-loading")},fire:function(){for(var e=arguments.length,t=new Array(e),n=0;nwindow.innerHeight&&(m.previousBodyPadding=parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right")),document.body.style.paddingRight=m.previousBodyPadding+function(){if("ontouchstart"in window||navigator.msMaxTouchPoints)return 0;var e=document.createElement("div");e.style.width="50px",e.style.height="50px",e.style.overflow="scroll",document.body.appendChild(e);var t=e.offsetWidth-e.clientWidth;return document.body.removeChild(e),t}()+"px")},be=function(){return!!window.MSInputMethodContext&&!!document.documentMode},ve=function(){var e=b(),t=w();e.style.removeProperty("align-items"),t.offsetTop<0&&(e.style.alignItems="flex-start")},ye={swalPromiseResolve:new WeakMap};function we(e){var t=b(),n=w(),o=me.innerParams.get(this),i=ye.swalPromiseResolve.get(this),r=o.onClose,a=o.onAfterClose;if(n){null!==r&&"function"==typeof r&&r(n),U(n,I.show),D(n,I.hide);var s=function(){T()?Ce(a):(new Promise(function(e){var t=window.scrollX,n=window.scrollY;se.restoreFocusTimeout=setTimeout(function(){se.previousActiveElement&&se.previousActiveElement.focus?(se.previousActiveElement.focus(),se.previousActiveElement=null):document.body&&document.body.focus(),e()},100),void 0!==t&&void 0!==n&&window.scrollTo(t,n)}).then(function(){return Ce(a)}),se.keydownTarget.removeEventListener("keydown",se.keydownHandler,{capture:se.keydownListenerCapture}),se.keydownHandlerAdded=!1),t.parentNode&&t.parentNode.removeChild(t),U([document.documentElement,document.body],[I.shown,I["height-auto"],I["no-backdrop"],I["toast-shown"],I["toast-column"]]),E()&&(null!==m.previousBodyPadding&&(document.body.style.paddingRight=m.previousBodyPadding+"px",m.previousBodyPadding=null),function(){if(g(document.body,I.iosfix)){var e=parseInt(document.body.style.top,10);U(document.body,I.iosfix),document.body.style.top="",document.body.scrollTop=-1*e}}(),"undefined"!=typeof window&&be()&&window.removeEventListener("resize",ve),p(document.body.children).forEach(function(e){e.hasAttribute("data-previous-aria-hidden")?(e.setAttribute("aria-hidden",e.getAttribute("data-previous-aria-hidden")),e.removeAttribute("data-previous-aria-hidden")):e.removeAttribute("aria-hidden")}))};X&&!g(n,I.noanimation)?n.addEventListener(X,function e(){n.removeEventListener(X,e),g(n,I.hide)&&s()}):s(),i(e||{})}}var Ce=function(e){null!==e&&"function"==typeof e&&setTimeout(function(){e()})};var ke=function e(t,n){a(this,e);var o,i,r=n;this.running=!1,this.start=function(){return this.running||(this.running=!0,i=new Date,o=setTimeout(t,r)),r},this.stop=function(){return this.running&&(this.running=!1,clearTimeout(o),r-=new Date-i),r},this.increase=function(e){var t=this.running;return t&&this.stop(),r+=e,t&&this.start(),r},this.getTimerLeft=function(){return this.running&&(this.stop(),this.start()),r},this.isRunning=function(){return this.running},this.start()},xe={email:function(e,t){return/^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,24}$/.test(e)?Promise.resolve():Promise.resolve(t||"Invalid email address")},url:function(e,t){return/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,63}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)$/.test(e)?Promise.resolve():Promise.resolve(t||"Invalid URL")}};var Be=function(e){var t=b(),n=w();null!==e.onBeforeOpen&&"function"==typeof e.onBeforeOpen&&e.onBeforeOpen(n),e.animation?(D(n,I.show),D(t,I.fade),U(n,I.hide)):U(n,I.fade),z(n),t.style.overflowY="hidden",X&&!g(n,I.noanimation)?n.addEventListener(X,function e(){n.removeEventListener(X,e),t.style.overflowY="auto"}):t.style.overflowY="auto",D([document.documentElement,document.body,t],I.shown),e.heightAuto&&e.backdrop&&!e.toast&&D([document.documentElement,document.body],I["height-auto"]),E()&&(e.scrollbarPadding&&he(),function(){if(/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream&&!g(document.body,I.iosfix)){var e=document.body.scrollTop;document.body.style.top=-1*e+"px",D(document.body,I.iosfix)}}(),"undefined"!=typeof window&&be()&&(ve(),window.addEventListener("resize",ve)),p(document.body.children).forEach(function(e){e===b()||function(e,t){if("function"==typeof e.contains)return e.contains(t)}(e,b())||(e.hasAttribute("aria-hidden")&&e.setAttribute("data-previous-aria-hidden",e.getAttribute("aria-hidden")),e.setAttribute("aria-hidden","true"))}),setTimeout(function(){t.scrollTop=0})),T()||se.previousActiveElement||(se.previousActiveElement=document.activeElement),null!==e.onOpen&&"function"==typeof e.onOpen&&setTimeout(function(){e.onOpen(n)})};var Ae,Pe=Object.freeze({hideLoading:ge,disableLoading:ge,getInput:function(e){var t=me.innerParams.get(this),n=me.domCache.get(this);if(!(e=e||t.input))return null;switch(e){case"select":case"textarea":case"file":return _(n.content,I[e]);case"checkbox":return n.popup.querySelector(".".concat(I.checkbox," input"));case"radio":return n.popup.querySelector(".".concat(I.radio," input:checked"))||n.popup.querySelector(".".concat(I.radio," input:first-child"));case"range":return n.popup.querySelector(".".concat(I.range," input"));default:return _(n.content,I.input)}},close:we,closePopup:we,closeModal:we,closeToast:we,enableButtons:function(){var e=me.domCache.get(this);e.confirmButton.disabled=!1,e.cancelButton.disabled=!1},disableButtons:function(){var e=me.domCache.get(this);e.confirmButton.disabled=!0,e.cancelButton.disabled=!0},enableConfirmButton:function(){me.domCache.get(this).confirmButton.disabled=!1},disableConfirmButton:function(){me.domCache.get(this).confirmButton.disabled=!0},enableInput:function(){var e=this.getInput();if(!e)return!1;if("radio"===e.type)for(var t=e.parentNode.parentNode.querySelectorAll("input"),n=0;n.swal2-modal{box-shadow:0 0 10px rgba(0,0,0,.4)}body.swal2-no-backdrop .swal2-shown.swal2-top{top:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-top-left,body.swal2-no-backdrop .swal2-shown.swal2-top-start{top:0;left:0}body.swal2-no-backdrop .swal2-shown.swal2-top-end,body.swal2-no-backdrop .swal2-shown.swal2-top-right{top:0;right:0}body.swal2-no-backdrop .swal2-shown.swal2-center{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}body.swal2-no-backdrop .swal2-shown.swal2-center-left,body.swal2-no-backdrop .swal2-shown.swal2-center-start{top:50%;left:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-center-end,body.swal2-no-backdrop .swal2-shown.swal2-center-right{top:50%;right:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-bottom{bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}body.swal2-no-backdrop .swal2-shown.swal2-bottom-left,body.swal2-no-backdrop .swal2-shown.swal2-bottom-start{bottom:0;left:0}body.swal2-no-backdrop .swal2-shown.swal2-bottom-end,body.swal2-no-backdrop .swal2-shown.swal2-bottom-right{right:0;bottom:0}.swal2-container{display:flex;position:fixed;top:0;right:0;bottom:0;left:0;flex-direction:row;align-items:center;justify-content:center;padding:10px;background-color:transparent;z-index:1060;overflow-x:hidden;-webkit-overflow-scrolling:touch}.swal2-container.swal2-top{align-items:flex-start}.swal2-container.swal2-top-left,.swal2-container.swal2-top-start{align-items:flex-start;justify-content:flex-start}.swal2-container.swal2-top-end,.swal2-container.swal2-top-right{align-items:flex-start;justify-content:flex-end}.swal2-container.swal2-center{align-items:center}.swal2-container.swal2-center-left,.swal2-container.swal2-center-start{align-items:center;justify-content:flex-start}.swal2-container.swal2-center-end,.swal2-container.swal2-center-right{align-items:center;justify-content:flex-end}.swal2-container.swal2-bottom{align-items:flex-end}.swal2-container.swal2-bottom-left,.swal2-container.swal2-bottom-start{align-items:flex-end;justify-content:flex-start}.swal2-container.swal2-bottom-end,.swal2-container.swal2-bottom-right{align-items:flex-end;justify-content:flex-end}.swal2-container.swal2-bottom-end>:first-child,.swal2-container.swal2-bottom-left>:first-child,.swal2-container.swal2-bottom-right>:first-child,.swal2-container.swal2-bottom-start>:first-child,.swal2-container.swal2-bottom>:first-child{margin-top:auto}.swal2-container.swal2-grow-fullscreen>.swal2-modal{display:flex!important;flex:1;align-self:stretch;justify-content:center}.swal2-container.swal2-grow-row>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container.swal2-grow-column{flex:1;flex-direction:column}.swal2-container.swal2-grow-column.swal2-bottom,.swal2-container.swal2-grow-column.swal2-center,.swal2-container.swal2-grow-column.swal2-top{align-items:center}.swal2-container.swal2-grow-column.swal2-bottom-left,.swal2-container.swal2-grow-column.swal2-bottom-start,.swal2-container.swal2-grow-column.swal2-center-left,.swal2-container.swal2-grow-column.swal2-center-start,.swal2-container.swal2-grow-column.swal2-top-left,.swal2-container.swal2-grow-column.swal2-top-start{align-items:flex-start}.swal2-container.swal2-grow-column.swal2-bottom-end,.swal2-container.swal2-grow-column.swal2-bottom-right,.swal2-container.swal2-grow-column.swal2-center-end,.swal2-container.swal2-grow-column.swal2-center-right,.swal2-container.swal2-grow-column.swal2-top-end,.swal2-container.swal2-grow-column.swal2-top-right{align-items:flex-end}.swal2-container.swal2-grow-column>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container:not(.swal2-top):not(.swal2-top-start):not(.swal2-top-end):not(.swal2-top-left):not(.swal2-top-right):not(.swal2-center-start):not(.swal2-center-end):not(.swal2-center-left):not(.swal2-center-right):not(.swal2-bottom):not(.swal2-bottom-start):not(.swal2-bottom-end):not(.swal2-bottom-left):not(.swal2-bottom-right):not(.swal2-grow-fullscreen)>.swal2-modal{margin:auto}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-container .swal2-modal{margin:0!important}}.swal2-container.swal2-fade{transition:background-color .1s}.swal2-container.swal2-shown{background-color:rgba(0,0,0,.4)}.swal2-popup{display:none;position:relative;flex-direction:column;justify-content:center;width:32em;max-width:100%;padding:1.25em;border-radius:.3125em;background:#fff;font-family:inherit;font-size:1rem;box-sizing:border-box}.swal2-popup:focus{outline:0}.swal2-popup.swal2-loading{overflow-y:hidden}.swal2-popup .swal2-header{display:flex;flex-direction:column;align-items:center}.swal2-popup .swal2-title{display:block;position:relative;max-width:100%;margin:0 0 .4em;padding:0;color:#595959;font-size:1.875em;font-weight:600;text-align:center;text-transform:none;word-wrap:break-word}.swal2-popup .swal2-actions{flex-wrap:wrap;align-items:center;justify-content:center;margin:1.25em auto 0;z-index:1}.swal2-popup .swal2-actions:not(.swal2-loading) .swal2-styled[disabled]{opacity:.4}.swal2-popup .swal2-actions:not(.swal2-loading) .swal2-styled:hover{background-image:linear-gradient(rgba(0,0,0,.1),rgba(0,0,0,.1))}.swal2-popup .swal2-actions:not(.swal2-loading) .swal2-styled:active{background-image:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,.2))}.swal2-popup .swal2-actions.swal2-loading .swal2-styled.swal2-confirm{width:2.5em;height:2.5em;margin:.46875em;padding:0;border:.25em solid transparent;border-radius:100%;border-color:transparent;background-color:transparent!important;color:transparent;cursor:default;box-sizing:border-box;-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.swal2-popup .swal2-actions.swal2-loading .swal2-styled.swal2-cancel{margin-right:30px;margin-left:30px}.swal2-popup .swal2-actions.swal2-loading :not(.swal2-styled).swal2-confirm::after{display:inline-block;width:15px;height:15px;margin-left:5px;border:3px solid #999;border-radius:50%;border-right-color:transparent;box-shadow:1px 1px 1px #fff;content:'';-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal}.swal2-popup .swal2-styled{margin:.3125em;padding:.625em 2em;font-weight:500;box-shadow:none}.swal2-popup .swal2-styled:not([disabled]){cursor:pointer}.swal2-popup .swal2-styled.swal2-confirm{border:0;border-radius:.25em;background:initial;background-color:#3085d6;color:#fff;font-size:1.0625em}.swal2-popup .swal2-styled.swal2-cancel{border:0;border-radius:.25em;background:initial;background-color:#aaa;color:#fff;font-size:1.0625em}.swal2-popup .swal2-styled:focus{outline:0;box-shadow:0 0 0 2px #fff,0 0 0 4px rgba(50,100,150,.4)}.swal2-popup .swal2-styled::-moz-focus-inner{border:0}.swal2-popup .swal2-footer{justify-content:center;margin:1.25em 0 0;padding:1em 0 0;border-top:1px solid #eee;color:#545454;font-size:1em}.swal2-popup .swal2-image{max-width:100%;margin:1.25em auto}.swal2-popup .swal2-close{position:absolute;top:0;right:0;justify-content:center;width:1.2em;height:1.2em;padding:0;transition:color .1s ease-out;border:none;border-radius:0;outline:initial;background:0 0;color:#ccc;font-family:serif;font-size:2.5em;line-height:1.2;cursor:pointer;overflow:hidden}.swal2-popup .swal2-close:hover{-webkit-transform:none;transform:none;color:#f27474}.swal2-popup>.swal2-checkbox,.swal2-popup>.swal2-file,.swal2-popup>.swal2-input,.swal2-popup>.swal2-radio,.swal2-popup>.swal2-select,.swal2-popup>.swal2-textarea{display:none}.swal2-popup .swal2-content{justify-content:center;margin:0;padding:0;color:#545454;font-size:1.125em;font-weight:300;line-height:normal;z-index:1;word-wrap:break-word}.swal2-popup #swal2-content{text-align:center}.swal2-popup .swal2-checkbox,.swal2-popup .swal2-file,.swal2-popup .swal2-input,.swal2-popup .swal2-radio,.swal2-popup .swal2-select,.swal2-popup .swal2-textarea{margin:1em auto}.swal2-popup .swal2-file,.swal2-popup .swal2-input,.swal2-popup .swal2-textarea{width:100%;transition:border-color .3s,box-shadow .3s;border:1px solid #d9d9d9;border-radius:.1875em;background:inherit;font-size:1.125em;box-shadow:inset 0 1px 1px rgba(0,0,0,.06);box-sizing:border-box}.swal2-popup .swal2-file.swal2-inputerror,.swal2-popup .swal2-input.swal2-inputerror,.swal2-popup .swal2-textarea.swal2-inputerror{border-color:#f27474!important;box-shadow:0 0 2px #f27474!important}.swal2-popup .swal2-file:focus,.swal2-popup .swal2-input:focus,.swal2-popup .swal2-textarea:focus{border:1px solid #b4dbed;outline:0;box-shadow:0 0 3px #c4e6f5}.swal2-popup .swal2-file::-webkit-input-placeholder,.swal2-popup .swal2-input::-webkit-input-placeholder,.swal2-popup .swal2-textarea::-webkit-input-placeholder{color:#ccc}.swal2-popup .swal2-file:-ms-input-placeholder,.swal2-popup .swal2-input:-ms-input-placeholder,.swal2-popup .swal2-textarea:-ms-input-placeholder{color:#ccc}.swal2-popup .swal2-file::-ms-input-placeholder,.swal2-popup .swal2-input::-ms-input-placeholder,.swal2-popup .swal2-textarea::-ms-input-placeholder{color:#ccc}.swal2-popup .swal2-file::placeholder,.swal2-popup .swal2-input::placeholder,.swal2-popup .swal2-textarea::placeholder{color:#ccc}.swal2-popup .swal2-range{margin:1em auto;background:inherit}.swal2-popup .swal2-range input{width:80%}.swal2-popup .swal2-range output{width:20%;font-weight:600;text-align:center}.swal2-popup .swal2-range input,.swal2-popup .swal2-range output{height:2.625em;padding:0;font-size:1.125em;line-height:2.625em}.swal2-popup .swal2-input{height:2.625em;padding:0 .75em}.swal2-popup .swal2-input[type=number]{max-width:10em}.swal2-popup .swal2-file{background:inherit;font-size:1.125em}.swal2-popup .swal2-textarea{height:6.75em;padding:.75em}.swal2-popup .swal2-select{min-width:50%;max-width:100%;padding:.375em .625em;background:inherit;color:#545454;font-size:1.125em}.swal2-popup .swal2-checkbox,.swal2-popup .swal2-radio{align-items:center;justify-content:center;background:inherit}.swal2-popup .swal2-checkbox label,.swal2-popup .swal2-radio label{margin:0 .6em;font-size:1.125em}.swal2-popup .swal2-checkbox input,.swal2-popup .swal2-radio input{margin:0 .4em}.swal2-popup .swal2-validation-message{display:none;align-items:center;justify-content:center;padding:.625em;background:#f0f0f0;color:#666;font-size:1em;font-weight:300;overflow:hidden}.swal2-popup .swal2-validation-message::before{display:inline-block;width:1.5em;min-width:1.5em;height:1.5em;margin:0 .625em;border-radius:50%;background-color:#f27474;color:#fff;font-weight:600;line-height:1.5em;text-align:center;content:'!';zoom:normal}@supports (-ms-accelerator:true){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@-moz-document url-prefix(){.swal2-close:focus{outline:2px solid rgba(50,100,150,.4)}}.swal2-icon{position:relative;justify-content:center;width:5em;height:5em;margin:1.25em auto 1.875em;border:.25em solid transparent;border-radius:50%;line-height:5em;cursor:default;box-sizing:content-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;zoom:normal}.swal2-icon-text{font-size:3.75em}.swal2-icon.swal2-error{border-color:#f27474}.swal2-icon.swal2-error .swal2-x-mark{position:relative;flex-grow:1}.swal2-icon.swal2-error [class^=swal2-x-mark-line]{display:block;position:absolute;top:2.3125em;width:2.9375em;height:.3125em;border-radius:.125em;background-color:#f27474}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:1.0625em;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:1em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.swal2-icon.swal2-warning{border-color:#facea8;color:#f8bb86}.swal2-icon.swal2-info{border-color:#9de0f6;color:#3fc3ee}.swal2-icon.swal2-question{border-color:#c9dae1;color:#87adbd}.swal2-icon.swal2-success{border-color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-circular-line]{position:absolute;width:3.75em;height:7.5em;-webkit-transform:rotate(45deg);transform:rotate(45deg);border-radius:50%}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.4375em;left:-2.0635em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:3.75em 3.75em;transform-origin:3.75em 3.75em;border-radius:7.5em 0 0 7.5em}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.6875em;left:1.875em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:0 3.75em;transform-origin:0 3.75em;border-radius:0 7.5em 7.5em 0}.swal2-icon.swal2-success .swal2-success-ring{position:absolute;top:-.25em;left:-.25em;width:100%;height:100%;border:.25em solid rgba(165,220,134,.3);border-radius:50%;z-index:2;box-sizing:content-box}.swal2-icon.swal2-success .swal2-success-fix{position:absolute;top:.5em;left:1.625em;width:.4375em;height:5.625em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);z-index:1}.swal2-icon.swal2-success [class^=swal2-success-line]{display:block;position:absolute;height:.3125em;border-radius:.125em;background-color:#a5dc86;z-index:2}.swal2-icon.swal2-success [class^=swal2-success-line][class$=tip]{top:2.875em;left:.875em;width:1.5625em;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.swal2-icon.swal2-success [class^=swal2-success-line][class$=long]{top:2.375em;right:.5em;width:2.9375em;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.swal2-progress-steps{align-items:center;margin:0 0 1.25em;padding:0;background:inherit;font-weight:600}.swal2-progress-steps li{display:inline-block;position:relative}.swal2-progress-steps .swal2-progress-step{width:2em;height:2em;border-radius:2em;background:#3085d6;color:#fff;line-height:2em;text-align:center;z-index:20}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step{background:#3085d6}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step{background:#add8e6;color:#fff}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step-line{background:#add8e6}.swal2-progress-steps .swal2-progress-step-line{width:2.5em;height:.4em;margin:0 -1px;background:#3085d6;z-index:10}[class^=swal2]{-webkit-tap-highlight-color:transparent}.swal2-show{-webkit-animation:swal2-show .3s;animation:swal2-show .3s}.swal2-show.swal2-noanimation{-webkit-animation:none;animation:none}.swal2-hide{-webkit-animation:swal2-hide .15s forwards;animation:swal2-hide .15s forwards}.swal2-hide.swal2-noanimation{-webkit-animation:none;animation:none}.swal2-rtl .swal2-close{right:auto;left:0}.swal2-animate-success-icon .swal2-success-line-tip{-webkit-animation:swal2-animate-success-line-tip .75s;animation:swal2-animate-success-line-tip .75s}.swal2-animate-success-icon .swal2-success-line-long{-webkit-animation:swal2-animate-success-line-long .75s;animation:swal2-animate-success-line-long .75s}.swal2-animate-success-icon .swal2-success-circular-line-right{-webkit-animation:swal2-rotate-success-circular-line 4.25s ease-in;animation:swal2-rotate-success-circular-line 4.25s ease-in}.swal2-animate-error-icon{-webkit-animation:swal2-animate-error-icon .5s;animation:swal2-animate-error-icon .5s}.swal2-animate-error-icon .swal2-x-mark{-webkit-animation:swal2-animate-error-x-mark .5s;animation:swal2-animate-error-x-mark .5s}@-webkit-keyframes swal2-rotate-loading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes swal2-rotate-loading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@media print{body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow-y:scroll!important}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown)>[aria-hidden=true]{display:none}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) .swal2-container{position:initial!important}}");
\ No newline at end of file
diff --git a/js/veeam.js b/js/veeam.js
new file mode 100644
index 0000000..526d4b3
--- /dev/null
+++ b/js/veeam.js
@@ -0,0 +1,29 @@
+$(document).ready(function(e) {
+ /* Logout option */
+ $('#logout').click(function(e) {
+ e.preventDefault();
+
+ const swalWithBootstrapButtons = Swal.mixin({
+ confirmButtonClass: 'btn btn-success btn-margin',
+ cancelButtonClass: 'btn btn-danger',
+ buttonsStyling: false,
+ })
+
+ swalWithBootstrapButtons.fire({
+ type: 'question',
+ title: 'Logout',
+ text: 'You are about to logout. Are you sure you want to continue?',
+ showCancelButton: true,
+ confirmButtonText: 'Yes',
+ cancelButtonText: 'No',
+ }).then((result) => {
+ if (result.value) {
+ $.post('index.php', {'logout' : true}, function(data) {
+ window.location.replace('index.php');
+ });
+ } else {
+ return;
+ }
+ })
+ });
+});
\ No newline at end of file
diff --git a/onedrive.php b/onedrive.php
new file mode 100644
index 0000000..6bab1b1
--- /dev/null
+++ b/onedrive.php
@@ -0,0 +1,1069 @@
+setToken($_SESSION['token']);
+ $veeam->refreshToken($_SESSION['refreshtoken']);
+}
+?>
+
+
+
+
+
+ Project Martini
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OneDrive
+
+ getOrganizationByID($oid);
+
+ $users = $veeam->getLicensedUsers($oid);
+ $repo = $veeam->getOrganizationRepository($oid);
+ $usersarray = array();
+
+ for ($i = 0; $i < count($users['results']); $i++) {
+ array_push($usersarray, array(
+ 'id' => $users['results'][$i]['id'],
+ 'isBackedUp' => $users['results'][$i]['isBackedUp'],
+ 'lastBackupDate' => $users['results'][$i]['lastBackupDate']
+ ));
+ }
+
+ if (count($users['results']) != '0') { /* Gather the backed up users from the repositories related to the organization */
+ $repousersarray = array(); /* Array used to sort the users in case of double data on the repositories */
+
+ for ($i = 0; $i < count($repo); $i++) {
+ $id = explode('/', $repo[$i]['_links']['backupRepository']['href']); /* Get the organization ID */
+ $repoid = end($id);
+
+ for ($j = 0; $j < count($users['results']); $j++) {
+ $combinedid = $users['results'][$j]['backedUpOrganizationId'] . $users['results'][$j]['id'];
+ $userdata = $veeam->getUserData($repoid, $combinedid);
+
+ /* Only store data when the OneDrive data is backed up */
+ if (!is_null($userdata) && $userdata['isOneDriveBackedUp']) {
+ array_push($repousersarray, array(
+ 'id' => $userdata['accountId'],
+ 'email' => $userdata['email'],
+ 'name' => $userdata['displayName'],
+ 'isOneDriveBackedUp' => $userdata['isOneDriveBackedUp']
+ ));
+ }
+ }
+ }
+
+ $usersort = array_values(array_column($repousersarray , null, 'name')); /* Sort the array and make sure every value is unique */
+ }
+
+ if (count($usersort) != '0') {
+ ?>
+
+
+ Explore last backup
+
+
+
+ Start Restore
+
+
+
+
The following is an overview on all backed up accounts and their objects within the organization.
+
+
+
+ Account
+ Objects in backup
+ Last backup
+
+
+
+ ';
+ echo '' . $usersort[$i]['name'] . ' ';
+ echo '';
+ if ($usersort[$i]['isOneDriveBackedUp']) {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+ echo ' ';
+ echo '' . date('d/m/Y H:i T', strtotime($usersarray[$licinfo]['lastBackupDate'])) . ' ';
+ echo '';
+ }
+ ?>
+
+
+ No users found for this organization.';
+ }
+ } else { /* No organization has been selected */
+ echo '
Select an organization to start a restore session.
';
+ }
+ } else { /* Restore session is running */
+ if (isset($uid) && !empty($uid)) {
+ $owner = $veeam->getOneDriveID($rid, $uid);
+ $folders = $veeam->getOneDriveTree($rid, $uid);
+ $documents = $veeam->getOneDriveTree($rid, $uid, 'documents');
+
+ if ((count($folders['results']) != '0') || (count($documents['results']) != '0')) {
+ ?>
+
+
+ Restore selected
+
+
+
+
+
+ -- Jump to folder --
+
+
+
+
+
+
+
+
+
+
+ No items available for this OneDrive account.';
+ }
+ } else { /* List all accounts */
+ ?>
+
+
+
+ Name
+ Options
+
+
+
+ $users['results'][$i]['name'], 'id' => $users['results'][$i]['id']));
+ }
+
+ uasort($onedrives, function($a, $b) {
+ return strcasecmp($a['name'], $b['name']);
+ });
+
+ foreach ($onedrives as $key => $value) {
+ ?>
+
+
+
+
+ Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sharepoint.php b/sharepoint.php
new file mode 100644
index 0000000..f913c02
--- /dev/null
+++ b/sharepoint.php
@@ -0,0 +1,1188 @@
+setToken($_SESSION['token']);
+ $veeam->refreshToken($_SESSION['refreshtoken']);
+}
+?>
+
+
+
+
+
+ Project Martini
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SharePoint
+
+ getOrganizationByID($oid);
+
+ $users = $veeam->getLicensedUsers($oid);
+ $repo = $veeam->getOrganizationRepository($oid);
+ $usersarray = array();
+
+ for ($i = 0; $i < count($users['results']); $i++) {
+ array_push($usersarray, array(
+ 'id' => $users['results'][$i]['id'],
+ 'isBackedUp' => $users['results'][$i]['isBackedUp'],
+ 'lastBackupDate' => $users['results'][$i]['lastBackupDate']
+ ));
+ }
+
+ if (count($users['results']) != '0') { /* Gather the backed up users from the repositories related to the organization */
+ $repousersarray = array(); /* Array used to sort the users in case of double data on the repositories */
+
+ for ($i = 0; $i < count($repo); $i++) {
+ $id = explode('/', $repo[$i]['_links']['backupRepository']['href']); /* Get the organization ID */
+ $repoid = end($id);
+
+ for ($j = 0; $j < count($users['results']); $j++) {
+ $combinedid = $users['results'][$j]['backedUpOrganizationId'] . $users['results'][$j]['id'];
+ $userdata = $veeam->getUserData($repoid, $combinedid);
+
+ /* Only store data when the SharePoint data is backed up */
+ if (!is_null($userdata) && $userdata['isPersonalSiteBackedUp']) {
+ array_push($repousersarray, array(
+ 'id' => $userdata['accountId'],
+ 'email' => $userdata['email'],
+ 'name' => $userdata['displayName'],
+ 'isPersonalSiteBackedUp' => $userdata['isPersonalSiteBackedUp']
+ ));
+ }
+ }
+ }
+
+ $usersort = array_values(array_column($repousersarray , null, 'name')); /* Sort the array and make sure every value is unique */
+ }
+
+ if (count($usersort) != '0') {
+ ?>
+
+
+ Explore last backup
+
+
+
+ Start Restore
+
+
+
+
The following is an overview on all backed up accounts and their objects within the organization.
+
+
+
+ Personal sites
+ Objects in backup
+ Last backup
+
+
+
+ ';
+ echo '' . $usersort[$i]['name'] . ' ';
+ echo '';
+ if ($usersort[$i]['isPersonalSiteBackedUp']) {
+ echo ' ';
+ } else {
+ echo ' ';
+ }
+ echo ' ';
+ echo '' . date('d/m/Y H:i T', strtotime($usersarray[$licinfo]['lastBackupDate'])) . ' ';
+ echo '';
+ }
+ ?>
+
+
+ No SharePoint sites found for this organization.';
+ }
+ } else { /* No organization has been selected */
+ echo '
Select an organization to start a restore session.
';
+ }
+ } else { /* Restore session is running */
+ if (isset($sid) && !empty($sid)) {
+ if (isset($_GET['cid'])) {
+ $cid = $_GET['cid'];
+ }
+
+ $type = $_GET['type'];
+ $name = $veeam->getSharePointSiteName($rid, $sid);
+
+ if (isset($cid) && !empty($cid)) {
+ $folders = $veeam->getSharePointTree($rid, $sid, $cid);
+
+ if (strcmp($type, 'list') === 0) { /* Lists have folders and items */
+ $items = $veeam->getSharePointTree($rid, $sid, $cid, 'Items');
+ $list = $veeam->getSharePointListName($rid, $sid, $cid, 'Lists');
+ } else { /* Libraries have folders and documents */
+ $documents = $veeam->getSharePointTree($rid, $sid, $cid, 'Documents');
+ $list = $veeam->getSharePointListName($rid, $sid, $cid, 'Libraries');
+ }
+ ?>
+
+ No items available in this list.';
+ } elseif (strcmp($type, 'library') === 0 && (count($folders['results']) == '0' && count($documents['results']) == '0')) {
+ echo '
No items available in this library.
';
+ } else {
+ ?>
+
+
+ Restore selected
+
+
+
+
+
+ -- Jump to folder --
+
+
+
+
+
+
+
+
+
+
+
+
Select a library or list to view the specific content.
+
+
+
+
+ Site
+ Options
+
+
+
+ $sites['results'][$i]['name'], 'id' => $sites['results'][$i]['id']));
+ }
+
+ uasort($siteslist, function($a, $b) {
+ return strcasecmp($a['name'], $b['name']);
+ });
+
+ foreach ($siteslist as $key => $value) {
+ ?>
+
+
+
+
+ Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapi/api.php b/webapi/api.php
new file mode 100644
index 0000000..b1dbaa8
--- /dev/null
+++ b/webapi/api.php
@@ -0,0 +1,680 @@
+username) && isset($json->password)) {
+ $res = authenticateWithToken($json->username, $json->password);
+ if ($res->auth == 1 && $res->token->token != '') {
+ $t = $res->token->token;
+
+ header("X-Authentication: $t");
+
+ $returnobject->token = $t;
+ $returnobject->renew = $res->token->renew;
+ $returnobject->lifetime = $res->token->lifetime;
+ $date = new DateTime();
+ $returnobject->now = $date->getTimestamp();
+ $exec404 = 0;
+
+ print json_encode($returnobject);
+ } else {
+ http_response_code(401);
+ }
+ }
+
+ break;
+
+ case "heartbeat":
+ $result = 0;
+ $returnobject = new stdClass();
+ $returnobject->status = "unauthenticated";
+
+ if ($tokentype == "bearer" && $tokenextract != '') {
+ $check = verifyToken($tokenextract);
+ $result = $check->getStatus();
+
+ if ($result == 1) {
+ $returnobject->status = "ok";
+ $exec404 = 0;
+ } else if ($result == 2) {
+ $returnobject->status = "expired";
+ $exec404 = 0;
+ }
+ }
+
+ if ($exec404 == 0) {
+ print json_encode($returnobject);
+ }
+
+ break;
+
+ case "renew":
+ if ($tokentype == "bearer" && $tokenextract != '') {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+ $returnobject = new stdClass();
+
+ if (isset($json->renew)) {
+ $res = renewToken($tokenextract, $json->renew);
+ $status = $res->getStatus();
+
+ if($status == 1) {
+ $t = $res->token;
+
+ header('X-Authentication: ' . $t);
+
+ $returnobject->status = 'ok';
+ $returnobject->token = $t;
+ $returnobject->renew = $res->renew;
+ $returnobject->lifetime = $res->lifetime;
+ $date = new DateTime();
+ $returnobject->now = $date->getTimestamp();
+
+ print json_encode($returnobject);
+
+ $exec404 = 0;
+ } elseif ($status == 2) {
+ $returnobject->status = 'expired';
+ $returnobject->token = 0;
+ $returnobject->renew = 0;
+ $returnobject->lifetime = 0;
+
+ print json_encode($returnobject);
+
+ $exec404 = 0;
+ } else {
+ http_response_code(401);
+ }
+ } else {
+ http_response_code(401);
+ }
+ } else {
+ http_response_code(401);
+ }
+
+ break;
+ default:
+ http_response_code(401);
+ break;
+ }
+
+ if ($exec404) {
+ http_response_code(401);
+ }
+} else {
+ $result = 0;
+ if ($tokentype == 'bearer' && $tokenextract != '') {
+ $check = verifyToken($tokenextract);
+ $result = $check->getStatus();
+ }
+
+ if ($result == 1) {
+ switch ($getclass) {
+ case "tenant":
+ switch ($getaction) {
+ case "create":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+
+ (new JSONValidationRuleTenantName('name'))->Validate($json);
+ (new JSONValidationRuleEmail('email'))->Validate($json);
+
+
+ $mt = new MartiniTenant(-1, $json->name, $json->email, -1);
+ saveTenant($mt);
+
+ if ($mt->id != -1) {
+ $returnobject = new stdClass();
+ $returnobject->id = strval($mt->id);
+ $returnobject->name = strval($mt->name);
+ $returnobject->email = strval($mt->email);
+ $returnobject->password = strval($mt->password);
+ $returnobject->registered = strval($mt->registered);
+ } else {
+ throw new Exception('tenant id creation resulted in -1');
+ }
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (Throwable $ex) {
+ http_response_code(500);
+ $returnobject->status = 'Unexpected error, please check at server side logs.';
+ error_log($ex->getMessage());
+ }
+ print json_encode($returnobject);
+ break;
+
+ case "list":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+
+ try {
+ $returnobject = getTenants();
+ } catch (Throwable $ex) {
+ http_response_code(500);
+ $returnobject->status = 'Unexpected error, please check at server side logs.';
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+
+ case "delete":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger('id'))->Validate($json);
+
+ $id = deleteTenant($json->id);
+ $returnobject->status = 'deleted';
+ $returnobject->id = $id;
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (TenantNotFoundException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Tenant id {$ex->tenantid} unknown, please list and try an existing tenantid";
+ } catch (Throwable $ex) {
+ http_response_code(500);
+ $returnobject->status = 'Unexpected error, please check at server side logs.';
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+ default:
+ http_response_code(401);
+ break;
+ }
+
+ break;
+
+ case "instance":
+ switch ($getaction) {
+ case "list":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger("id"))->Validate($json);
+
+ $returnobject = getTenantAllInstancesRef($json->id);
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (TenantNotFoundException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Tenant id {$ex->tenantid} unknown, please list and try an existing tenantid";
+ } catch (Throwable $ex) {
+ http_response_code(500);
+ $returnobject->status = 'Unexpected error, please check at server side logs.';
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+
+ case "listorphans":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+
+ try {
+ $returnobject = getOrphanedInstances();
+ } catch (Throwable $ex) {
+ http_response_code(500);
+ $returnobject->status = 'Unexpected error, please check at server side logs.';
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+
+ case "delete":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger("id"))->Validate($json);
+
+ $id = deleteInstance($json->id);
+ $returnobject->status = "deleted";
+ $returnobject->id = $id;
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (TenantNotFoundException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Instnace id {$ex->tenantid} unknown, please list and try an existing tenantid";
+ } catch (Throwable $ex) {
+ http_response_code(500);
+ $returnobject->status = 'Unexpected error, please check at server side logs.';
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+
+ case "assign":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger("newtenantid"))->Validate($json);
+ (new JSONValidationRuleInteger("instanceid"))->Validate($json);
+
+ assignInstance($json->newtenantid, $json->instanceid);
+ $returnobject->status = "reassigned";
+ $returnobject->id = $tenantid;
+ $returnobject->subId = $instanceid;
+
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (TenantNotFoundException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Instnace id {$ex->tenantid} unknown, please list and try an existing tenantid";
+ } catch (Throwable $ex) {
+ http_response_code(500);
+ $returnobject->status = 'Unexpected error, please check at server side logs.';
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+
+ case "broker":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+ $returnobject->id = "-1";
+
+ try {
+ $readpost = file_get_contents('php://input');
+
+ $json = json_decode($readpost);
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger("id"))->Validate($json);
+
+ $clientip = $_SERVER['REMOTE_ADDR'];
+ if (isset($json->clientip) && $json->clientip != "") {
+ (new JSONValidationRuleFQDN("clientip"))->Validate($json);
+ $clientip = $json->clientip;
+ }
+ if (!isset($clientip) || $clientip == "") {
+ throw new Exception('Don\'t know who to broker for.');
+ }
+ $port = brokerTenantInstance($json->id,$clientip);
+ $returnobject->port = $port;
+ $returnobject->expectedclient = $clientip;
+ $returnobject->status = "mapped";
+ $returnobject->id = $json->id;
+
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}.";
+ } catch (InstanceNotFoundException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Instance id {$ex->instanceid} unknown, please list and try an existing instanceid.";
+ } catch (InstanceNoFreeBrokerSlotException $ex) {
+ http_response_code(500);
+ $returnobject->status = 'No free port slots to broker, please add more slots or wait until some expire.';
+ } catch (Throwable $ex) {
+ http_response_code(500);
+ $returnobject->status = 'Unexpected error, please check at server side logs.';
+ error_log($ex->getMessage());
+ }
+ print json_encode($returnobject);
+ break;
+
+ case "deploy":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger("id"))->Validate($json);
+ (new JSONValidationRuleGenericLabel("type"))->Validate($json);
+
+ $id = deployTenant($json->id,$json->type, $json->config);
+ $returnobject->status = "deployed";
+ $returnobject->id = $id;
+
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}.";
+ } catch(DeployMethodUnknownCloudException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Cloud Deployment Parameter: {$ex->getMessage()}.";
+ } catch (TenantNotFoundException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Tenantid not found id {$ex->tenantid} unknown, please list and try an existing tenantid.";
+ } catch (Throwable $ex) {
+ http_response_code(500);
+ $returnobject->status = 'Unexpected error, please check at server side logs.';
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+ case "create":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+
+ (new JSONValidationRuleTenantName("name"))->Validate($json);
+ (new JSONValidationRuleFQDN("hostname"))->Validate($json);
+ (new JSONValidationRuleInteger("port"))->Validate($json);
+ (new JSONValidationRuleGenericLabel("username"))->Validate($json);
+ (new JSONValidationRulePassword("password"))->Validate($json);
+
+ (new JSONValidationRuleInteger("tenant_id"))->Validate($json);
+ (new JSONValidationRuleGenericLabel("type"))->Validate($json);
+ (new JSONValidationRuleInteger("status"))->Validate($json);
+ (new JSONValidationRuleGenericLabel("location"))->Validate($json);
+
+ $miid = saveInstance($json->tenant_id, $json->name, "", $json->type, $json->status, $json->location);
+ updateInstance($miid, $json->hostname,$json->port, $json->username, $json->password);
+
+ if ($miid != -1) {
+ $returnobject->status = "created";
+ $returnobject->id = strval($miid);
+ } else {
+ throw new Exception("tenant id creation resulted in -1");
+ }
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (Throwable $ex) {
+ http_response_code(500);
+ $returnobject->status = 'Unexpected error, please check at server side logs.';
+ error_log($ex->getMessage());
+ }
+ print json_encode($returnobject);
+ break;
+
+ default:
+ http_response_code(401);
+ break;
+ }
+ break;
+
+ case "job":
+ switch ($getaction) {
+ case "list":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger("id"))->Validate($json);
+
+ $returnobject = getJobs($json->id);
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (Exception $ex) {
+ http_response_code(500);
+ $returnobject->data = "error listing job, internal credentials error might be broken";
+ error_log($ex->getMessage());
+ }
+ print json_encode($returnobject);
+
+ break;
+ case "start":
+ $returnobject = new stdClass();
+ $returnobject->status = 'internal error';
+
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger("id"))->Validate($json);
+ (new JSONValidationRuleHash("jobid"))->Validate($json);
+
+ startJob($json->id, $json->jobid);
+
+ $returnobject->id = $json->id;
+ $returnobject->jobid = $json->jobid;
+ $returnobject->status = "started";
+
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (TenantNotFoundException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Tenant id {$ex->tenantid} unknown, please list and try an existing tenantid";
+ } catch (Exception $ex) {
+ http_response_code(500);
+ $returnobject->data = "error starting job, internal credentials error might be broken";
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+
+ default:
+ $returnobject = new stdClass();
+ $returnobject->status = "unknown tenant action";
+ http_response_code(401);
+ print json_encode($returnobject);
+ break;
+ }
+ break;
+
+ case "brokerendpoint":
+ switch($getaction) {
+ case "add":
+ $returnobject = new stdClass();
+
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger("port"))->Validate($json);
+
+ $p = addbrokerendpoint($json->port);
+ $returnobject->status = "added";
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (BrokerEndpointException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Port already exists";
+ } catch (Exception $ex) {
+ http_response_code(500);
+ $returnobject->status = "error adding port";
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+
+ case "delete":
+ $returnobject = new stdClass();
+
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger("port"))->Validate($json);
+
+ $p = deletebrokerendpoint($json->port);
+ $returnobject->status = "deleted";
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (BrokerEndpointException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Port does not exists";
+ } catch (Exception $ex) {
+ http_response_code(500);
+ $returnobject->status = "error deleting port";
+ error_log($ex->getMessage());
+ }
+ print json_encode($returnobject);
+ break;
+
+ case "list":
+ $returnobject = new stdClass();
+
+ try {
+ $ps = getBrokerendpoints();
+ $returnobject->portlist = $ps;
+ } catch (Exception $ex) {
+ http_response_code(500);
+ $returnobject->status = "error deleting port";
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+
+ default:
+ $returnobject = new stdClass();
+ $returnobject->status = "unknown brokerendpoint action";
+ http_response_code(401);
+ print json_encode($returnobject);
+ break;
+ }
+
+ break;
+
+ case "license":
+ switch($getaction) {
+ case "listusers":
+ $returnobject = new stdClass();
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger("id"))->Validate($json);
+
+ $returnobject = getLicensedUsers($json->id);
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (Exception $ex) {
+ http_response_code(500);
+ $returnobject->status = "Unexpected internal error, please check error log on the server";
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+
+ case "listinformation":
+ $returnobject = new stdClass();
+ try {
+ $readpost = file_get_contents('php://input');
+ $json = json_decode($readpost);
+
+ if (!isset($json)) { throw new InvalidDataException('Could not parse JSON, make sure it is valid.'); }
+ (new JSONValidationRuleInteger("id"))->Validate($json);
+
+ $returnobject = getLicenseInformation($json->id);
+ } catch(InvalidDataException $ex) {
+ http_response_code(500);
+ $returnobject->status = "Invalid Data: {$ex->getMessage()}";
+ } catch (Exception $ex) {
+ http_response_code(500);
+ $returnobject->status = "Unexpected internal error, please check error log on the server";
+ error_log($ex->getMessage());
+ }
+
+ print json_encode($returnobject);
+ break;
+
+ default:
+ $returnobject = new stdClass();
+ $returnobject->status = "unknown license action";
+ http_response_code(401);
+ print json_encode($returnobject);
+ break;
+ }
+
+ break;
+
+ default:
+ http_response_code(401);
+ break;
+ }
+ } else {
+ http_response_code(401);
+ }
+}
+?>
\ No newline at end of file
diff --git a/webapi/jsonvalidate.php b/webapi/jsonvalidate.php
new file mode 100644
index 0000000..eafa101
--- /dev/null
+++ b/webapi/jsonvalidate.php
@@ -0,0 +1,120 @@
+name = $name;
+ }
+ public function Validate($json) {
+ }
+}
+class JSONValidationRuleInteger extends JSONValidationRule {
+ public function __construct($name) {
+ parent::__construct($name);
+ }
+
+ public function Validate($json) {
+ if (isset($json->{$this->name})) {
+ $val = $json->{$this->name};
+
+ if ($val != "") {
+ if (is_numeric($val)) {
+ $ival = (int)$val;
+ if("$ival" != "$val") {
+ throw new InvalidDataException("{$this->name} is set but not an integer $ival == $val");
+ }
+ } else {
+ throw new InvalidDataException("{$this->name} is set but not numeric");
+ }
+ } else {
+ throw new InvalidDataException("{$this->name} is set but is empty so not usable {$this->typename}");
+ }
+ } else {
+ throw new InvalidDataException("{$this->name} is not set (expecting integer)");
+ }
+ }
+}
+
+class JSONValidationRuleFloat extends JSONValidationRule {
+ public function __construct($name) {
+ parent::__construct($name);
+ }
+
+ public function Validate($json) {
+ if (isset($json->{$this->name})) {
+ $val = $json->{$this->name};
+
+ if ($val != "") {
+ if (!is_numeric($val)) {
+ throw new InvalidDataException("{$this->name} is set but not numeric");
+ }
+ } else {
+ throw new InvalidDataException("{$this->name} is set but is empty so not usable {$this->typename}");
+ }
+ } else {
+ throw new InvalidDataException("{$this->name} is not set (expecting float)");
+ }
+ }
+}
+
+class JSONValidationRuleRegex extends JSONValidationRule {
+ public $regexp;
+ public $typename;
+
+ public function __construct($name,$typename,$regexp) {
+ parent::__construct($name);
+ $this->typename = $typename;
+ $this->regexp = $regexp;
+ }
+
+ public function Validate($json) {
+ if (isset($json->{$this->name})) {
+
+ $val = $json->{$this->name};
+ if ($val != "") {
+ if (preg_match($this->regexp,$val) != 1) {
+ throw new InvalidDataException("{$this->name} is set but not a valid {$this->typename}");
+ }
+ } else {
+ throw new InvalidDataException("{$this->name} is set but is empty so not usable {$this->typename}");
+ }
+ } else {
+ throw new InvalidDataException("{$this->name} is not set (expecting {$this->typename})");
+ }
+ }
+}
+
+class JSONValidationRuleEmail extends JSONValidationRuleRegex {
+ public function __construct($name) {
+ parent::__construct($name,"email address","/^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/");
+ }
+}
+
+class JSONValidationRuleTenantName extends JSONValidationRuleRegex {
+ public function __construct($name) {
+ parent::__construct($name,"tenant name (only alpha,num,_,- and . allowed)","/^([a-zA-Z0-9_\-\.]+)$/");
+ }
+}
+class JSONValidationRuleGenericLabel extends JSONValidationRuleRegex {
+ public function __construct($name) {
+ parent::__construct($name,"label (only alpha,num,_,- and . allowed)","/^([a-zA-Z0-9_\-\.]+)$/");
+ }
+}
+
+class JSONValidationRulePassword extends JSONValidationRuleRegex {
+ public function __construct($name) {
+ parent::__construct($name,"password (quotes, backslashes and dollars not allowed)","/^([^\"\'\\\\$]+)$/");
+ }
+}
+class JSONValidationRuleFQDN extends JSONValidationRuleRegex {
+ public function __construct($name) {
+ parent::__construct($name,"fqdn (only alpha,num,_,- and . allowed)","/^([a-zA-Z0-9_\-\.]+)$/");
+ }
+}
+class JSONValidationRuleHash extends JSONValidationRuleRegex {
+ public function __construct($name) {
+ parent::__construct($name,"fqdn (only alpha,num and - allowed)","/^([a-zA-Z0-9\-]+)$/");
+ }
+}
+?>
\ No newline at end of file
diff --git a/webfonts/fa-brands-400.eot b/webfonts/fa-brands-400.eot
new file mode 100644
index 0000000..f7accfa
Binary files /dev/null and b/webfonts/fa-brands-400.eot differ
diff --git a/webfonts/fa-brands-400.svg b/webfonts/fa-brands-400.svg
new file mode 100644
index 0000000..c94b526
--- /dev/null
+++ b/webfonts/fa-brands-400.svg
@@ -0,0 +1,1148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webfonts/fa-brands-400.ttf b/webfonts/fa-brands-400.ttf
new file mode 100644
index 0000000..2ffa92d
Binary files /dev/null and b/webfonts/fa-brands-400.ttf differ
diff --git a/webfonts/fa-brands-400.woff b/webfonts/fa-brands-400.woff
new file mode 100644
index 0000000..34110b2
Binary files /dev/null and b/webfonts/fa-brands-400.woff differ
diff --git a/webfonts/fa-brands-400.woff2 b/webfonts/fa-brands-400.woff2
new file mode 100644
index 0000000..540dffd
Binary files /dev/null and b/webfonts/fa-brands-400.woff2 differ
diff --git a/webfonts/fa-regular-400.eot b/webfonts/fa-regular-400.eot
new file mode 100644
index 0000000..c6b1218
Binary files /dev/null and b/webfonts/fa-regular-400.eot differ
diff --git a/webfonts/fa-regular-400.svg b/webfonts/fa-regular-400.svg
new file mode 100644
index 0000000..43fa6b7
--- /dev/null
+++ b/webfonts/fa-regular-400.svg
@@ -0,0 +1,467 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webfonts/fa-regular-400.ttf b/webfonts/fa-regular-400.ttf
new file mode 100644
index 0000000..43406e8
Binary files /dev/null and b/webfonts/fa-regular-400.ttf differ
diff --git a/webfonts/fa-regular-400.woff b/webfonts/fa-regular-400.woff
new file mode 100644
index 0000000..d49d464
Binary files /dev/null and b/webfonts/fa-regular-400.woff differ
diff --git a/webfonts/fa-regular-400.woff2 b/webfonts/fa-regular-400.woff2
new file mode 100644
index 0000000..ecd00ed
Binary files /dev/null and b/webfonts/fa-regular-400.woff2 differ
diff --git a/webfonts/fa-solid-900.eot b/webfonts/fa-solid-900.eot
new file mode 100644
index 0000000..3968757
Binary files /dev/null and b/webfonts/fa-solid-900.eot differ
diff --git a/webfonts/fa-solid-900.svg b/webfonts/fa-solid-900.svg
new file mode 100644
index 0000000..10cd5b0
--- /dev/null
+++ b/webfonts/fa-solid-900.svg
@@ -0,0 +1,2312 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webfonts/fa-solid-900.ttf b/webfonts/fa-solid-900.ttf
new file mode 100644
index 0000000..a1da1bb
Binary files /dev/null and b/webfonts/fa-solid-900.ttf differ
diff --git a/webfonts/fa-solid-900.woff b/webfonts/fa-solid-900.woff
new file mode 100644
index 0000000..41cbd7d
Binary files /dev/null and b/webfonts/fa-solid-900.woff differ
diff --git a/webfonts/fa-solid-900.woff2 b/webfonts/fa-solid-900.woff2
new file mode 100644
index 0000000..b307c26
Binary files /dev/null and b/webfonts/fa-solid-900.woff2 differ