├── .gitignore ├── .vscode └── settings.json ├── docs ├── images │ ├── bandwidth.png │ ├── existing_vps.png │ ├── new_product.png │ ├── custom_fields.png │ ├── product_config.png │ ├── product_group.png │ ├── products_services.png │ ├── server_plan_ready.png │ ├── server_plan_config.png │ └── configurable_options_normal.png └── INSTALL.md ├── modules └── servers │ └── upCloudVps │ ├── templates │ ├── assets │ │ ├── img │ │ │ ├── vnc.png │ │ │ ├── boot.png │ │ │ ├── disks.png │ │ │ ├── editvm.png │ │ │ ├── graphs.png │ │ │ ├── reboot.png │ │ │ ├── stop.png │ │ │ ├── network.png │ │ │ ├── sort_asc.png │ │ │ ├── ajax-loader.gif │ │ │ ├── sort_both.png │ │ │ ├── sort_desc.png │ │ │ ├── vps-details.png │ │ │ ├── ajax-loader-small.gif │ │ │ ├── sort_asc_disabled.png │ │ │ └── sort_desc_disabled.png │ │ └── css │ │ │ ├── theme.css │ │ │ └── layout.css │ ├── error.tpl │ └── overview.tpl │ ├── cron │ └── bandwidth.php │ ├── lib │ ├── usageUpdate.php │ ├── Helper.php │ ├── clientManager.php │ ├── vmManager.php │ ├── ajaxAction.php │ ├── adminManager.php │ ├── upCloudVps.php │ └── configOptions.php │ ├── lang │ └── english.php │ └── upCloudVps.php ├── composer.json ├── package.json ├── .phpcs.xml.dist ├── .config └── lefthook.yaml ├── .github ├── dependabot.yml └── workflows │ ├── lint.yaml │ └── release.yaml ├── .releaserc.yaml ├── LICENSE ├── README.md └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /vendor/ 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.xml.dist": "xml" 4 | } 5 | } -------------------------------------------------------------------------------- /docs/images/bandwidth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/docs/images/bandwidth.png -------------------------------------------------------------------------------- /docs/images/existing_vps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/docs/images/existing_vps.png -------------------------------------------------------------------------------- /docs/images/new_product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/docs/images/new_product.png -------------------------------------------------------------------------------- /docs/images/custom_fields.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/docs/images/custom_fields.png -------------------------------------------------------------------------------- /docs/images/product_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/docs/images/product_config.png -------------------------------------------------------------------------------- /docs/images/product_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/docs/images/product_group.png -------------------------------------------------------------------------------- /docs/images/products_services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/docs/images/products_services.png -------------------------------------------------------------------------------- /docs/images/server_plan_ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/docs/images/server_plan_ready.png -------------------------------------------------------------------------------- /docs/images/server_plan_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/docs/images/server_plan_config.png -------------------------------------------------------------------------------- /docs/images/configurable_options_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/docs/images/configurable_options_normal.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/vnc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/vnc.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/boot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/boot.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/disks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/disks.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/editvm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/editvm.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/graphs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/graphs.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/reboot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/reboot.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/stop.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/network.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/sort_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/sort_asc.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/ajax-loader.gif -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/sort_both.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/sort_both.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/sort_desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/sort_desc.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/vps-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/vps-details.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/ajax-loader-small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/ajax-loader-small.gif -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/sort_asc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/sort_asc_disabled.png -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/img/sort_desc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UpCloudLtd/upcloud-whmcs-module/HEAD/modules/servers/upCloudVps/templates/assets/img/sort_desc_disabled.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "upcloudltd/upcloud-whmcs-module", 3 | "description": "UpCloud WHMCS module", 4 | "license": "MIT", 5 | "require-dev": { 6 | "squizlabs/php_codesniffer": "4.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "semantic-release": "24.2.9", 4 | "@semantic-release/git": "10.0.1", 5 | "semantic-release-replace-plugin": "1.2.7", 6 | "@hexonet/semantic-release-whmcs": "5.0.59" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/error.tpl: -------------------------------------------------------------------------------- 1 | {if $message} 2 |
3 | {$message} 4 |
5 | {else} 6 |
7 | Something went wrong. Please try again later. 8 |
9 | {/if} 10 | -------------------------------------------------------------------------------- /.phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | ^vendor/* 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.config/lefthook.yaml: -------------------------------------------------------------------------------- 1 | min_version: 1.11.13 2 | 3 | pre-commit: 4 | jobs: 5 | - name: phpcbf 6 | glob: 7 | - "*.php" 8 | exclude: 9 | - vendor/* 10 | run: | 11 | vendor/bin/phpcbf --runtime-set php_version 70200 --runtime-set ignore_non_auto_fixable_on_exit 1 {staged_files} 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: daily 11 | - package-ecosystem: npm 12 | directory: / 13 | schedule: 14 | interval: daily 15 | -------------------------------------------------------------------------------- /.releaserc.yaml: -------------------------------------------------------------------------------- 1 | release: 2 | branches: 3 | - main 4 | 5 | plugins: 6 | - "@semantic-release/commit-analyzer" 7 | - "@semantic-release/release-notes-generator" 8 | - - semantic-release-replace-plugin 9 | - replacements: 10 | - files: 11 | - modules/servers/upCloudVps/lib/upCloudVps.php 12 | from: "'MODULE_VERSION', '.+?'" 13 | to: "'MODULE_VERSION', '${nextRelease.version}'" 14 | results: 15 | - file: modules/servers/upCloudVps/lib/upCloudVps.php 16 | hasChanged: true 17 | numMatches: 1 18 | numReplacements: 1 19 | countMatches: true 20 | - - "@semantic-release/git" 21 | - assets: 22 | - modules/servers/upCloudVps/lib/upCloudVps.php 23 | message: "chore: release ${nextRelease.version} [skip ci]" 24 | - "@semantic-release/github" 25 | - "@hexonet/semantic-release-whmcs" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 UpCloud Oy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | - pull_request 5 | 6 | jobs: 7 | phpcbf: 8 | name: phpcbf 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 12 | - name: Get composer cache dir 13 | run: printf "dir=%s\n" "$(composer config cache-files-dir)" >>"$GITHUB_OUTPUT" 14 | id: get-composer-cache-dir 15 | - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 16 | with: 17 | # https://github.com/actions/cache/blob/main/examples.md#php---composer 18 | path: ${{ steps.get-composer-cache-dir.outputs.dir }} 19 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 20 | restore-keys: | 21 | ${{ runner.os }}-composer- 22 | - run: composer --ansi install 23 | - name: Run phpcbf 24 | run: | 25 | set -x +e 26 | rc=0 27 | vendor/bin/phpcbf --runtime-set php_version 70200 --runtime-set ignore_non_auto_fixable_on_exit 1 --colors .; ((rc+=$?)) 28 | git diff --color --exit-code; ((rc+=$?)) 29 | ((rc==0)) 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | FORCE_COLOR: 1 # semantic-release (and unknowns) 10 | NPM_CONFIG_COLOR: always # npm and friends 11 | 12 | jobs: 13 | release: 14 | name: Release 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 18 | with: 19 | fetch-depth: 0 20 | persist-credentials: false 21 | - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 22 | with: 23 | node-version: 22 24 | check-latest: true 25 | cache: npm 26 | - name: Configure puppeteer to cache in npm cache dir 27 | run: | 28 | printf "PUPPETEER_CACHE_DIR=%s\n" "$(npm config get cache)/puppeteer-cache" >>"$GITHUB_ENV" 29 | - name: Install dependencies 30 | run: npm ci 31 | - name: Release 32 | env: 33 | GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} 34 | WHMCSMP_LOGIN: ${{ secrets.WHMCSMP_LOGIN }} 35 | WHMCSMP_PASSWORD: ${{ secrets.WHMCSMP_PASSWORD }} 36 | WHMCSMP_PRODUCTID: "7770" 37 | WHMCSMP_MINVERSION: "8.10" 38 | run: npx semantic-release 39 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/cron/bandwidth.php: -------------------------------------------------------------------------------- 1 | join('tblproducts', 'tblproducts.id', '=', 'tblhosting.packageid') 13 | ->where('tblhosting.domainstatus', 'Active') 14 | ->where('tblproducts.servertype', 'upCloudVps')->get(['tblhosting.id', 'tblhosting.packageid']); 15 | 16 | foreach ($services as $service) { 17 | $serviceId = $service->id; 18 | $packageId = $service->packageid; 19 | 20 | $tblcustomfields = Capsule::table('tblcustomfields') 21 | ->where('tblcustomfields.relid', $packageId) 22 | ->where('tblcustomfields.fieldname', 'instanceId|instance Id')->value('id'); 23 | 24 | $instanceId = Capsule::table('tblcustomfieldsvalues') 25 | ->where('tblcustomfieldsvalues.relid', $serviceId) 26 | ->where('tblcustomfieldsvalues.fieldid', $tblcustomfields)->value('value'); 27 | 28 | $product = Capsule::table('tblproducts')->where('id', $packageId)->first(); 29 | $server = Capsule::table('tblservers') 30 | ->join('tblservergroupsrel', 'tblservergroupsrel.serverid', '=', 'tblservers.id') 31 | ->where('tblservergroupsrel.groupid', $product->servergroup) 32 | ->first(); 33 | 34 | $params = [ 35 | 'serverusername' => $server->username, 36 | 'serverpassword' => decrypt($server->password), 37 | ]; 38 | 39 | try { 40 | $manager = new upCloudVps($params); 41 | $details = $manager->GetServer($instanceId)['response']['server']; 42 | $Ipv4 = $manager->formatSizeBytestoMB($details['plan_ipv4_bytes']); 43 | $Ipv6 = $manager->formatSizeBytestoMB($details['plan_ipv6_bytes']); 44 | if ($Ipv4 != '' && $Ipv6 != '') { 45 | Capsule::table('mod_upCloudVps_bandwidth')->insert([ 46 | 'serviceId' => $serviceId, 47 | 'IPv4' => $Ipv4, 48 | 'IPv6' => $Ipv6 49 | ]); 50 | } 51 | } catch (\Exception $e) { 52 | echo $e->getMessage() . ' [' . $service->id . ']' . PHP_EOL; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UpCloud WHMCS Module 2 | 3 | This is the official WHMCS provisioning module for [UpCloud](https://upcloud.com/), allowing you to offer and manage UpCloud VPS services through your WHMCS installation. 4 | 5 | ## Features 6 | 7 | - Automated provisioning of UpCloud VPS servers 8 | - Server management capabilities (start, stop, reboot) 9 | - Client area interface for server management 10 | - Bandwidth monitoring and usage tracking 11 | - Reverse DNS management 12 | - Package/plan upgrades and downgrades 13 | - Support for UpCloud's API 14 | 15 | ## Requirements 16 | 17 | - WHMCS 8.10 or later 18 | - UpCloud API credentials 19 | 20 | ## Installation 21 | 22 | See [INSTALL.md](docs/INSTALL.md) for instructions. 23 | 24 | ## Client Area Features 25 | 26 | The module provides a comprehensive client area interface that allows your clients to: 27 | 28 | - View server details and specifications 29 | - Monitor bandwidth usage 30 | - Start, stop, and reboot their VPS 31 | - Manage reverse DNS settings 32 | - View server status and uptime 33 | 34 | ## Admin Features 35 | 36 | As an administrator, you can: 37 | 38 | - Provision new VPS servers automatically 39 | - Suspend, unsuspend, and terminate accounts 40 | - Change server packages/plans 41 | - View detailed server information 42 | - Manage reverse DNS settings 43 | 44 | ## Module Structure 45 | 46 | The module follows the standard WHMCS module structure: 47 | 48 | ``` 49 | modules/servers/upCloudVps/ 50 | ├── upCloudVps.php # Main module file with all WHMCS hook functions 51 | ├── templates/ # Client area templates 52 | │ ├── overview.tpl # Main client area template 53 | │ ├── error.tpl # Error template 54 | │ └── assets/ # CSS, JS, and image assets 55 | ├── lib/ # Module libraries and classes 56 | ├── cron/ # Cron job scripts for usage updates 57 | └── lang/ # Language files for internationalization 58 | ``` 59 | 60 | ## Contributing 61 | 62 | Contributions to the UpCloud WHMCS module are welcome. To contribute: 63 | 64 | 1. Fork the repository 65 | 2. Create a feature branch (`git checkout -b feature/amazing-feature`) 66 | 3. Commit your changes (`git commit`), prefer commit messages that adhere to [conventional commits](https://www.conventionalcommits.org) 67 | 4. Push the branch (`git push -u origin feature/amazing-feature`) 68 | 5. Open a Pull Request 69 | 70 | ## License 71 | 72 | This module is released under the MIT License. See the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /modules/servers/upCloudVps/lib/usageUpdate.php: -------------------------------------------------------------------------------- 1 | manager = new upCloudVps($params); 19 | $this->params = $params; 20 | } 21 | 22 | public function usage() 23 | { 24 | $services = Capsule::table('tblhosting') 25 | ->join('tblproducts', 'tblproducts.id', '=', 'tblhosting.packageid') 26 | ->where('tblhosting.domainstatus', 'Active') 27 | ->where('tblhosting.server', $this->params['serverid']) 28 | ->where('tblproducts.servertype', 'upCloudVps')->get(['tblhosting.id', 'tblhosting.packageid']); 29 | foreach ($services as $whmcsData) { 30 | $serviceId = $whmcsData->id; 31 | $packageId = $whmcsData->packageid; 32 | $tblcustomfields = Capsule::table('tblcustomfields') 33 | ->where('tblcustomfields.relid', $packageId) 34 | ->where('tblcustomfields.fieldname', 'instanceId|instance Id')->value('id'); 35 | $instanceId = Capsule::table('tblcustomfieldsvalues') 36 | ->where('tblcustomfieldsvalues.relid', $serviceId) 37 | ->where('tblcustomfieldsvalues.fieldid', $tblcustomfields)->value('value'); 38 | $details = $this->manager->GetServer($instanceId)['response']['server']; 39 | $Outgoing = $this->manager->formatSizeBytestoGB($details['plan_ipv4_bytes'] + $details['plan_ipv6_bytes']); 40 | foreach ($this->manager->Getplans()['response']['plans']['plan'] as $Plan) { 41 | if ($Plan['name'] == $details['plan'] and $Plan['memory_amount'] == $details['memory_amount']) { 42 | $TotalTraffic = $Plan['public_traffic_out'] * 1000; 43 | } 44 | } 45 | $Outgoing = $this->manager->formatSizeBytestoMB($details['plan_ipv4_bytes'] + $details['plan_ipv6_bytes']); 46 | Capsule::table('tblhosting') 47 | ->where('server', $this->params['serverid']) 48 | ->where('id', $serviceId) 49 | ->where('packageid', $packageId) 50 | ->update([ 51 | 'bwusage' => $Outgoing, 52 | 'bwlimit' => $TotalTraffic, 53 | 'lastupdate' => Capsule::raw('now()'), 54 | ]); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "aff5dc682fca5a9ed914c33e2f9d87af", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "squizlabs/php_codesniffer", 12 | "version": "4.0.0", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", 16 | "reference": "06113cfdaf117fc2165f9cd040bd0f17fcd5242d" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/06113cfdaf117fc2165f9cd040bd0f17fcd5242d", 21 | "reference": "06113cfdaf117fc2165f9cd040bd0f17fcd5242d", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "ext-simplexml": "*", 26 | "ext-tokenizer": "*", 27 | "ext-xmlwriter": "*", 28 | "php": ">=7.2.0" 29 | }, 30 | "require-dev": { 31 | "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" 32 | }, 33 | "bin": [ 34 | "bin/phpcbf", 35 | "bin/phpcs" 36 | ], 37 | "type": "library", 38 | "notification-url": "https://packagist.org/downloads/", 39 | "license": [ 40 | "BSD-3-Clause" 41 | ], 42 | "authors": [ 43 | { 44 | "name": "Greg Sherwood", 45 | "role": "Former lead" 46 | }, 47 | { 48 | "name": "Juliette Reinders Folmer", 49 | "role": "Current lead" 50 | }, 51 | { 52 | "name": "Contributors", 53 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" 54 | } 55 | ], 56 | "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", 57 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 58 | "keywords": [ 59 | "phpcs", 60 | "standards", 61 | "static analysis" 62 | ], 63 | "support": { 64 | "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", 65 | "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", 66 | "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 67 | "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" 68 | }, 69 | "funding": [ 70 | { 71 | "url": "https://github.com/PHPCSStandards", 72 | "type": "github" 73 | }, 74 | { 75 | "url": "https://github.com/jrfnl", 76 | "type": "github" 77 | }, 78 | { 79 | "url": "https://opencollective.com/php_codesniffer", 80 | "type": "open_collective" 81 | }, 82 | { 83 | "url": "https://thanks.dev/u/gh/phpcsstandards", 84 | "type": "thanks_dev" 85 | } 86 | ], 87 | "time": "2025-09-15T11:28:58+00:00" 88 | } 89 | ], 90 | "aliases": [], 91 | "minimum-stability": "stable", 92 | "stability-flags": {}, 93 | "prefer-stable": false, 94 | "prefer-lowest": false, 95 | "platform": {}, 96 | "platform-dev": {}, 97 | "plugin-api-version": "2.6.0" 98 | } 99 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/lib/Helper.php: -------------------------------------------------------------------------------- 1 | $action(); 44 | if ($details['response']['error']['error_message']) { 45 | $results['result'] = 'failure'; 46 | $results['message'] = $details['response']['error']['error_message']; 47 | } else { 48 | $results['message'] = (!empty($_LANG['ajax'][$action])) ? $_LANG['ajax'][$action] : $_LANG['ajax']['action']['success']; 49 | switch ($action) { 50 | case 'refreshServer': 51 | $results['data']['details']['status'] = $details['response']['server']['state']; 52 | $results['data']['details']['statusLang'] = $_LANG['status'][$details['response']['server']['state']]; 53 | 54 | break; 55 | case 'vncDetails': 56 | $results['vnchost'] = $details['vnchost']; 57 | $results['vncport'] = $details['vncport']; 58 | 59 | break; 60 | case 'getIpAddresses': 61 | $results = $details; 62 | 63 | break; 64 | case 'getBandwidth': 65 | $results = $details; 66 | 67 | break; 68 | } 69 | } 70 | } else { 71 | $results['result'] = 'failure'; 72 | $results['message'] = $_LANG['ajax']['action']['not_valid']; 73 | } 74 | 75 | echo json_encode($results); 76 | die; 77 | } catch (\Exception $e) { 78 | echo json_encode(['result' => 'failure', 'message' => $e->getMessage()]); 79 | die; 80 | } 81 | } 82 | 83 | public static function clientAreaPrimarySidebarHook(array $params) 84 | { 85 | add_hook('ClientAreaPrimarySidebar', 1, function (MenuItem $primarySidebar) use ($params) { 86 | 87 | $_LANG = Helper::getLang(); 88 | $panel = $primarySidebar->getChild('Service Details Overview'); 89 | if (is_a($panel, 'WHMCS\View\Menu\Item')) { 90 | $panel = $panel->getChild('Information'); 91 | if (is_a($panel, 'WHMCS\View\Menu\Item')) { 92 | $panel->setUri("clientarea.php?action=productdetails&id={$params['serviceid']}"); 93 | $panel->setAttributes([]); 94 | } 95 | } 96 | }); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/lib/clientManager.php: -------------------------------------------------------------------------------- 1 | manager = new upCloudVps($params); 18 | $this->params = $params; 19 | } 20 | 21 | public function getData($page) 22 | { 23 | try { 24 | $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); 25 | switch ($page) { 26 | case 'details': 27 | $details = $this->manager->GetServer($instanceId); 28 | if ($details['response_code'] == '200') { 29 | $details = $details['response']['server']; 30 | $totalStorage = 0; 31 | $memoryGb = $details['memory_amount'] / 1024; 32 | $templ = $details['storage_devices']['storage_device']; 33 | $zones = $this->manager->GetZones()['response']['zones']['zone']; 34 | foreach ($zones as $zone) { 35 | if ($zone['id'] == $details['zone']) { 36 | $details['zoneDescription'] = $zone['description']; 37 | break; 38 | } 39 | } 40 | 41 | foreach ($templ as $temp) { 42 | if ($temp['part_of_plan'] == 'yes' || $details['plan'] == 'custom') { 43 | $details['osname'] = $temp['storage_title']; 44 | $details['base_storage_size'] = $temp['storage_size']; 45 | break; 46 | } 47 | } 48 | 49 | $vnc = ($details['remote_access_enabled'] == 'yes') ? 'on' : 'off'; 50 | $remoteAccessHost = $details['remote_access_host']; 51 | if (!empty($remoteAccessHost)) { 52 | $resolvedHost = gethostbyname($remoteAccessHost); 53 | $resolvedHost = ($resolvedHost !== $remoteAccessHost) ? $resolvedHost : $remoteAccessHost; 54 | } else { 55 | $resolvedHost = null; 56 | } 57 | 58 | $vncIp = $resolvedHost; 59 | $data['timezones'] = $this->manager->getTimezones()['response']['timezones']['timezone']; 60 | $data['details'] = [ 61 | 'hostname' => $details['hostname'], 62 | 'ip' => $details->ip, 63 | 'uuid' => $details['uuid'], 64 | 'plan' => $details['plan'], 65 | 'template' => $details['osname'], 66 | 'diskSize' => $details['base_storage_size'], 67 | 'status' => $details['state'], 68 | 'location' => $details['zoneDescription'], 69 | 'vnc' => $vnc, 70 | 'vnc_host' => $vncIp, 71 | 'vnc_port' => $details['remote_access_port'], 72 | 'vnc_password' => $details['remote_access_password'], 73 | 'video_model' => $details['video_model'], 74 | 'nic_model' => $details['nic_model'], 75 | 'timezone' => $details['timezone'], 76 | 'boot_order' => $details['boot_order'], 77 | ]; 78 | if (!empty($details['ip_addresses'])) { 79 | foreach ($details['ip_addresses']['ip_address'] as $ip) { 80 | if ($ip['access'] == 'public' && $ip['family'] == 'IPv4') { 81 | $data['details']['ip'] = $ip['address']; 82 | } 83 | } 84 | } 85 | } 86 | 87 | break; 88 | } 89 | } catch (\Exception $e) { 90 | return []; 91 | } 92 | 93 | return $data; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/lang/english.php: -------------------------------------------------------------------------------- 1 | manager = new upCloudVps($params); 21 | $this->params = $params; 22 | $this->_LANG = Helper::getLang(); 23 | } 24 | 25 | public function stop() 26 | { 27 | $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); 28 | $getInstamceState = $this->manager->GetServer($instanceId)['response']; 29 | if ($getInstamceState['server']['state'] == 'started') { 30 | $stop = $this->manager->StopServer($instanceId); 31 | sleep(45); 32 | return isset($stop['response']['error']['error_message']) ? $stop['response']['error']['error_message'] : 'success'; 33 | } 34 | } 35 | 36 | public function start() 37 | { 38 | $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); 39 | $start = $this->manager->StartServer($instanceId); 40 | sleep(45); 41 | return isset($start['response']['server']['host']) ? 'success' : $start['response']['error']['error_message']; 42 | } 43 | 44 | public function reboot() 45 | { 46 | $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); 47 | $reboot = $this->manager->RestartServer($instanceId); 48 | return isset($reboot['response']['error']['error_message']) ? $reboot['response']['error']['error_message'] : 'success'; 49 | } 50 | 51 | public function terminate() 52 | { 53 | $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); 54 | $delete = ($this->params['configoptions']['backup'] == 'no') ? $this->manager->DeleteServernStorage($instanceId) : $this->manager->DeleteServernStorageBackup($instanceId); 55 | if ($delete['response_code'] == '204') { 56 | $this->params['model']->serviceProperties->save(['instanceId|instance Id' => '']); 57 | Capsule::table('mod_upCloudVps_bandwidth')->where('serviceId', '=', $this->params['serviceid'])->delete(); 58 | $message = 'success'; 59 | } else { 60 | $message = $delete['response']['error']['error_message']; 61 | } 62 | return $message; 63 | } 64 | 65 | public function create() 66 | { 67 | $zone = $this->params['configoptions']['location'] ?? $this->params['configoption1']; 68 | $Plan = $this->params['configoption2']; 69 | $OsUUID = $this->params['configoptions']['template'] ?? $this->params['configoption3']; 70 | $sshkey = $this->params['customfields']['ssh_key']; 71 | $user_data = $this->params['customfields']['userData']; 72 | $Hostname = !empty($this->params['domain']) ? $this->params['domain'] : 'client' . $this->params['serviceid'] . '.' . $_SERVER['SERVER_NAME']; 73 | $sshkey = empty($sshkey) ? 'na' : $sshkey; 74 | $user_data = empty($user_data) ? 'na' : $user_data; 75 | $backup = $this->params['configoptions']['backup']; 76 | $networking = 'ipv4only'; 77 | if ($Plan == 'custom') { 78 | $ram = $this->params['configoptions']['ram']; 79 | $vcpu = $this->params['configoptions']['vcpu']; 80 | $storage = $this->params['configoptions']['storage']; 81 | $actionResponse = $this->manager->CreateServer($zone, $Hostname, $Plan, $OsUUID, $sshkey, $user_data, $backup, $networking, $ram, $vcpu, $storage); 82 | } else { 83 | $actionResponse = $this->manager->CreateServer($zone, $Hostname, $Plan, $OsUUID, $sshkey, $user_data, $backup, $networking); 84 | } 85 | if ($actionResponse['response_code'] == '202') { 86 | foreach ($actionResponse['response']['server']['ip_addresses']['ip_address'] as $IPList) { 87 | if ($IPList['access'] == 'public') { 88 | if ($IPList['family'] == 'IPv4' && ($IPList['part_of_plan'] || $Plan == 'custom')) { 89 | $IPv4 = $IPList['address']; 90 | } elseif ($IPList['family'] == 'IPv6') { 91 | $IPv6 = $IPList['address']; 92 | } 93 | } 94 | } 95 | 96 | $postData = [ 97 | 'serviceid' => $this->params['serviceid'], 98 | 'serviceusername' => $actionResponse['response']['server']['username'], 99 | 'dedicatedip' => $IPv4, 100 | 'servicepassword' => $actionResponse['response']['server']['password'], 101 | 'assignedips' => $IPv6, 102 | 'domain' => $actionResponse['response']['server']['hostname'], 103 | ]; 104 | localAPI('UpdateClientProduct', $postData); 105 | $this->params['model']->serviceProperties->save(['instanceId|instance Id' => $actionResponse['response']['server']['uuid']]); 106 | $message = 'success'; 107 | } else { 108 | $message = $actionResponse['response']['error']['error_message']; 109 | } 110 | return $message; 111 | } 112 | 113 | public function rdns() 114 | { 115 | $ip = $this->params['ip']; 116 | $DNSValue = $this->params['rdns']; 117 | $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); 118 | $data = $this->manager->ModifyIPaddress($instanceId, $ip, $DNSValue); 119 | return $data['response_code'] == '202' ? 'success' : $data['response']['error']['error_message']; 120 | } 121 | 122 | public function upgradePlan() 123 | { 124 | $Plan = $this->params['configoption2']; 125 | if ($Plan == 'custom') { 126 | return []; 127 | } else { 128 | $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); 129 | $data = $this->manager->ModifyServer($instanceId, $Plan); 130 | return isset($data['response']['error']['error_message']) ? $data['response']['error']['error_message'] : 'success'; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/lib/ajaxAction.php: -------------------------------------------------------------------------------- 1 | manager = new upCloudVps($params); 20 | $this->instanceId = $params['model']->serviceProperties->get('instanceId|instance Id'); 21 | $this->serviceid = $params['serviceid']; 22 | } 23 | 24 | public function refreshServer() 25 | { 26 | return $this->manager->GetServer($this->instanceId); 27 | } 28 | 29 | public function StartServer() 30 | { 31 | return $this->manager->StartServer($this->instanceId); 32 | } 33 | 34 | public function RestartServer() 35 | { 36 | return $this->manager->RestartServer($this->instanceId); 37 | } 38 | 39 | public function StopServer() 40 | { 41 | return $this->manager->StopServer($this->instanceId); 42 | } 43 | 44 | public function saveVNCConfiguration() 45 | { 46 | return $this->manager->vncPasswordUpdate($this->instanceId, filter_input(INPUT_POST, 'vnc_password', FILTER_SANITIZE_STRING)); 47 | } 48 | 49 | public function changeVNCStatus() 50 | { 51 | $type = filter_input(INPUT_POST, 'vnc', FILTER_SANITIZE_STRING); 52 | $type = ($type == 'on') ? 'yes' : 'no'; 53 | return $this->manager->vncEnableDisable($this->instanceId, $type); 54 | } 55 | 56 | public function vncDetails() 57 | { 58 | $details = $this->manager->GetServer($this->instanceId); 59 | if ($details['response']['error']['error_message']) { 60 | return $details['response']['error']['error_message']; 61 | } else { 62 | $remoteAccessHost = $details['response']['server']['remote_access_host']; 63 | if (!empty($remoteAccessHost)) { 64 | $resolvedHost = gethostbyname($remoteAccessHost); 65 | $resolvedHost = ($resolvedHost !== $remoteAccessHost) ? $resolvedHost : $remoteAccessHost; 66 | } else { 67 | $resolvedHost = null; 68 | } 69 | $results['vnchost'] = $resolvedHost; 70 | $results['vncport'] = $details['response']['server']['remote_access_port']; 71 | return $results; 72 | } 73 | } 74 | 75 | public function getIpAddresses() 76 | { 77 | $details = $this->manager->GetServer($this->instanceId); 78 | $ips = $details['response']['server']['ip_addresses']['ip_address']; 79 | foreach ($ips as $ip) { 80 | $ReverseDNSValue = $this->manager->GetIPaddress($ip['address'])['response']['ip_address']['ptr_record']; 81 | if (strpos($ReverseDNSValue, 'upcloud') !== false) { 82 | $this->manager->ModifyIPaddress($this->instanceId, $ip['address'], 'client.' . $_SERVER['SERVER_NAME'] . '.host'); 83 | } 84 | $btn = ($ip['access'] == 'utility') ? '' : ''; 85 | $output[] = [ 86 | ucfirst($ip['access']) . ' ' . $ip['family'], 87 | $ip['address'], 88 | $ReverseDNSValue, 89 | $btn, 90 | ]; 91 | } 92 | 93 | $ips['data'] = (!empty($output)) ? $output : []; 94 | return $ips; 95 | } 96 | 97 | public function editIp() 98 | { 99 | return $this->manager->ModifyIPaddress($this->instanceId, filter_input(INPUT_POST, 'ip', FILTER_SANITIZE_STRING), filter_input(INPUT_POST, 'ptr', FILTER_SANITIZE_STRING)); 100 | } 101 | 102 | public function saveServerConfiguration() 103 | { 104 | $serverConfig = [ 105 | 'server' => [ 106 | 'hostname' => filter_input(INPUT_POST, 'hostname', FILTER_SANITIZE_STRING), 107 | 'boot_order' => filter_input(INPUT_POST, 'bootOrder', FILTER_SANITIZE_STRING), 108 | 'video_model' => filter_input(INPUT_POST, 'displayAdapter', FILTER_SANITIZE_STRING), 109 | 'nic_model' => filter_input(INPUT_POST, 'networkAdapter', FILTER_SANITIZE_STRING), 110 | 'timezone' => filter_input(INPUT_POST, 'timezone', FILTER_SANITIZE_STRING), 111 | ], 112 | ]; 113 | return $this->manager->modifyVPS($this->instanceId, $serverConfig); 114 | } 115 | 116 | public function getBandwidth() 117 | { 118 | $data = Capsule::table('mod_upCloudVps_bandwidth'); 119 | if (!empty(filter_input(INPUT_POST, 'time', FILTER_SANITIZE_STRING))) { 120 | switch (filter_input(INPUT_POST, 'time', FILTER_SANITIZE_STRING)) { 121 | case '24 Hours': 122 | $data->whereRaw('created_at >= NOW() - INTERVAL 1 DAY') 123 | ->where('serviceId', $this->serviceid) 124 | ->groupBy(Capsule::raw('day(created_at),hour(created_at),from_unixtime(FLOOR(UNIX_TIMESTAMP(created_at)/(15*60))*(15*60)) ')); 125 | 126 | break; 127 | case 'Week': 128 | $data->whereRaw('created_at >= NOW() - INTERVAL 1 WEEK') 129 | ->where('serviceId', $this->serviceid) 130 | ->groupBy(Capsule::raw('week(created_at),day(created_at), hour(created_at), from_unixtime(FLOOR(UNIX_TIMESTAMP(created_at)/(60*60))*(60*60))')); 131 | 132 | break; 133 | case 'Month': 134 | $data->whereRaw('created_at >= NOW() - INTERVAL 1 MONTH') 135 | ->where('serviceId', $this->serviceid) 136 | ->groupBy(Capsule::raw('month(created_at),day(created_at)')); 137 | 138 | break; 139 | case 'Year': 140 | $data->whereRaw('created_at >= NOW() - INTERVAL 1 YEAR') 141 | ->where('serviceId', $this->serviceid) 142 | ->groupBy(Capsule::raw('year(created_at),month(created_at),week(created_at)')); 143 | 144 | break; 145 | default: 146 | $data->whereRaw('created_at >= NOW() - INTERVAL 1 DAY') 147 | ->where('serviceId', $this->serviceid) 148 | ->groupBy(Capsule::raw('day(created_at),hour(created_at),from_unixtime(FLOOR(UNIX_TIMESTAMP(created_at)/(15*60))*(15*60))')); 149 | } 150 | } 151 | 152 | $data = $data->get(); 153 | $output = []; 154 | foreach ($data as $index => $dat) { 155 | if ($index >= 0) { 156 | $output['IPv4'][] = ($data[$index]->IPv4 - $data[($index - 1)]->IPv4); 157 | $output['IPv6'][] = ($data[$index]->IPv6 - $data[($index - 1)]->IPv6); 158 | $output['labels'][] = $dat->created_at; 159 | } 160 | } 161 | 162 | return ['data' => $output]; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/upCloudVps.php: -------------------------------------------------------------------------------- 1 | 'UpCloud VPS', 22 | 'APIVersion' => '1.3', 23 | 'ServiceSingleSignOnLabel' => 'Login to Panel as User', 24 | 'RequiresServer' => true, 25 | ]; 26 | } 27 | 28 | function upCloudVps_ConfigOptions(array $params) 29 | { 30 | if (!Capsule::schema()->hasTable('mod_upCloudVps_bandwidth')) { 31 | Capsule::schema()->create('mod_upCloudVps_bandwidth', function ($table) { 32 | 33 | $table->integer('serviceId'); 34 | $table->string('IPv4'); 35 | $table->string('IPv6'); 36 | $table->timestamp('created_at')->default(Capsule::raw('CURRENT_TIMESTAMP')); 37 | }); 38 | } 39 | 40 | 41 | $server = Capsule::table('tblservers') 42 | ->join('tblservergroupsrel', 'tblservergroupsrel.serverid', '=', 'tblservers.id') 43 | ->where('tblservergroupsrel.groupid', App::getFromRequest('servergroup')) 44 | ->where('tblservers.disabled', '0') 45 | ->first(); 46 | $params['serverusername'] = $server->username; 47 | $params['serverpassword'] = decrypt($server->password); 48 | $manager = new configOptions($params); 49 | return $manager->configs(); 50 | } 51 | 52 | function upCloudVps_TestConnection(array $params) 53 | { 54 | try { 55 | $manager = new upCloudVps($params); 56 | $account = $manager->GetAccountInfo(); 57 | if ($account['response_code'] == '200') { 58 | $success = true; 59 | } else { 60 | $errorMsg = 'Invalid Credentials'; 61 | } 62 | } catch (\Exception $e) { 63 | $success = false; 64 | $errorMsg = $e->getMessage(); 65 | } 66 | return ['success' => $success, 'error' => $errorMsg]; 67 | } 68 | 69 | function upCloudVps_CreateAccount(array $params) 70 | { 71 | if ($params['status'] != 'Pending' && $params['status'] != 'Terminated') { 72 | return 'Cannot create service.'; 73 | } 74 | try { 75 | $vmManager = new vmManager($params); 76 | return $vmManager->create(); 77 | } catch (\Exception $e) { 78 | return $e->getMessage(); 79 | } 80 | } 81 | 82 | function upCloudVps_TerminateAccount(array $params) 83 | { 84 | if ($params['status'] != 'Active' && $params['status'] != 'Suspended') { 85 | return 'Cannot terminate service'; 86 | } 87 | try { 88 | $vmManager = new vmManager($params); 89 | return $vmManager->terminate(); 90 | } catch (\Exception $e) { 91 | return $e->getMessage(); 92 | } 93 | } 94 | 95 | function upCloudVps_SuspendAccount(array $params) 96 | { 97 | if ($params['status'] == 'Terminated') { 98 | return 'Cannot suspend terminated service'; 99 | } 100 | try { 101 | $vmManager = new vmManager($params); 102 | return $vmManager->stop(); 103 | } catch (\Exception $e) { 104 | return $e->getMessage(); 105 | } 106 | } 107 | 108 | function upCloudVps_UnsuspendAccount(array $params) 109 | { 110 | if ($params['status'] == 'Terminated') { 111 | return 'Cannot unsuspend terminated service'; 112 | } 113 | try { 114 | $vmManager = new vmManager($params); 115 | return $vmManager->start(); 116 | } catch (\Exception $e) { 117 | return $e->getMessage(); 118 | } 119 | } 120 | 121 | function upCloudVps_StopVPS(array $params) 122 | { 123 | try { 124 | $vmManager = new vmManager($params); 125 | return $vmManager->stop(); 126 | } catch (\Exception $e) { 127 | return $e->getMessage(); 128 | } 129 | } 130 | 131 | function upCloudVps_StartVPS(array $params) 132 | { 133 | try { 134 | $vmManager = new vmManager($params); 135 | return $vmManager->start(); 136 | } catch (\Exception $e) { 137 | return $e->getMessage(); 138 | } 139 | } 140 | 141 | function upCloudVps_RebootVPS(array $params) 142 | { 143 | try { 144 | $vmManager = new vmManager($params); 145 | return $vmManager->reboot(); 146 | } catch (\Exception $e) { 147 | return $e->getMessage(); 148 | } 149 | } 150 | 151 | function upCloudVps_AdminCustomButtonArray() 152 | { 153 | return [ 154 | 'Start VPS' => 'StartVPS', 155 | 'Stop VPS' => 'StopVPS', 156 | 'Reboot VPS' => 'RebootVPS', 157 | ]; 158 | } 159 | 160 | function upCloudVps_ReverseDNS($params) 161 | { 162 | try { 163 | $params['ip'] = $_POST['ip']; 164 | $params['rdns'] = $_POST['rdns']; 165 | $vmManager = new vmManager($params); 166 | return $vmManager->rdns(); 167 | } catch (\Exception $e) { 168 | return $e->getMessage(); 169 | } 170 | } 171 | 172 | function upCloudVps_AdminServicesTabFields(array $params) 173 | { 174 | if ($params['status'] != 'Active') { 175 | return []; 176 | } 177 | try { 178 | $adminManager = new adminManager($params); 179 | return $adminManager->adminarea(); 180 | } catch (\Exception $e) { 181 | return ['error' => $e->getMessage()]; 182 | } 183 | } 184 | 185 | function upCloudVps_AdminServicesTabFieldsSave($params) 186 | { 187 | try { 188 | if ($_REQUEST['act'] == 'rDNS') { 189 | upCloudVps_ReverseDNS($params); 190 | } 191 | } catch (\Exception $e) { 192 | return $e->getMessage(); 193 | } 194 | return 'success'; 195 | } 196 | 197 | function upCloudVps_ChangePackage(array $params) 198 | { 199 | try { 200 | $vmManager = new vmManager($params); 201 | return $vmManager->upgradePlan(); 202 | } catch (\Exception $e) { 203 | return $e->getMessage(); 204 | } 205 | } 206 | 207 | 208 | function upCloudVps_ClientArea(array $params) 209 | { 210 | if ($params['status'] != 'Active') { 211 | return []; 212 | } 213 | try { 214 | Helper::clientAreaPrimarySidebarHook($params); 215 | $action = App::getFromRequest('subaction'); 216 | if (!empty($action)) { 217 | Helper::ajaxAction($params, $action); 218 | } 219 | $clientManager = new clientManager($params); 220 | $vm = $clientManager->getData('details'); 221 | return [ 222 | 'templatefile' => 'templates/overview.tpl', 223 | 'templateVariables' => [ 224 | 'vm' => $vm, 225 | '_LANG' => Helper::getLang(), 226 | ], 227 | ]; 228 | } catch (\Exception $e) { 229 | return ['tabOverviewReplacementTemplate' => 'templates/error.tpl']; 230 | } 231 | } 232 | 233 | function upCloudVps_UsageUpdate(array $params) 234 | { 235 | try { 236 | $manager = new usageUpdate($params); 237 | $manager->usage(); 238 | } catch (\Exception $e) { 239 | return $e->getMessage(); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/lib/adminManager.php: -------------------------------------------------------------------------------- 1 | manager = new upCloudVps($params); 20 | $this->params = $params; 21 | $this->_LANG = Helper::getLang(); 22 | } 23 | 24 | 25 | public function adminarea() 26 | { 27 | global $aInt; 28 | $instanceId = $this->params['model']->serviceProperties->get('instanceId|instance Id'); 29 | $details = $this->manager->GetServer($instanceId)['response']['server']; 30 | $totalStorage = 0; 31 | $memoryGb = $details['memory_amount'] / 1024; 32 | $templ = $details['storage_devices']['storage_device']; 33 | $zones = $this->manager->GetZones()['response']['zones']['zone']; 34 | foreach ($zones as $zone) { 35 | if ($zone['id'] == $details['zone']) { 36 | $details['zoneDescription'] = $zone['description']; 37 | break; 38 | } 39 | } 40 | 41 | 42 | foreach ($templ as $temp) { 43 | if ($temp['part_of_plan'] == 'yes' || $details['plan'] == 'custom') { 44 | $details['osname'] = $temp['storage_title']; 45 | $details['base_storage_size'] = $temp['storage_size']; 46 | break; 47 | } 48 | } 49 | if (!empty($details['ip_addresses'])) { 50 | foreach ($details['ip_addresses']['ip_address'] as $ip) { 51 | $ReverseDNSValue = $this->manager->GetIPaddress($ip['address'])['response']; 52 | if (strpos($ReverseDNSValue['ip_address']['ptr_record'], 'upcloud') !== false) { 53 | $this->manager->ModifyIPaddress($instanceId, $ip['address'], 'client.' . $_SERVER['SERVER_NAME'] . '.host'); 54 | } 55 | $tableData[] = array($ip['address'], $ReverseDNSValue['ip_address']['ptr_record'], $ip['access'], $ip['family']); 56 | } 57 | $aInt->sortableTableInit('nopagination'); 58 | $interfaceInfo = $aInt->sortableTable(array($this->_LANG['IPAddress'], $this->_LANG['reversePTR'], $this->_LANG['Access'], $this->_LANG['Family'], ), $tableData); 59 | } 60 | 61 | 62 | foreach ($this->manager->Getplans()['response']['plans']['plan'] as $Plan) { 63 | if ($Plan['name'] == $details['plan'] and $Plan['memory_amount'] == $details['memory_amount']) { 64 | $TotalTraffic = $Plan['public_traffic_out']; 65 | $Outgoing = $this->manager->formatSizeBytestoGB($details['plan_ipv4_bytes'] + $details['plan_ipv6_bytes']); 66 | $Percentage = round((($Outgoing / $TotalTraffic) * 100), 2); 67 | $progressClass = 'progress-bar-success'; 68 | if ($Percentage >= 49 && $Percentage < 70) { 69 | $progressClass = 'progress-bar-info'; 70 | } elseif ($Percentage >= 70 && $Percentage < 86) { 71 | $progressClass = 'progress-bar-warning'; 72 | } elseif ($Percentage >= 86) { 73 | $progressClass = 'progress-bar-danger'; 74 | } 75 | 76 | $Bandwidth = '
77 |
79 | ' . $Percentage . '% 80 |
81 |
82 | ' . $this->_LANG['TotalTraffic'] . ': ' . $TotalTraffic . ' ' . $this->_LANG['GB'] . ' – ' . $this->_LANG['used'] . ' ' . $Outgoing . ' ' . $this->_LANG['GB'] . ' (' . $Percentage . '%)'; 83 | } 84 | } 85 | 86 | 87 | $remoteAccessHost = $details['remote_access_host']; 88 | if (!empty($remoteAccessHost)) { 89 | $resolvedHost = gethostbyname($remoteAccessHost); 90 | $resolvedHost = ($resolvedHost !== $remoteAccessHost) ? $resolvedHost : $remoteAccessHost; 91 | } else { 92 | $resolvedHost = null; 93 | } 94 | 95 | $vncIp = $resolvedHost; 96 | $output = ' 97 |
98 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
' . $this->_LANG['Hostname'] . ' ' . $details['hostname'] . '
' . $this->_LANG['VMId'] . ' ' . $details['uuid'] . '
' . $this->_LANG['Template'] . ' ' . $details['osname'] . '
' . $this->_LANG['Plan'] . ' ' . $details['plan'] . '
' . $this->_LANG['Status'] . ' ' . $details['state'] . '
' . $this->_LANG['Location'] . ' ' . $details['zoneDescription'] . '
' . $this->_LANG['Backup'] . ' ' . ucfirst($details['simple_backup']) . '
110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
' . $this->_LANG['cpu'] . ' ' . $details['core_number'] . '' . $this->_LANG['core'] . '
' . $this->_LANG['Disk'] . ' ' . $details['base_storage_size'] . '' . $this->_LANG['GB'] . '
' . $this->_LANG['memory'] . ' ' . $memoryGb . '' . $this->_LANG['GB'] . '
' . $this->_LANG['vncEnabled'] . ' ' . ucfirst($details['remote_access_enabled']) . '
' . $this->_LANG['vncHost'] . ' ' . $vncIp . '
' . $this->_LANG['vncPort'] . ' ' . $details['remote_access_port'] . '
' . $this->_LANG['vncPassword'] . ' ' . $details['remote_access_password'] . '
119 |
120 | '; 121 | $ReverseDNS = ' 122 |
123 | 133 | 134 | 135 |
'; 136 | $menu[$this->_LANG['VmInfo']] = $output; 137 | $menu[$this->_LANG['Interface']] = $interfaceInfo; 138 | foreach ($this->manager->Getplans()['response']['plans']['plan'] as $Plan) { 139 | if ($Plan['name'] == $details['plan'] and $Plan['memory_amount'] == $details['memory_amount']) { 140 | $menu[$this->_LANG['Bandwidth']] = $Bandwidth; 141 | } 142 | } 143 | $menu[$this->_LANG['reversePTR']] = $ReverseDNS; 144 | return $menu; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /docs/INSTALL.md: -------------------------------------------------------------------------------- 1 | # UpCloud WHMCS Module Installation Guide 2 | 3 | This guide will walk you through the process of installing and configuring the UpCloud VPS Module for WHMCS. 4 | 5 | ## Table of Contents 6 | 7 | - [Overview](#overview) 8 | - [Features](#features) 9 | - [Installation](#installation) 10 | - [Downloading and Uploading to WHMCS](#downloading-and-uploading-to-whmcs) 11 | - [Obtaining API Credentials](#obtaining-api-credentials) 12 | - [Creating Your First WHMCS Server](#creating-your-first-whmcs-server) 13 | - [Creating a Server Group](#creating-a-server-group) 14 | - [Configuration](#configuration) 15 | - [Creating a Product Group](#creating-a-product-group) 16 | - [Creating a New Product](#creating-a-new-product) 17 | - [Module Settings](#module-settings) 18 | - [Configurable Options](#configurable-options) 19 | - [Adding Existing VPS to Users](#adding-existing-vps-to-users) 20 | 21 | ## Overview 22 | 23 | The UpCloud VPS Module for WHMCS is a versatile tool designed to assist businesses of all sizes in efficiently managing their Cloud Instances. It simplifies the process of configuring and overseeing VM instances, providing users with seamless control and automation capabilities. 24 | 25 | ## Features 26 | 27 | ### Admin Features: 28 | 29 | 1. **Deployment**: Administrators can manually deploy VMs by clicking 'Create' or set up automatic deployment during product setup, specifying location, region, OS, and custom fields like SSH keys or user data (cloud-init) initialization. 30 | 31 | 2. **Termination**: VM termination can be done manually or automated, with the option to set up automatic termination through WHMCS automation settings, which functions based on WHMCS cron jobs. 32 | 33 | 3. **Upgrade/Downgrade**: Administrators can manually adjust CPU/RAM/Storage configurations by clicking the 'Change Package' button, or configure automatic upgrades/downgrades. 34 | 35 | 4. **VM Management**: Administrators can start, stop, restart, suspend, unsuspend VMs, and view all basic VM information including VPS, VNC, IP addresses, etc. 36 | 37 | 5. **Reverse PTR**: Admins can set and view reverse PTR records. 38 | 39 | 6. **Login Credentials**: If configured, the system can even send login credentials automatically. 40 | 41 | 7. **Custom Plans**: Once set by admin during product setup, users can customize their plans and select them for deployment after making a payment. 42 | 43 | 8. **Cron Job**: The administrator must schedule cron jobs to run every 5 minutes to generate bandwidth usage graphs. 44 | 45 | 9. **Pricing Adjustment**: While UpCloud has predefined prices for Windows, CPU, RAM, and storage, you have the flexibility to adjust them according to your specific requirements. 46 | 47 | ### Client Features: 48 | 49 | 1. **VM Information**: Clients can view basic VM information such as VPS, VNC, IP addresses details. 50 | 51 | 2. **VNC Management**: Clients can enable/disable VNC access. 52 | 53 | 3. **Password Management**: Clients can change their VNC passwords. 54 | 55 | 4. **Reverse PTR**: Clients can view and set reverse PTR records. 56 | 57 | 5. **VM Control**: Clients can start, stop, and restart their VMs. 58 | 59 | 6. **Configuration Modification**: Clients can modify server configurations to suit their needs. 60 | 61 | 7. **Bandwidth Usage Graph**: Clients can view bandwidth usage graphs for intervals spanning 24 hours, a week, a month, and a year. 62 | 63 | ## Installation 64 | 65 | ### Downloading and Uploading to WHMCS 66 | 67 | 1. Obtain the module from our GitHub repository. 68 | 2. Unpack the module on your local system and transfer the "upCloudVps" folder to your WHMCS directory named "/path-to-your-WHMCS/modules/servers/" on your WHMCS installation. 69 | 70 | ### Obtaining API Credentials 71 | 72 | 1. Access the UpCloud Hub at https://hub.upcloud.com and log in using your credentials. 73 | 2. Navigate to the "People" section from the left sidebar. 74 | 3. Proceed to create a sub-account by selecting the "Create subaccount" button. 75 | 4. Enter a username, password, and complete other required details (Personal Information), then click "Create subaccount" at the bottom. 76 | 5. After creating the user, click on "Edit", then navigate to the Permissions section and click on "Go to permissions". 77 | 6. Enable all permissions except "Control Panel" and save the changes. 78 | 79 | 80 | 81 | ### Creating Your First WHMCS Server 82 | 83 | Reference: https://docs.whmcs.com/Servers 84 | 85 | 1. Log in to your WHMCS as an admin user and go to Configuration > System Settings > Servers. 86 | 2. Select "Add New Server" and choose "UpCloud VPS" from the module dropdown. 87 | 3. Enter the Hostname or IP Address as: api.upcloud.com (you can assign any name). 88 | 4. For Username, input your API username created in the previous step. 89 | 5. For Password, input your API password created in the previous step. 90 | 6. Proceed by clicking on "Test Connection" or "Continue Anyway". 91 | 92 | ### Creating a Server Group 93 | 94 | 1. Click Create New Group under the Options heading. 95 | 2. Enter a name for your group. 96 | 3. Select the servers you want to assign (previously created) to this group in the box on the left. 97 | 4. Click Add to move them to the box on the right, which contains the servers for this group. 98 | 5. Click Save Changes to complete the process. 99 | 100 | ## Configuration 101 | 102 | ### Creating a Product Group 103 | 104 | 1. Navigate to Configuration > System Settings > Product/Services 105 | 106 | ![Producs & Services](images/products_services.png) 107 | 108 | 109 | 2. Click on Create Product Group 110 | 111 | ![Product Group](images/product_group.png) 112 | 113 | 114 | 3. Provide a Product Group name and adjust all other settings based on your requirements 115 | 116 | ![Product Config](images/product_config.png) 117 | 118 | 4. Click on Save Changes 119 | 120 | 121 | ### Creating a New Product 122 | 123 | 1. Click on Create a New Product 124 | 125 | ![New Product](images/new_product.png) 126 | 2. Configure the following details: 127 | - **Product Type**: Select as "Other" 128 | - **Product Group**: Select the product group that you created 129 | - **Product Name**: Provide your product name 130 | - **Module**: UpCloud VPS 131 | - **Create as Hidden**: Off 132 | 3. Click on Save Changes 133 | 134 | ### Module Settings 135 | 136 | On the next page: 137 | 1. Verify the Product Type 138 | 2. Set Welcome Email to "Dedicated/VPS Server Welcome Email" 139 | 3. Go to the Module Settings Tab 140 | 4. Select the required details: 141 | - Module Name 142 | - Server group 143 | - Default Location 144 | - Plan 145 | - Template 146 | 5. If for Plans you selected "Custom", then users will have the ability to choose their own CPU, RAM, and Storage space based on which VMs will be created. 147 | 148 | ![Server Plan Config](images/server_plan_config.png) 149 | 150 | 6. Decide whether the product requires manual approval from admins or is automatically deployed upon order. 151 | 7. Click the Save Changes button. 152 | 153 | ![Server Plan Ready](images/server_plan_ready.png) 154 | 155 | This will create custom fields named (`instanceId`, `ssh_key` & `userData`) as shown below: 156 | 157 | ![Custom Fields](images/custom_fields.png) 158 | 159 | ### Configurable Options 160 | 161 | The module will automatically create Configurable Options when you create your first product. You can select option groups that apply to your product. 162 | 163 | To adjust these options: 164 | 165 | 1. Go to System Settings -> Configurable Options 166 | 2. Edit the newly created Options 167 | 168 | ![Configurable Options](images/configurable_options_normal.png) 169 | 170 | Through these configurations you can: 171 | 172 | - Set custom pricing for backup options 173 | - Configure available locations 174 | - Configure available custom plans 175 | - Adjust prices for available images (such as Windows) 176 | - Backup and Location: set custom pricing for backup options, and configure available locations 177 | 178 | If custom plans are utilised: 179 | 180 | - **Memory**: Users can choose the memory of a VPS, ranging from 4GB to 64GB. 181 | - **vCPU**: Users can choose the vCPU of a VPS, ranging from 2 cores to 24 cores. 182 | - **Storage**: Users can choose the storage of a VPS, ranging from 50GB to 460GB. 183 | 184 | Note that custom plans are a discontinued feature. See the [UpCloud product documentation](https://upcloud.com/docs/products/cloud-servers/pricing/flexible-plans/) for more information. 185 | 186 | ## Adding Existing VPS to Users 187 | 188 | To add an existing UpCloud VPS to a user's account: 189 | 1. Edit the user's product 190 | 2. Within the custom field named "Instance Id", add your VPS UUID that you can obtain from UpCloud Hub 191 | 192 | ![Existing VPS](images/existing_vps.png) 193 | 194 | ## Additional Configuration 195 | 196 | ### Product/Service -> Custom Fields 197 | 198 | - **User Data/Cloud-init**: If provided by the user, these scripts will initiate after VM creation to kickstart the initialization process. 199 | - **SSH Key**: If supplied by the user during VM creation, it will be utilized for user authentication instead of a password; otherwise, the system will default to using a password for authentication. Note that some templates only support SSH keys. 200 | - **Instance Id**: This field is used to assign a user with a VPS 201 | 202 | ## Cron Job Setup 203 | 204 | The administrator must schedule cron jobs to run every 5 minutes to generate bandwidth usage graphs. This is essential for the bandwidth monitoring feature to work properly. 205 | 206 | ![Bandwidth](images/bandwidth.png) -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/css/theme.css: -------------------------------------------------------------------------------- 1 | 2 | .custom-input-group { 3 | display: flex; 4 | align-items: center; 5 | } 6 | 7 | .custom-input-group label { 8 | margin-right: 10px; /* Adjust as needed */ 9 | } 10 | 11 | .custom-input-group input { 12 | flex: 1; 13 | padding: 5px; 14 | border: 1px solid #ccc; /* Add any desired border styles */ 15 | } 16 | .loader { 17 | position: fixed; 18 | z-index: 9999; 19 | top: 50%; 20 | left: 50%; 21 | display: inline-block; 22 | width: 50px; 23 | height: 50px; 24 | border: 3px solid rgba(255, 255, 255, .3); 25 | border-radius: 50%; 26 | border-top-color: rgb(0, 0, 0); 27 | animation: spin 1s ease-in-out infinite; 28 | -webkit-animation: spin 1s ease-in-out infinite; 29 | } 30 | 31 | 32 | @keyframes spin { 33 | to { -webkit-transform: rotate(360deg); } 34 | } 35 | @-webkit-keyframes spin { 36 | to { -webkit-transform: rotate(360deg); } 37 | } 38 | 39 | .upCloudTable td{ 40 | vertical-align: middle !important; 41 | } 42 | .dataTables_length 43 | { 44 | background:white !important; 45 | } 46 | 47 | .dataTables_filter input { width: 300px !important } 48 | 49 | 50 | .alert-fixed { 51 | position:fixed; 52 | top: 0px; 53 | left: 0px; 54 | width: 100%; 55 | z-index:9999; 56 | border-radius:0px 57 | } 58 | 59 | .module-main-header { 60 | border-bottom: 1px solid #DDD; 61 | } 62 | 63 | .module-main-header h2 { 64 | letter-spacing: 0; 65 | font-family: "Helvetica Neue", helvetica, arial, sans-serif; 66 | font-size: 22px; 67 | font-weight: lighter; 68 | line-height: 36px; 69 | } 70 | 71 | .module-main-header .btn-back { 72 | border: 0; 73 | background: none; 74 | font-size: 15px; 75 | line-height: 36px; 76 | color: #8A8E99; 77 | } 78 | 79 | .module-main-header .btn-back:focus { 80 | outline: none; 81 | } 82 | 83 | .module-main-header .btn-back::-moz-focus-inner { 84 | border: 0; 85 | } 86 | 87 | #uc-wrapper { 88 | font-family: "Helvetica Neue",helvetica,arial,sans-serif; 89 | font-size: 14px; 90 | line-height: 1.42857143; 91 | color: #333; 92 | } 93 | 94 | #uc-wrapper *:focus { 95 | outline: none; 96 | } 97 | 98 | #uc-wrapper *::-moz-focus-inner { 99 | border: 0; 100 | } 101 | 102 | #uc-wrapper .btn:hover, 103 | #uc-wrapper .btn:active, 104 | #uc-wrapper .btn:focus, 105 | #uc-wrapper a:focus, 106 | #uc-wrapper a:active, 107 | #uc-wrapper a:hover, 108 | #uc-wrapper a { 109 | text-decoration: none!important; 110 | outline: none!important; 111 | } 112 | 113 | @media (min-width: 768px) { 114 | 115 | #uc-wrapper .text-center-sm { 116 | text-align: center; 117 | } 118 | 119 | } 120 | 121 | #uc-wrapper .buttons-content .big-button { 122 | background: #FFF; 123 | border: 0; 124 | color: #337AB7; 125 | cursor: pointer; 126 | text-align: left; 127 | } 128 | 129 | #uc-wrapper .buttons-content .big-button:hover { 130 | color: #000; 131 | } 132 | 133 | #uc-wrapper .form-group .fa-question-circle { 134 | font-size: 16px; 135 | color: #337AB7; 136 | line-height: 36px; 137 | } 138 | 139 | #uc-wrapper .module-sidebar .sidebar-header h3 { 140 | font-size: 18px; 141 | font-weight: 400; 142 | line-height: 30px; 143 | } 144 | 145 | #uc-wrapper .module-sidebar .btn.cp-sidebar-toggle { 146 | background: none; 147 | box-shadow: none; 148 | } 149 | 150 | #uc-wrapper .module-sidebar .cp-sidebar-toggle .icon-bar { 151 | background-color: #8A8E99; 152 | border-radius: 0; 153 | } 154 | 155 | #uc-wrapper .module-sidebar .module-menu li a { 156 | color: #5C5E66; 157 | background: #FFF; 158 | line-height: 30px; 159 | } 160 | 161 | #uc-wrapper .module-sidebar .module-menu li a:hover { 162 | color: #000; 163 | } 164 | 165 | @media (min-width: 768px) { 166 | 167 | #uc-wrapper .module-sidebar .module-menu li a:hover { 168 | box-shadow: 0 0 5px rgba(0,0,0,0.1); 169 | } 170 | 171 | } 172 | 173 | @media (max-width: 768px) { 174 | 175 | #uc-wrapper .module-sidebar .module-menu li a:hover { 176 | background-color: #E6E6E6; 177 | } 178 | 179 | } 180 | 181 | #uc-wrapper .module-sidebar .module-menu li.active a { 182 | font-weight: 700; 183 | } 184 | 185 | #uc-wrapper .module-content { 186 | background-color: #FFF; 187 | } 188 | 189 | #uc-wrapper .module-header .icon-header { 190 | background-repeat: no-repeat; 191 | } 192 | 193 | /* icons */ 194 | #uc-wrapper .icon-boot { 195 | background-image: url('../img/boot.png'); 196 | } 197 | 198 | #uc-wrapper .icon-reboot { 199 | background-image: url('../img/reboot.png'); 200 | } 201 | 202 | #uc-wrapper .icon-stop { 203 | background-image: url('../img/stop.png'); 204 | } 205 | 206 | #uc-wrapper .icon-vnc { 207 | background-image: url('../img/vnc.png'); 208 | } 209 | 210 | #uc-wrapper .icon-network { 211 | background-image: url('../img/network.png'); 212 | } 213 | 214 | #uc-wrapper .icon-disks { 215 | background-image: url('../img/disks.png'); 216 | } 217 | 218 | #uc-wrapper .icon-graphs { 219 | background-image: url('../img/graphs.png'); 220 | } 221 | 222 | 223 | #uc-wrapper .module-sub-header h2, 224 | #uc-wrapper .module-header h1 { 225 | font-size: 20px; 226 | color: #45464C; 227 | line-height: 34px; 228 | } 229 | 230 | #uc-wrapper .module-body h4 { 231 | font-size: 16px; 232 | } 233 | 234 | /* forms */ 235 | #uc-wrapper .form-group .btn span { 236 | line-height: 1.5; 237 | } 238 | 239 | #uc-wrapper .form-group span:not(.input-group-addon), 240 | #uc-wrapper .form-fluid span:not(.input-group-addon) { 241 | line-height: 36px; 242 | } 243 | 244 | #uc-wrapper label:not(.control-label) { 245 | font-weight: 400; 246 | font-size: 12px; 247 | line-height: 34px; 248 | } 249 | 250 | #uc-wrapper label.control-label { 251 | font-size: 12px; 252 | font-weight: 800; 253 | } 254 | 255 | #uc-wrapper .input-group .form-control.first { 256 | border-radius: 4px 0 0 4px!important; 257 | } 258 | 259 | #uc-wrapper .input-group .form-control.last { 260 | border-radius: 0 4px 4px 0!important; 261 | } 262 | 263 | #uc-wrapper .input-group-addon:not(.first):not(.last), 264 | #uc-wrapper .input-group-btn:not(.first):not(.last), 265 | #uc-wrapper .input-group .form-control:not(.first):not(.last) { 266 | border-radius: 0; 267 | border-width: 1px 0; 268 | } 269 | 270 | #uc-wrapper .advanced-options { 271 | /*background: rgba(0,0,0,0.05); 272 | border-radius: 4px;*/ 273 | } 274 | 275 | #uc-wrapper .well .form-actions { 276 | border-top: 1px solid #E6E6E6; 277 | } 278 | 279 | #uc-wrapper p { 280 | line-height: 22px; 281 | } 282 | 283 | /* tables */ 284 | #uc-wrapper .table { 285 | border-bottom: 1px solid #E4E8F0; 286 | } 287 | 288 | #uc-wrapper .table > tbody > tr > td.cell-actions { 289 | text-align: right; 290 | } 291 | 292 | #uc-wrapper .table > tbody > tr > td.cell-actions > * { 293 | display: inline-block; 294 | margin: 0; 295 | } 296 | 297 | #uc-wrapper .table > tbody > tr > td .btn-icon .fa { 298 | font-size: 16px; 299 | line-height: 22px; 300 | } 301 | 302 | #uc-wrapper .table > tbody > tr > td.cell-actions .btn-icon .fa, 303 | #uc-wrapper .table > tbody > tr > td.cell-actions .btn-icon .caret { 304 | color: #8A8E99; 305 | } 306 | 307 | #uc-wrapper .table tr th, 308 | #uc-wrapper .table tr td { 309 | border-color: #E4E8F0; 310 | } 311 | 312 | #uc-wrapper .table thead tr th { 313 | font-size: 11px; 314 | text-transform: uppercase; 315 | font-weight: 800; 316 | color: #A1A5B3; 317 | } 318 | 319 | #uc-wrapper .table tr td { 320 | font-size: 12px; 321 | line-height: 22px; 322 | } 323 | 324 | #uc-wrapper .table tr td a { 325 | cursor: pointer; 326 | } 327 | 328 | /* buttons */ 329 | #uc-wrapper .btn { 330 | font-size: 12px; 331 | line-height: 22px; 332 | } 333 | 334 | #uc-wrapper .btn.btn-icon, #uc-wrapper .btn.btn-icon:hover, #uc-wrapper .btn.btn-icon:active, #uc-wrapper .btn-group.open .btn.btn-icon { 335 | background: transparent; 336 | border: none; 337 | box-shadow: none; 338 | border-radius: 0; 339 | } 340 | 341 | #uc-wrapper .fa-question-circle { 342 | cursor: pointer; 343 | } 344 | 345 | #uc-wrapper a.panel-heading h6 { 346 | color: #5C5E66; 347 | } 348 | 349 | #uc-wrapper a.panel-heading:hover h6 { 350 | color: #000; 351 | } 352 | 353 | #UCLoader { 354 | background-color: rgba(0, 0, 0, 0.1); 355 | left: 0; 356 | top: 0; 357 | position: fixed; 358 | min-width: 100%; 359 | min-height: 100%; 360 | z-index: 60000; 361 | } 362 | 363 | #UCLoader img { 364 | position: absolute; 365 | top: 50%; 366 | left: 50%; 367 | width: 300px; 368 | margin-left: -150px; 369 | } 370 | 371 | .modal-loader { 372 | width: 100%; 373 | height: 100px; 374 | background: url("../img/ajax-loader.gif") no-repeat scroll 0 0 rgba(0, 0, 0, 0); 375 | background-position: center; 376 | } 377 | 378 | #uc-wrapper .icon-editvm { 379 | background-image: url('../img/editvm.png'); 380 | } 381 | 382 | #uc-wrapper .loader-small { 383 | background: rgba(0, 0, 0, 0) url("../img/ajax-loader-small.gif") no-repeat scroll 0 0; 384 | display: inline-block; 385 | height: 16px; 386 | margin-left: 4px; 387 | position: absolute; 388 | width: 16px; 389 | } 390 | 391 | #uc-wrapper .icon-vps-details { 392 | background-image: url('../img/vps-details.png'); 393 | } 394 | 395 | #uc-wrapper .icon-vps-details { 396 | background-image: url('../img/vps-details.png'); 397 | } 398 | 399 | 400 | #uc-wrapper .dataTables_filter input { 401 | margin-left: 5px; 402 | } 403 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/lib/upCloudVps.php: -------------------------------------------------------------------------------- 1 | baseUrl = 'https://api.upcloud.com/1.3/'; 23 | $this->curl = curl_init(); 24 | curl_setopt_array($this->curl, [ 25 | CURLOPT_USERAGENT => 'upcloud-whmcs-module/' . MODULE_VERSION, 26 | CURLOPT_RETURNTRANSFER => true, 27 | CURLOPT_USERPWD => $user . ':' . $password, 28 | ]); 29 | } 30 | 31 | public function __destruct() 32 | { 33 | curl_close($this->curl); 34 | } 35 | 36 | protected function setHttpHeader($name, $value) 37 | { 38 | $this->httpHeader[$name] = $value; 39 | } 40 | 41 | protected function executeRequest($method, $url, $data = null) 42 | { 43 | curl_setopt($this->curl, CURLOPT_URL, $this->baseUrl . $url); 44 | curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $method); 45 | if ($data !== null) { 46 | curl_setopt($this->curl, CURLOPT_POSTFIELDS, json_encode($data)); 47 | $this->setHttpHeader('Content-Type', 'application/json'); 48 | } 49 | 50 | // Set HTTP headers 51 | curl_setopt($this->curl, CURLOPT_HTTPHEADER, array_map(function ($key, $value) { 52 | 53 | return "$key: $value"; 54 | }, array_keys($this->httpHeader), $this->httpHeader)); 55 | $response = curl_exec($this->curl); 56 | $statusCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); 57 | if ($response === false) { 58 | throw new \Exception('Curl error: ' . curl_error($this->curl)); 59 | } 60 | 61 | logModuleCall('upCloudVps', strtoupper($method), $url . PHP_EOL . ((!empty($data)) ? json_encode($data, JSON_PRETTY_PRINT) : ''), json_decode($response, true), json_decode($response, true), []); 62 | return ['response_code' => $statusCode, 'response' => json_decode($response, true)]; 63 | } 64 | 65 | public function get($url) 66 | { 67 | return $this->executeRequest('GET', $url); 68 | } 69 | 70 | public function post($url, $data = null) 71 | { 72 | return $this->executeRequest('POST', $url, $data); 73 | } 74 | 75 | public function put($url, $data = null) 76 | { 77 | return $this->executeRequest('PUT', $url, $data); 78 | } 79 | 80 | public function delete($url, $data = null) 81 | { 82 | return $this->executeRequest('DELETE', $url, $data); 83 | } 84 | 85 | 86 | 87 | //Get Account Info 88 | public function GetAccountInfo() 89 | { 90 | return $this->get('account'); 91 | } 92 | 93 | //List prices 94 | public function GetPrices() 95 | { 96 | return $this->get('price'); 97 | } 98 | 99 | //List available zones 100 | public function GetZones() 101 | { 102 | return $this->get('zone'); 103 | } 104 | 105 | //List timezones 106 | public function GetTimezones() 107 | { 108 | return $this->get('timezone'); 109 | } 110 | 111 | //List available plans 112 | public function GetPlans() 113 | { 114 | return $this->get('plan'); 115 | } 116 | 117 | //List server configurations 118 | public function GetServerConfigurations() 119 | { 120 | return $this->get('server_size'); 121 | } 122 | 123 | //List servers 124 | public function GetAllServers() 125 | { 126 | return $this->get('server'); 127 | } 128 | 129 | //Get server details 130 | public function GetServer($ServerUUID) 131 | { 132 | return $this->get('server/' . $ServerUUID); 133 | } 134 | 135 | //Create server - Getting templates 136 | public function GetTemplate() 137 | { 138 | return $this->get('storage/template'); 139 | } 140 | 141 | //Create server - Creating from a template 142 | public function CreateServer($ZoneID, $Hostname, $Plan, $OsUUID, $sshKey, $user_data, $backup, $networking, $ram = null, $vcpu = null, $storage = null) 143 | { 144 | $Templates = $this->GetTemplate()['response']['storages']['storage']; 145 | foreach ($Templates as $Template) { 146 | if ($Template['uuid'] == $OsUUID) { 147 | $TemplateTitle = $Template['title']; 148 | $TemplateUUID = $Template['uuid']; 149 | $TemplateType = $Template['template_type']; 150 | break; 151 | } 152 | } 153 | 154 | if (($Plan == 'custom') && isset($ram) && isset($vcpu) && isset($storage)) { 155 | $postData = [ 156 | 'server' => [ 157 | 'metadata' => 'yes', 158 | 'zone' => $ZoneID, // GetZones() 159 | 'title' => $Hostname, // hostname 160 | 'hostname' => $Hostname, // hostname 161 | 'remote_access_enabled' => 'yes', 162 | 'core_number' => (int) $vcpu, 163 | 'memory_amount' => (int) '1024' * (int) $ram, 164 | 'storage_devices' => [ 165 | 'storage_device' => [ 166 | [ 167 | 'action' => 'clone', 168 | 'storage' => $TemplateUUID, // GetTemplates() 169 | 'size' => $storage, 170 | 'tier' => 'maxiops', 171 | 'title' => $TemplateTitle, // OS Name 172 | ] 173 | ] 174 | ] 175 | ] 176 | ]; 177 | } else { 178 | $AllPlans = $this->Getplans()['response']['plans']['plan']; 179 | foreach ($AllPlans as $Plans) { 180 | if ($Plans['name'] == $Plan) { 181 | $PlanName = $Plans['name']; 182 | $PlanSize = $Plans['storage_size']; 183 | $PlanTier = $Plans['storage_tier']; 184 | break; 185 | } 186 | } 187 | 188 | $postData = [ 189 | 'server' => [ 190 | 'metadata' => 'yes', 191 | 'zone' => $ZoneID, // GetZones() 192 | 'title' => $Hostname, // hostname 193 | 'hostname' => $Hostname, // hostname 194 | 'plan' => $PlanName, // Getplans() 195 | 'remote_access_enabled' => 'yes', 196 | 'storage_devices' => [ 197 | 'storage_device' => [ 198 | [ 199 | 'action' => 'clone', 200 | 'storage' => $TemplateUUID, // GetTemplates() 201 | 'size' => $PlanSize, // storage_size from Getplans() 202 | 'tier' => $PlanTier, // storage_tier from Getplans() 203 | 'title' => $TemplateTitle, // OS Name 204 | ] 205 | ] 206 | ] 207 | ] 208 | ]; 209 | } 210 | 211 | if ($sshKey != 'na') { 212 | $postData['server']['login_user'] = [ 213 | 'username' => 'root', 214 | 'ssh_keys' => [ 215 | 'ssh_key' => [ 216 | $sshKey, 217 | ], 218 | ], 219 | ]; 220 | } 221 | 222 | if ($TemplateType == 'native') { 223 | if (!preg_match('/Windows/', $TemplateTitle)) { 224 | if ($sshKey != 'na') { 225 | $postData['server']['login_user'] = [ 226 | 'username' => 'root', 227 | 'ssh_keys' => [ 228 | 'ssh_key' => [ 229 | $sshKey, 230 | ], 231 | ], 232 | ]; 233 | } 234 | } 235 | } elseif ($TemplateType == 'cloud-init') { 236 | if ($sshKey != 'na') { 237 | $postData['server']['login_user'] = [ 238 | 'username' => 'root', 239 | 'ssh_keys' => [ 240 | 'ssh_key' => [ 241 | $sshKey, 242 | ], 243 | ], 244 | ]; 245 | } else { 246 | $error['response']['error']['error_message'] = 'ssh key required'; 247 | return $error; 248 | } 249 | } 250 | 251 | if ($networking == 'ipv4only') { 252 | $postData['server']['networking'] = [ 253 | 'interfaces' => [ 254 | 'interface' => [ 255 | [ 256 | 'ip_addresses' => [ 257 | 'ip_address' => [ 258 | [ 259 | 'family' => 'IPv4' 260 | ] 261 | ] 262 | ], 263 | 'type' => 'public' 264 | ] 265 | ] 266 | ] 267 | ]; 268 | } 269 | 270 | if ($user_data != 'na') { 271 | $postData['server']['user_data'] = $user_data; 272 | } 273 | 274 | if ($backup != 'no') { 275 | $postData['server']['simple_backup'] = '0100,' . $backup; 276 | } 277 | 278 | return $this->post('server', $postData); 279 | } 280 | 281 | public function serverOperation($action, $ServerUUID, $stop_type = null) 282 | { 283 | $data = array(); 284 | if ($stop_type !== null) { 285 | $data[$action . '_server']['stop_type'] = $stop_type; 286 | $data[$action . '_server']['timeout'] = '60'; 287 | } 288 | return $this->post('server/' . $ServerUUID . '/' . $action, $data); 289 | } 290 | 291 | public function StartServer($ServerUUID) 292 | { 293 | return $this->serverOperation('start', $ServerUUID); 294 | } 295 | 296 | public function StopServer($ServerUUID) 297 | { 298 | return $this->serverOperation('stop', $ServerUUID, 'hard'); 299 | } 300 | 301 | public function RestartServer($ServerUUID) 302 | { 303 | return $this->serverOperation('restart', $ServerUUID, 'hard'); 304 | } 305 | 306 | public function CancelServer($ServerUUID) 307 | { 308 | return $this->serverOperation('cancel', $ServerUUID); 309 | } 310 | 311 | public function DeleteServer($ServerUUID) 312 | { 313 | return $this->delete('server/' . $ServerUUID); 314 | } 315 | 316 | public function ModifyServer($uuid, $Plan) 317 | { 318 | $this->stopServerAndWait($uuid); 319 | $allPlans = $this->Getplans()['response']['plans']['plan']; 320 | foreach ($allPlans as $plan) { 321 | if ($plan['name'] == $Plan) { 322 | $planSize = $plan['storage_size']; 323 | break; 324 | } 325 | } 326 | 327 | $upgradePlan = $this->put('server/' . $uuid, ['server' => ['plan' => $Plan]]); 328 | if ($upgradePlan['response']['error']['error_message']) { 329 | return $upgradePlan; 330 | } else { 331 | $storages = $this->GetServer($uuid)['response']['server']['storage_devices']['storage_device']; 332 | foreach ($storages as $storage) { 333 | if ($storage['part_of_plan'] == 'yes') { 334 | $storageId = $storage['storage']; 335 | $existingStorageSize = $storage['storage_size']; 336 | break; 337 | } 338 | } 339 | if ($storageId && $planSize > $existingStorageSize) { 340 | $modeyStorage = $this->modifyStorage($storageId, $planSize); 341 | $this->StartServer($uuid); 342 | return $modeyStorage; 343 | } 344 | } 345 | } 346 | 347 | 348 | public function modifyStorage($storageId, $planSize) 349 | { 350 | $body = [ 351 | 'storage' => [ 352 | 'size' => $planSize 353 | ] 354 | ]; 355 | return $this->put('storage/' . $storageId, $body); 356 | } 357 | 358 | //Delete server and storage 359 | public function DeleteServernStorage($ServerUUID) 360 | { 361 | $this->stopServerAndWait($ServerUUID); 362 | return $this->delete('server/' . $ServerUUID . '?storages=1'); 363 | } 364 | 365 | public function DeleteServernStorageBackup($ServerUUID) 366 | { 367 | $this->stopServerAndWait($ServerUUID); 368 | return $this->delete('server/' . $ServerUUID . '?storages=1&backups=delete'); 369 | } 370 | 371 | public function stopServerAndWait($ServerUUID) 372 | { 373 | $result = $this->GetServer($ServerUUID); 374 | $state = $result['response']['server']['state']; 375 | if ($state == 'started') { 376 | $this->StopServer($ServerUUID); 377 | } 378 | 379 | $times = 0; 380 | // If VM delete takes time please increase it but when tested working within 45 second 381 | while ($state != 'stopped' && $times < 45) { 382 | sleep(2); 383 | $result = $this->GetServer($ServerUUID); 384 | $state = $result['response']['server']['state']; 385 | ++$times; 386 | } 387 | 388 | if ($state != 'stopped') { 389 | throw new \Exception('Can not stop server, taking time to response'); 390 | } 391 | } 392 | 393 | public function GetIPaddress($IPAddress) 394 | { 395 | return $this->get('ip_address/' . $IPAddress); 396 | } 397 | 398 | public function ModifyIPaddress($instanceId, $IP, $ptr_record) 399 | { 400 | if (!($this->GetIPaddress($IP)['response']['ip_address']['server'] == $instanceId)) { 401 | throw new \Exception('IP does not belong to your server'); 402 | } 403 | return $this->put('ip_address/' . $IP, array('ip_address' => array('ptr_record' => $ptr_record))); 404 | } 405 | 406 | 407 | public function vncPasswordUpdate($instanceId, $vncPass) 408 | { 409 | return $this->put('server/' . $instanceId, array('server' => array('remote_access_password' => $vncPass))); 410 | } 411 | 412 | public function vncEnableDisable($instanceId, $vncType) 413 | { 414 | return $this->put('server/' . $instanceId, array('server' => array('remote_access_enabled' => $vncType))); 415 | } 416 | 417 | public function modifyVPS($instanceId, $serverConfig) 418 | { 419 | return $this->put('server/' . $instanceId, $serverConfig); 420 | } 421 | 422 | public function formatSizeBytestoTB($bytes) 423 | { 424 | return round($bytes / 1024 / 1024 / 1024 / 1024, 2); 425 | } 426 | 427 | public function formatSizeBytestoMB($bytes) 428 | { 429 | return round($bytes / 1024 / 1024, 2); 430 | } 431 | 432 | public function formatSizeBytestoGB($bytes) 433 | { 434 | return round($bytes / 1024 / 1024 / 1024, 2); 435 | } 436 | 437 | public function formatSizeMBtoGB($MB) 438 | { 439 | return round($MB / 1024); 440 | } 441 | 442 | public function formatBytes($bytes, $precision = 2) 443 | { 444 | $unit = ['B', 'KB', 'MB', 'GB', 'TB']; 445 | $exp = floor(log($bytes, 1024)) | 0; 446 | return round($bytes / (pow(1024, $exp)), $precision) . ' ' . $unit[$exp]; 447 | } 448 | 449 | public function CurrencyConvert($fromCurrency, $toCurrency, $amount) 450 | { 451 | $amount_from_db = Capsule::table('tblcurrencies')->where('code', $toCurrency)->get(); 452 | $exchangeRate_whmcs = json_decode($amount_from_db, true)[0]['rate']; 453 | $convertedAmount = $amount * $exchangeRate_whmcs; 454 | return array('convertedAmount' => round($convertedAmount, 2)); 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/lib/configOptions.php: -------------------------------------------------------------------------------- 1 | manager = new upCloudVps($params); 23 | $this->params = $params; 24 | $this->_LANG = Helper::getLang(); 25 | } 26 | 27 | public function configs() 28 | { 29 | $product = Product::find(App::getFromRequest('id')); 30 | if (App::getFromRequest('action') == 'save') { 31 | $this->ensureCustomFields($product); 32 | $this->createLinuxCloudInitConfigurableOptions($product); 33 | $this->createLinuxNativeConfigurableOptions($product); 34 | $this->createWindowsNativeConfigurableOptions($product); 35 | $this->createBackupLocationConfigurableOptions($product); 36 | $this->createCustomConfigurableOptions($product); 37 | } // Save End 38 | 39 | return [ 40 | 'Default Location' => ['Type' => 'dropdown', 'Options' => $this->getZoneLocation()], 41 | 'Plan' => ['Type' => 'dropdown', 'Options' => $this->getVmplans()], 42 | 'Template' => ['Type' => 'dropdown', 'Options' => $this->getTemplateIds()], 43 | ]; 44 | } 45 | 46 | private function createCustomConfigurableOptions($product) 47 | { 48 | $currencyId = Capsule::table('tblcurrencies')->where('default', '1')->first()->id; 49 | $currencyCode = Capsule::table('tblcurrencies')->where('default', '1')->first()->code; 50 | $groupId = Capsule::table('tblproductconfiggroups')->where('name', 'Configurable options for UpCloud - custom plans')->first()->id; 51 | if (!$groupId) { 52 | $groupId = Capsule::table('tblproductconfiggroups')->insertGetId(['name' => 'Configurable options for UpCloud - custom plans', 'description' => 'Auto generated by upCloudVps module']); 53 | $groupIdLinks = Capsule::table('tblproductconfiglinks')->where('gid', $groupId)->where('pid', $product->id)->first()->gid; 54 | if (!$groupIdLinks) { 55 | Capsule::table('tblproductconfiglinks')->insert(['gid' => $groupId, 'pid' => $product->id]); 56 | } 57 | $this->createRAMFields($groupId, $currencyId, $currencyCode); 58 | $this->createCPUFields($groupId, $currencyId, $currencyCode); 59 | $this->createStorageFields($groupId, $currencyId, $currencyCode); 60 | } 61 | } 62 | 63 | private function createLinuxCloudInitConfigurableOptions($product) 64 | { 65 | $groupId = Capsule::table('tblproductconfiggroups')->where('name', 'Configurable options for UpCloud - Linux (cloud-init template)')->first()->id; 66 | $currencyId = Capsule::table('tblcurrencies')->where('default', '1')->first()->id; 67 | $currencyCode = Capsule::table('tblcurrencies')->where('default', '1')->first()->code; 68 | if (!$groupId) { 69 | $groupId = Capsule::table('tblproductconfiggroups')->insertGetId(['name' => 'Configurable options for UpCloud - Linux (cloud-init template)', 'description' => 'Auto generated by upCloudVps module']); 70 | $groupIdLinks = Capsule::table('tblproductconfiglinks')->where('gid', $groupId)->where('pid', $product->id)->first()->gid; 71 | if (!$groupIdLinks) { 72 | Capsule::table('tblproductconfiglinks')->insert(['gid' => $groupId, 'pid' => $product->id]); 73 | } 74 | $pomTemplates = $this->getTemplateIds('cloudinit'); 75 | $this->createTemplateFields($groupId, $pomTemplates, $currencyId, $currencyCode); 76 | } 77 | } 78 | 79 | private function createLinuxNativeConfigurableOptions($product) 80 | { 81 | $groupId = Capsule::table('tblproductconfiggroups')->where('name', 'Configurable options for UpCloud - Linux (native template)')->first()->id; 82 | $currencyId = Capsule::table('tblcurrencies')->where('default', '1')->first()->id; 83 | $currencyCode = Capsule::table('tblcurrencies')->where('default', '1')->first()->code; 84 | if (!$groupId) { 85 | $groupId = Capsule::table('tblproductconfiggroups')->insertGetId(['name' => 'Configurable options for UpCloud - Linux (native template)', 'description' => 'Auto generated by upCloudVps module']); 86 | $groupIdLinks = Capsule::table('tblproductconfiglinks')->where('gid', $groupId)->where('pid', $product->id)->first()->gid; 87 | if (!$groupIdLinks) { 88 | Capsule::table('tblproductconfiglinks')->insert(['gid' => $groupId, 'pid' => $product->id]); 89 | } 90 | $pomTemplates = $this->getTemplateIds('nativelinux'); 91 | $this->createTemplateFields($groupId, $pomTemplates, $currencyId, $currencyCode); 92 | } 93 | } 94 | 95 | private function createWindowsNativeConfigurableOptions($product) 96 | { 97 | $groupId = Capsule::table('tblproductconfiggroups')->where('name', 'Configurable options for UpCloud - Windows (native template)')->first()->id; 98 | $currencyId = Capsule::table('tblcurrencies')->where('default', '1')->first()->id; 99 | $currencyCode = Capsule::table('tblcurrencies')->where('default', '1')->first()->code; 100 | if (!$groupId) { 101 | $groupId = Capsule::table('tblproductconfiggroups')->insertGetId(['name' => 'Configurable options for UpCloud - Windows (native template)', 'description' => 'Auto generated by upCloudVps module']); 102 | $groupIdLinks = Capsule::table('tblproductconfiglinks')->where('gid', $groupId)->where('pid', $product->id)->first()->gid; 103 | if (!$groupIdLinks) { 104 | Capsule::table('tblproductconfiglinks')->insert(['gid' => $groupId, 'pid' => $product->id]); 105 | } 106 | $pomTemplates = $this->getTemplateIds('nativewindows'); 107 | $this->createTemplateFields($groupId, $pomTemplates, $currencyId, $currencyCode); 108 | } 109 | } 110 | 111 | private function createBackupLocationConfigurableOptions($product) 112 | { 113 | $currencyId = Capsule::table('tblcurrencies')->where('default', '1')->first()->id; 114 | $currencyCode = Capsule::table('tblcurrencies')->where('default', '1')->first()->code; 115 | $groupId = Capsule::table('tblproductconfiggroups')->where('name', 'Configurable options for UpCloud - backup and location')->first()->id; 116 | if (!$groupId) { 117 | $groupId = Capsule::table('tblproductconfiggroups')->insertGetId(['name' => 'Configurable options for UpCloud - backup and location', 'description' => 'Auto generated by upCloudVps module']); 118 | $groupIdLinks = Capsule::table('tblproductconfiglinks')->where('gid', $groupId)->where('pid', $product->id)->first()->gid; 119 | if (!$groupIdLinks) { 120 | Capsule::table('tblproductconfiglinks')->insert(['gid' => $groupId, 'pid' => $product->id]); 121 | } 122 | $this->createBackupFields($groupId, $this->getBackups()); 123 | $this->createLocationFields($groupId, $this->getZoneLocation(), $currencyId); 124 | } 125 | } 126 | 127 | private function createBackupFields($groupId, $backup) 128 | { 129 | $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'backup|Backup')->first()->id; 130 | if (!$optionId) { 131 | $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'backup|Backup', 'optiontype' => 1]); 132 | foreach ($backup as $id => $val) { 133 | Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $id . '|' . $val], ['configid' => $optionId]); 134 | } 135 | } 136 | } 137 | 138 | private function createRAMFields($groupId, $currencyId, $currencyCode) 139 | { 140 | $mems = array(); 141 | for ($i = 4; $i <= 64; $i += 1) { 142 | $mems["$i"] = "$i Gigabyte (GB)"; 143 | } 144 | $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'ram|Memory (RAM)')->first()->id; 145 | if (!$optionId) { 146 | $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'ram|Memory (RAM)', 'optiontype' => 1]); 147 | foreach ($mems as $mem => $mvals) { 148 | Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $mem . '|' . $mvals], ['configid' => $optionId]); 149 | } 150 | foreach (Capsule::table('tblproductconfigoptionssub')->where('configid', $optionId)->get() as $id) { 151 | $parts = explode('|', $id->optionname); 152 | $fprice = $parts[0] * '2'; 153 | $monthlys = $this->manager->CurrencyConvert('EUR', $currencyCode, $fprice); 154 | Capsule::table('tblpricing')->updateOrInsert(['type' => 'configoptions', 'relid' => $id->id, 'monthly' => $monthlys['convertedAmount']], ['currency' => $currencyId]); 155 | } 156 | } 157 | } 158 | 159 | private function createCPUFields($groupId, $currencyId, $currencyCode) 160 | { 161 | $cpus = array(); 162 | for ($i = 2; $i <= 24; $i += 1) { 163 | $cpus["$i"] = "$i Core vCPU"; 164 | } 165 | $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'vcpu|vCPU')->first()->id; 166 | if (!$optionId) { 167 | $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'vcpu|vCPU', 'optiontype' => 1]); 168 | foreach ($cpus as $cpu => $cvals) { 169 | Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $cpu . '|' . $cvals], ['configid' => $optionId]); 170 | } 171 | foreach (Capsule::table('tblproductconfigoptionssub')->where('configid', $optionId)->get() as $id) { 172 | $parts = explode('|', $id->optionname); 173 | $fprice = $parts[0] * '6'; 174 | $monthlys = $this->manager->CurrencyConvert('EUR', $currencyCode, $fprice); 175 | Capsule::table('tblpricing')->updateOrInsert(['type' => 'configoptions', 'relid' => $id->id, 'monthly' => $monthlys['convertedAmount']], ['currency' => $currencyId]); 176 | } 177 | } 178 | } 179 | 180 | private function createStorageFields($groupId, $currencyId, $currencyCode) 181 | { 182 | $storages = array(); 183 | for ($i = 50; $i <= 460; $i += 10) { 184 | $storages["$i"] = "$i Gigabyte (GB)"; 185 | } 186 | $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'storage|Storage')->first()->id; 187 | if (!$optionId) { 188 | $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'storage|Storage', 'optiontype' => 1]); 189 | foreach ($storages as $storage => $vals) { 190 | Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $storage . '|' . $vals], ['configid' => $optionId]); 191 | } 192 | foreach (Capsule::table('tblproductconfigoptionssub')->where('configid', $optionId)->get() as $id) { 193 | $parts = explode('|', $id->optionname); 194 | $fprice = $parts[0] * '0.10'; 195 | $monthlys = $this->manager->CurrencyConvert('EUR', $currencyCode, $fprice); 196 | Capsule::table('tblpricing')->updateOrInsert(['type' => 'configoptions', 'relid' => $id->id, 'monthly' => $monthlys['convertedAmount']], ['currency' => $currencyId]); 197 | } 198 | } 199 | } 200 | 201 | private function createTemplateFields($groupId, $pomTemplates, $currencyId, $currencyCode) 202 | { 203 | $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'template|Template')->first()->id; 204 | if (!$optionId) { 205 | $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'template|Template', 'optiontype' => 1]); 206 | foreach ($pomTemplates as $id => $val) { 207 | Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $id . '|' . $val], ['configid' => $optionId]); 208 | } 209 | foreach (Capsule::table('tblproductconfigoptionssub')->where('configid', $optionId)->get() as $id) { 210 | if (strpos($id->optionname, 'Datacenter') !== false) { 211 | $monthlys = $this->manager->CurrencyConvert('EUR', $currencyCode, '25'); 212 | } elseif (strpos($id->optionname, 'Standard') !== false) { 213 | $monthlys = $this->manager->CurrencyConvert('EUR', $currencyCode, '10'); 214 | } else { 215 | $monthlys['convertedAmount'] = '0.00'; 216 | } 217 | Capsule::table('tblpricing')->updateOrInsert(['type' => 'configoptions', 'relid' => $id->id, 'monthly' => $monthlys['convertedAmount']], ['currency' => $currencyId]); 218 | } 219 | } 220 | } 221 | 222 | private function createLocationFields($groupId, $zones, $currencyId) 223 | { 224 | $optionId = Capsule::table('tblproductconfigoptions')->where('gid', $groupId)->where('optionname', 'location|Location')->first()->id; 225 | if (!$optionId) { 226 | $optionId = Capsule::table('tblproductconfigoptions')->insertGetId(['gid' => $groupId, 'optionname' => 'location|Location', 'optiontype' => 1]); 227 | foreach ($zones as $zon => $val) { 228 | Capsule::table('tblproductconfigoptionssub')->updateOrInsert(['optionname' => $zon . '|' . $val], ['configid' => $optionId]); 229 | } 230 | foreach (Capsule::table('tblproductconfigoptionssub')->where('configid', $optionId)->get() as $id) { 231 | Capsule::table('tblpricing')->updateOrInsert(['type' => 'configoptions', 'relid' => $id->id], ['currency' => $currencyId]); 232 | } 233 | } 234 | } 235 | 236 | private function ensureCustomFields($product) 237 | { 238 | $customFields = [ 239 | ['fieldname' => 'ssh_key|SSH Public Key', 'fieldtype' => 'textarea', 'description' => $this->_LANG['sshRsa'], 'regexpr' => '#^(ssh-(rsa|dss|ed25519)|ecdsa-sha2-nistp(256|384|521)|sk-(ssh-ed25519|ecdsa-sha2-nistp256)@openssh\.com) AAAA[0-9A-Za-z+/]+[=]{0,3}( .*)?$#', 'showorder' => 'on'], 240 | ['fieldname' => 'instanceId|instance Id', 'fieldtype' => 'text', 'adminonly' => 'on'], 241 | ['fieldname' => 'userData|User Data', 'fieldtype' => 'textarea', 'description' => $this->_LANG['userData'], 'showorder' => 'on'] 242 | ]; 243 | foreach ($customFields as $field) { 244 | if (empty(Capsule::table('tblcustomfields')->where('fieldname', $field['fieldname'])->where('relid', $product->id)->value('relid'))) { 245 | Capsule::table('tblcustomfields')->updateOrInsert(['type' => 'product', 'relid' => $product->id] + $field); 246 | } 247 | } 248 | } 249 | 250 | private function getBackups() 251 | { 252 | $backups['no'] = 'Disable'; 253 | $backups['daily'] = 'Daily (Day Plan)'; 254 | $backups['dailies'] = 'Dailies (Week Plan)'; 255 | $backups['weeklies'] = 'Weeklies (Month Plan)'; 256 | $backups['monthlies'] = 'Monthlies (Year Plan)'; 257 | return $backups; 258 | } 259 | 260 | 261 | private function getZoneLocation() 262 | { 263 | $zones = $this->manager->GetZones()['response']['zones']['zone']; 264 | $zoneLocation = []; 265 | foreach ($zones as $zone) { 266 | $zoneLocation[$zone['id']] = $zone['description']; 267 | } 268 | 269 | return $zoneLocation; 270 | } 271 | 272 | private function getTemplateIds($vmtype = null) 273 | { 274 | $templates = $this->manager->GetTemplate()['response']['storages']['storage']; 275 | $templateIds = []; 276 | if (!$vmtype) { 277 | foreach ($templates as $template) { 278 | $templateIds[$template['uuid']] = $template['title']; 279 | } 280 | } else { 281 | foreach ($templates as $template) { 282 | if ( 283 | ($template['template_type'] == 'native' && 284 | (($vmtype == 'nativewindows' && preg_match('/Windows/', $template['title'])) || 285 | ($vmtype == 'nativelinux' && !preg_match('/Windows/', $template['title'])))) || 286 | $template['template_type'] == 'cloud-init' && $vmtype == 'cloudinit' 287 | ) { 288 | $templateIds[$template['uuid']] = $template['title']; 289 | } 290 | } 291 | } 292 | return $templateIds; 293 | } 294 | 295 | private function getVmplans() 296 | { 297 | $plans = $this->manager->Getplans()['response']['plans']['plan']; 298 | $Vmplans = ['custom' => $this->_LANG['custom'] . ' [ ' . $this->_LANG['custDesc'] . ' ]']; 299 | foreach ($plans as $plan) { 300 | $PlanDesc = $this->_LANG['cpu'] . ': ' . $plan['core_number'] . ' ' . $this->_LANG['core'] . ' / ' . $this->_LANG['memory'] . ': ' 301 | . $plan['memory_amount'] . $this->_LANG['MB'] . ' / ' . $this->_LANG['Disk'] . ': ' . $plan['storage_size'] . $this->_LANG['GB']; 302 | $Vmplans[$plan['name']] = $plan['name'] . ' [ ' . $PlanDesc . ' ]'; 303 | } 304 | 305 | return $Vmplans; 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/overview.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 |
13 |
14 | 15 |

{$_LANG.basicdetails}

16 |

{$_LANG.basicdescription}

17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 33 | 34 | 35 |
{$_LANG['overviewdetails']}
{$_LANG['Hostname']} {$vm['details']['hostname']}
{$_LANG['IPAddress']} {$vm['details']['ip']}
{$_LANG['username']} {$username}
{$_LANG['password']} 25 | 26 | 27 |
{$_LANG['OS']} {$vm['details']['template']}
{$_LANG['Status']} 30 | {$vm['details']['status']|upper} 31 | 32 |
{$_LANG['Location']} {$vm['details']['location']}
36 | 37 |
38 | 39 | 67 |
68 | 69 | 70 |
71 |
72 |
73 | 74 |

{$_LANG['vnc']['title']}

75 |

{$_LANG['vnc']['description']}

76 |
77 | 78 |
79 | 82 | 83 | {if $vm['details']['vnc'] == 'on'} 84 | {$_LANG['vnc']['on']} 85 | {else} 86 | {$_LANG['vnc']['off']} 87 | {/if} 88 | 89 |
90 |
91 | 92 | 93 |
94 | 95 |
96 |
97 | 98 | 101 | 102 | 103 | 106 | 107 | 108 | 111 | 112 | 113 |
114 | 115 |
116 | 117 | 120 | 121 | {if $vm['details']['vnc'] == 'on'} 122 | 123 | 124 | {else} 125 | 126 | 127 | {/if} 128 |
129 |
130 | 131 | 132 | 133 | 134 | 135 | 158 | 159 |
160 |
161 |
162 |
163 | 164 |

{$_LANG.network.title}

165 |

{$_LANG.network.description}

166 |
167 |
168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 |
{$_LANG["IPAddresses"]}
{$_LANG['Family']}{$_LANG['IPAddress']}{$_LANG['reversePTR']}{$_LANG['Action']}
182 |
183 |
184 | 185 | 186 | 187 | 188 |
189 |
190 |
191 |
192 | 193 |

{$_LANG.server.title}

194 |

{$_LANG.server.description}

195 |
196 |
197 | 198 |
199 | 200 | 201 | 202 | 203 | 204 | 209 | 210 | 211 | 216 | 217 | 218 | 223 | 224 | 225 | 231 |
232 | 233 |
234 |
235 |
236 | 237 | 238 | 239 |
240 |
241 |
242 |
243 | 244 |

{$_LANG.bandwidth.title}

245 |

{$_LANG.bandwidth.description}

246 |
247 |
248 |
249 | 250 | 256 | 257 |
258 | 259 | 260 |
261 | 262 | 263 | 590 | -------------------------------------------------------------------------------- /modules/servers/upCloudVps/templates/assets/css/layout.css: -------------------------------------------------------------------------------- 1 | 2 | .module-main-header { 3 | margin: 0 0 15px 0 !important; 4 | height: 40px; 5 | overflow: hidden; 6 | } 7 | 8 | .module-main-header h2 { 9 | display: inline; 10 | margin: 0 0 0 5px; 11 | text-shadow: none; 12 | } 13 | 14 | .module-main-header .btn-back { 15 | vertical-align: top; 16 | padding: 0 12px; 17 | box-shadow: none; 18 | } 19 | 20 | .module-main-header .btn-back:hover { 21 | color: #000; 22 | } 23 | 24 | #uc-wrapper h1, 25 | #uc-wrapper h2, 26 | #uc-wrapper h3, 27 | #uc-wrapper h4, 28 | #uc-wrapper h5, 29 | #uc-wrapper h6 { 30 | margin: 0; 31 | letter-spacing: 0; 32 | text-shadow: none; 33 | } 34 | 35 | #uc-wrapper ul { 36 | padding: 0; 37 | margin: 0; 38 | } 39 | 40 | #uc-wrapper .mb-20 { 41 | margin-bottom: 20px!important; 42 | } 43 | 44 | #uc-wrapper .ml-10 { 45 | margin-left: 10px!important; 46 | } 47 | 48 | #uc-wrapper .mr-5 { 49 | margin-right: 5px!important; 50 | } 51 | 52 | #uc-wrapper .ml-5 { 53 | margin-left: 5px!important; 54 | } 55 | 56 | #uc-wrapper.module-container { 57 | position: relative; 58 | } 59 | 60 | #uc-wrapper [class^="icon-"], #uc-wrapper [class*=" icon-"] { 61 | background-position: 0 0; 62 | background-image: none; 63 | } 64 | 65 | #uc-wrapper .buttons-content .big-button { 66 | width: 100%; 67 | display: block; 68 | padding: 0; 69 | box-shadow: 0 0 5px rgba(0,0,0,0.1); 70 | position: relative; 71 | margin-bottom: 20px; 72 | } 73 | 74 | #uc-wrapper .buttons-content .big-button.disabled { 75 | position: relative; 76 | } 77 | 78 | #uc-wrapper .buttons-content .big-button.disabled span { 79 | opacity: 0; 80 | } 81 | 82 | #uc-wrapper .buttons-content .big-button.disabled .fa-spinner { 83 | position: absolute; 84 | left: 50%; 85 | top: 50%; 86 | margin: -6px 0 0 6px; 87 | } 88 | 89 | @media (max-width: 768px) { 90 | 91 | #uc-wrapper .buttons-content .big-button.disabled .fa-spinner { 92 | left: 50%; 93 | top: 72%; 94 | margin: 0 0 0 -7px; 95 | } 96 | 97 | } 98 | 99 | #uc-wrapper .buttons-content .big-button .icon-btn { 100 | width: 38px; 101 | height: 38px; 102 | background-repeat: no-repeat; 103 | background-size: 100%; 104 | background-position: center; 105 | } 106 | 107 | #uc-wrapper .panel-heading { 108 | display: block; 109 | } 110 | 111 | #uc-wrapper .tab-pane { 112 | padding: 20px 10px; 113 | } 114 | 115 | @media (min-width: 769px) { 116 | 117 | #uc-wrapper .buttons-content .big-button div { 118 | padding-left: 60px; 119 | display: table-cell; 120 | height: 60px; 121 | vertical-align: middle; 122 | padding-right: 10px; 123 | } 124 | 125 | #uc-wrapper .buttons-content .big-button .icon-btn { 126 | position: absolute; 127 | left: 13px; 128 | top: 12px; 129 | } 130 | 131 | } 132 | 133 | @media (max-width: 768px) { 134 | 135 | #uc-wrapper.module-container { 136 | min-height: 0!important; 137 | } 138 | 139 | #uc-wrapper .buttons-content .big-button div { 140 | padding: 10px; 141 | text-align: center; 142 | } 143 | 144 | #uc-wrapper .buttons-content .big-button .icon-btn { 145 | display: block; 146 | margin: 0 auto; 147 | } 148 | 149 | } 150 | 151 | #uc-wrapper .module-sidebar { 152 | position: absolute; 153 | top: 0; 154 | left: 0; 155 | width: 210px; 156 | } 157 | 158 | #uc-wrapper .module-sidebar .sidebar-header { 159 | margin-bottom: 13px; 160 | } 161 | 162 | #uc-wrapper .module-sidebar .sidebar-header h3 { 163 | display: inline-block; 164 | margin: 0 0 0 8px; 165 | box-shadow: none; 166 | } 167 | 168 | #uc-wrapper .module-sidebar .sidebar-header .show-hint { 169 | font-size: 20px; 170 | float: right; 171 | margin-top: 4px; 172 | } 173 | 174 | #uc-wrapper .module-sidebar .module-menu { 175 | list-style: none; 176 | margin: 0; 177 | } 178 | 179 | #uc-wrapper .module-sidebar .module-menu li a { 180 | position: relative; 181 | min-height: 40px; 182 | padding: 5px 5px 5px 45px; 183 | margin-bottom: 1px; 184 | display: block; 185 | } 186 | 187 | #uc-wrapper .module-sidebar .module-menu .icon-menu { 188 | position: absolute; 189 | left: 5px; 190 | top: 5px; 191 | width: 30px; 192 | height: 30px; 193 | background-size: 100%; 194 | background-repeat: no-repeat; 195 | background-position: center center; 196 | } 197 | 198 | #uc-wrapper .module-sidebar .btn.cp-sidebar-toggle { 199 | height: 30px; 200 | width: 30px; 201 | padding: 4px 8px; 202 | vertical-align: top; 203 | } 204 | 205 | #uc-wrapper .module-sidebar .cp-sidebar-toggle .icon-bar { 206 | display: block; 207 | height: 2px; 208 | width: 20px; 209 | margin: 0 0 4px 0; 210 | } 211 | 212 | #uc-wrapper .module-sidebar .cp-sidebar-toggle .icon-bar:last-of-type { 213 | margin-bottom: 0; 214 | } 215 | 216 | @media (min-width: 769px) { 217 | 218 | #uc-wrapper .module-menu { 219 | display: block!important; 220 | visibility: visible!important; 221 | height: 100%!important; 222 | overflow: visible; 223 | } 224 | 225 | #uc-wrapper.sidebar-closed .module-menu a:hover { 226 | width: 220px; 227 | border-radius: 4px; 228 | } 229 | 230 | #uc-wrapper.sidebar-closed .module-sidebar { 231 | width: 45px; 232 | z-index: 100; 233 | } 234 | 235 | #uc-wrapper.sidebar-closed .module-sidebar a span { 236 | display: none; 237 | } 238 | 239 | #uc-wrapper.sidebar-closed .module-sidebar a:hover span { 240 | display: block; 241 | } 242 | 243 | #uc-wrapper.sidebar-closed .sidebar-header h3 { 244 | display: none; 245 | } 246 | 247 | } 248 | 249 | @media (max-width: 768px) { 250 | 251 | #uc-wrapper .module-sidebar { 252 | position: relative; 253 | width: 100%; 254 | margin-bottom: 20px; 255 | } 256 | 257 | } 258 | 259 | /* module content */ 260 | #uc-wrapper .module-content { 261 | margin-left: 220px; 262 | } 263 | 264 | #uc-wrapper .module-sub-header, 265 | #uc-wrapper .module-header { 266 | position: relative; 267 | margin-bottom: 33px; 268 | } 269 | 270 | #uc-wrapper .module-header { 271 | padding-left: 80px; 272 | } 273 | 274 | #uc-wrapper .module-sub-header h2, 275 | #uc-wrapper .module-header h1 { 276 | margin-bottom: 5px; 277 | } 278 | 279 | #uc-wrapper .module-header.sub-page { 280 | padding-left: 0; 281 | } 282 | 283 | #uc-wrapper .module-header .btn-back { 284 | font-size: 14px; 285 | padding: 10px; 286 | vertical-align: top; 287 | line-height: 1; 288 | display: inline-block; 289 | margin-right: 5px; 290 | color: #45464C; 291 | } 292 | 293 | #uc-wrapper .module-header .icon-header { 294 | position: absolute; 295 | left: 0; 296 | top: 5px; 297 | width: 58px; 298 | height: 58px; 299 | } 300 | 301 | @media (min-width: 769px) { 302 | 303 | #uc-wrapper.sidebar-closed .module-content { 304 | margin-left: 55px; 305 | } 306 | 307 | } 308 | 309 | @media (max-width: 768px) { 310 | 311 | #uc-wrapper .module-content { 312 | margin: 0; 313 | } 314 | 315 | #uc-wrapper .module-header { 316 | position: absolute; 317 | display: none; 318 | } 319 | 320 | } 321 | 322 | #uc-wrapper .module-body h4 { 323 | margin-bottom: 15px; 324 | } 325 | 326 | /* forms */ 327 | #uc-wrapper form { 328 | margin: 0; 329 | } 330 | 331 | #uc-wrapper .form-control { 332 | height: 36px; 333 | padding: 6px 10px; 334 | margin: 0; 335 | } 336 | 337 | #uc-wrapper .form-horizontal .radio, #uc-wrapper .form-horizontal .checkbox, #uc-wrapper .form-horizontal .radio-inline, #uc-wrapper .form-horizontal .checkbox-inline { 338 | padding: 0; 339 | } 340 | 341 | #uc-wrapper input[type="radio"], 342 | #uc-wrapper input[type="checkbox"] { 343 | margin-top: 8px; 344 | } 345 | 346 | #uc-wrapper .form-fluid span:not(.input-group-addon), 347 | #uc-wrapper .form-group span:not(.input-group-addon) { 348 | padding: 0 5px 0 0; 349 | } 350 | 351 | #uc-wrapper .well .form-actions { 352 | margin: 20px -19px 0; 353 | padding: 20px 20px 0 20px; 354 | } 355 | 356 | #uc-wrapper .help-block { 357 | margin-bottom: 0; 358 | } 359 | 360 | #uc-wrapper .help-block:empty { 361 | display: none; 362 | } 363 | 364 | @media (max-width: 992px) { 365 | 366 | #uc-wrapper label.control-label.col-md-3 { 367 | width: 100%; 368 | float: none; 369 | text-align: left; 370 | margin-bottom: 5px; 371 | } 372 | 373 | #uc-wrapper label.control-label.col-md-3:empty { 374 | display: none; 375 | } 376 | 377 | } 378 | 379 | @media (max-width: 768px) { 380 | 381 | #uc-wrapper label.control-label { 382 | width: 100%; 383 | float: none; 384 | text-align: left; 385 | } 386 | 387 | } 388 | 389 | @media (min-width: 768px) { 390 | 391 | #uc-wrapper label.control-label { 392 | padding-top: 9px!important; 393 | } 394 | 395 | } 396 | 397 | #uc-wrapper .advanced-options .options-body { 398 | padding: 20px; 399 | } 400 | 401 | /* pw strength box */ 402 | #uc-wrapper #pwstrengthbox { 403 | color: white; 404 | border-radius: 3px; 405 | background-color: #F00; 406 | text-align: center; 407 | line-height: 26px; 408 | width: 100%; 409 | } 410 | 411 | /* buttons */ 412 | #uc-wrapper .btn { 413 | border-radius: 3px; 414 | height: 36px; 415 | } 416 | 417 | #uc-wrapper .btn:not(.btn-icon) { 418 | min-width: 60px; 419 | } 420 | 421 | #uc-wrapper .btn-sm { 422 | height: 28px; 423 | line-height: 18px!important; 424 | } 425 | 426 | #uc-wrapper .btn-sm span { 427 | line-height: 18px!important; 428 | vertical-align: top; 429 | } 430 | 431 | #uc-wrapper .btn.btn-icon { 432 | position: relative; 433 | height: 22px; 434 | width: 22px; 435 | padding: 0; 436 | line-height: 22px; 437 | text-align: center; 438 | } 439 | 440 | #uc-wrapper .dropdown .btn.btn-icon { 441 | width: 30px; 442 | } 443 | 444 | #uc-wrapper .btn + .btn { 445 | margin-left: 10px; 446 | } 447 | 448 | @media (max-width: 500px) { 449 | 450 | #uc-wrapper .btn-block-mobile { 451 | margin-left: 0!important; 452 | width: 100%; 453 | } 454 | 455 | #uc-wrapper .btn-block-mobile + .btn-block-mobile { 456 | margin-top: 10px; 457 | } 458 | 459 | } 460 | 461 | /* helper classes */ 462 | #uc-wrapper .module-body .section:not(:last-of-type) { 463 | margin-bottom: 50px; 464 | } 465 | 466 | #uc-wrapper .form-fluid, 467 | #uc-wrapper .row-fluid { 468 | display: table; 469 | padding: 0; 470 | } 471 | 472 | #uc-wrapper .form-fluid > .fluid-100, 473 | #uc-wrapper .row-fluid > .fluid-100 { 474 | position: relative; 475 | display: table-cell; 476 | width: 100%; 477 | vertical-align: top; 478 | } 479 | 480 | #uc-wrapper .form-fluid > .fluid-100 > *, #uc-wrapper .row-fluid > .fluid-100 > * { 481 | width: 100%; 482 | } 483 | 484 | #uc-wrapper .form-fluid > .fluid-0, #uc-wrapper .row-fluid > .fluid-0 { 485 | position: relative; 486 | float: none; 487 | display: table-cell; 488 | width: 0; 489 | padding: 0; 490 | white-space: nowrap; 491 | vertical-align: top; 492 | } 493 | 494 | #uc-wrapper .form-fluid > .fluid-0 .form-control, #uc-wrapper .row-fluid > .fluid-0 .form-control { 495 | min-width: 55px; 496 | } 497 | 498 | @media (min-width: 768px) { 499 | 500 | #uc-wrapper .form-fluid-sm, 501 | #uc-wrapper .row-fluid-sm { 502 | display: table; 503 | padding: 0; 504 | } 505 | 506 | #uc-wrapper .form-fluid-sm > .fluid-100, 507 | #uc-wrapper .row-fluid-sm > .fluid-100 { 508 | position: relative; 509 | display: table-cell; 510 | width: 100%; 511 | vertical-align: top; 512 | } 513 | 514 | #uc-wrapper .form-fluid-sm > .fluid-100 > *, 515 | #uc-wrapper .row-fluid-sm > .fluid-100 > * { 516 | width: 100%; 517 | } 518 | 519 | #uc-wrapper .form-fluid-sm > .fluid-0, 520 | #uc-wrapper .row-fluid-sm > .fluid-0 { 521 | position: relative; 522 | float: none; 523 | display: table-cell; 524 | width: 0; 525 | padding: 0; 526 | white-space: nowrap; 527 | vertical-align: top; 528 | } 529 | 530 | #uc-wrapper .form-fluid-sm > .fluid-0 .form-control, 531 | #uc-wrapper .row-fluid-sm > .fluid-0 .form-control { 532 | min-width: 55px; 533 | } 534 | 535 | } 536 | 537 | #uc-wrapper .table-header { 538 | display: table; 539 | width: 100%; 540 | line-height: 14px; 541 | margin-bottom: 8px; 542 | } 543 | 544 | #uc-wrapper .table-header > .header-title { 545 | display: table-cell; 546 | width: 0%; 547 | white-space: nowrap; 548 | vertical-align: middle; 549 | } 550 | 551 | #uc-wrapper .table-header > .header-actions { 552 | display: table-cell; 553 | width: 100%; 554 | } 555 | 556 | #uc-wrapper .table-header .header-search { 557 | float: right; 558 | } 559 | 560 | #uc-wrapper .table-header .header-title > h4 { 561 | float: left; 562 | margin: 0 20px 0 0; 563 | line-height: 36px; 564 | } 565 | 566 | #uc-wrapper .input-icon { 567 | position: relative; 568 | display: inline-block; 569 | vertical-align: top; 570 | } 571 | 572 | #uc-wrapper .input-icon > .fa { 573 | position: absolute; 574 | left: 15px; 575 | top: 50%; 576 | margin-top: -7px; 577 | } 578 | 579 | #uc-wrapper .table-header .header-search input { 580 | width: 240px; 581 | } 582 | 583 | #uc-wrapper .input-icon > .form-control { 584 | padding-left: 40px; 585 | } 586 | 587 | 588 | #uc-wrapper .table > tbody > tr > td ul { 589 | list-style: none; 590 | } 591 | 592 | #uc-wrapper .table > tbody > tr > td ul li { 593 | line-height: 22px; 594 | } 595 | 596 | #uc-wrapper .table input[type="radio"], 597 | #uc-wrapper .table input[type="checkbox"] { 598 | margin-top: 0; 599 | } 600 | 601 | #uc-wrapper .table-app img { 602 | width: 22px; 603 | margin: -4px 10px 0 0; 604 | vertical-align: top; 605 | } 606 | 607 | #uc-wrapper .pr-10 { 608 | padding-right: 10px!important; 609 | } 610 | 611 | #uc-wrapper .row { 612 | padding: 0; 613 | } 614 | 615 | #uc-wrapper .form-horizontal .row, 616 | #uc-wrapper .form-horizontal .form-group { 617 | margin-left: -5px; 618 | margin-right: -5px; 619 | } 620 | 621 | #uc-wrapper .form-horizontal [class*="col-"] { 622 | padding-left: 5px; 623 | padding-right: 5px; 624 | } 625 | 626 | #uc-wrapper .file-manager .btn { 627 | height: auto; 628 | padding: 6px 12px; 629 | float: left; 630 | line-height: 22px; 631 | } 632 | 633 | @media (max-width: 500px) { 634 | 635 | #uc-wrapper .file-manager .btn { 636 | padding: 6px 5px; 637 | } 638 | 639 | } 640 | 641 | #uc-wrapper .file-manager .dropdown { 642 | margin-left: 10px; 643 | } 644 | 645 | #uc-wrapper .file-manager .dropdown-menu li { 646 | margin: 0 10px 0 0 647 | } 648 | 649 | #uc-wrapper .file-manager .btn i { 650 | display: block; 651 | margin: 5px auto; 652 | } 653 | 654 | #uc-wrapper .breadcrumb { 655 | margin: 15px 0 5px 0; 656 | } 657 | 658 | #uc-wrapper .breadcrumb > .fa-folder-o { 659 | margin-right: 5px; 660 | } 661 | 662 | #uc-wrapper .dropdown-right { 663 | left: auto; 664 | right: 0; 665 | } 666 | 667 | #uc-wrapper .btn .caret { 668 | margin-top: 0; 669 | opacity: 1; 670 | margin-left: 5px; 671 | } 672 | 673 | #uc-wrapper table .dropdown-menu > li > a > .fa { 674 | margin-right: 7px; 675 | width: 14px; 676 | } 677 | 678 | @media (min-width: 992px) { 679 | 680 | #uc-wrapper .dropdown-mobile .dropdown-menu { 681 | overflow: hidden; 682 | float: none; 683 | display: block; 684 | position: relative; 685 | left: auto; 686 | top: auto; 687 | border: 0; 688 | box-shadow: none; 689 | background: none; 690 | margin: 0; 691 | padding: 0; 692 | } 693 | 694 | #uc-wrapper .dropdown-mobile .dropdown-menu li { 695 | float: left; 696 | } 697 | 698 | } 699 | 700 | @media (max-width: 991px) { 701 | 702 | #uc-wrapper .dropdown-mobile .dropdown-menu li { 703 | float: none; 704 | margin: 0; 705 | } 706 | 707 | #uc-wrapper .dropdown-mobile .dropdown-menu li .btn { 708 | float: none; 709 | border: 0; 710 | padding: 3px 20px; 711 | text-align: left; 712 | } 713 | 714 | #uc-wrapper .dropdown-mobile .dropdown-menu li .btn i { 715 | display: inline; 716 | margin-right: 5px; 717 | } 718 | 719 | } 720 | 721 | #uc-wrapper .modal { 722 | width: 100%; 723 | margin: 0; 724 | background: none; 725 | } 726 | 727 | #uc-wrapper .modal .modal-header { 728 | word-break: break-all; 729 | } 730 | 731 | #uc-wrapper .modal .modal-title { 732 | margin-bottom: 0; 733 | } 734 | 735 | #uc-wrapper .modal-body { 736 | max-height: 600px; 737 | } 738 | 739 | #uc-wrapper .btn.disabled { 740 | position: relative; 741 | } 742 | 743 | #uc-wrapper .btn.disabled :not(i) { 744 | opacity: 0; 745 | } 746 | 747 | #uc-wrapper .btn.disabled .fa-spinner { 748 | position: absolute; 749 | left: 50%; 750 | top: 50%; 751 | margin: -6px 0 0 -6px; 752 | } 753 | 754 | #uc-wrapper .btn.btn-icon.disabled .fa-spinner { 755 | margin: -11px 0 0 -7px; 756 | } 757 | 758 | @media (max-width: 768px) { 759 | 760 | #uc-wrapper .modal-large .modal-dialog { 761 | width: auto; 762 | } 763 | 764 | } 765 | 766 | @media (min-width: 768px) { 767 | 768 | #uc-wrapper .modal-large .modal-dialog { 769 | width: 750px; 770 | } 771 | 772 | } 773 | 774 | @media (min-width: 992px) { 775 | 776 | #uc-wrapper .modal-large .modal-dialog { 777 | width: 960px; 778 | } 779 | 780 | } 781 | 782 | #uc-wrapper .modal .modal-backdrop { 783 | position: fixed; 784 | top: 0; 785 | right: 0; 786 | bottom: 0; 787 | left: 0; 788 | z-index: 1040; 789 | background-color: #000; 790 | } 791 | 792 | @media (min-width: 992px) { 793 | 794 | .show-md { 795 | display: none!important; 796 | } 797 | 798 | } 799 | 800 | @media (min-width: 768px) { 801 | 802 | .show-sm { 803 | display: none!important; 804 | } 805 | 806 | } 807 | 808 | @media (max-width: 768px) { 809 | 810 | .hide-sm { 811 | display: none!important; 812 | } 813 | 814 | } 815 | 816 | /* table grid */ 817 | #uc-wrapper .table tbody tr td { 818 | word-break: break-all; 819 | } 820 | 821 | @media (min-width: 768px) { 822 | 823 | #uc-wrapper .table tbody tr td.no-wrap, 824 | #uc-wrapper #cron-table tr td:first-of-type, 825 | #uc-wrapper .table tbody tr td.cell-actions { 826 | white-space: nowrap; 827 | } 828 | 829 | } 830 | 831 | @media (max-width: 650px) { 832 | 833 | #uc-wrapper .table-header > .header-title { 834 | display: block; 835 | } 836 | 837 | #uc-wrapper .table-header > .header-actions { 838 | display: block; 839 | margin-bottom: 12px; 840 | overflow: hidden; 841 | } 842 | 843 | #uc-wrapper .table-header .header-search { 844 | float: none; 845 | } 846 | 847 | #uc-wrapper .table-header .input-icon { 848 | display: block; 849 | } 850 | 851 | #uc-wrapper .table-header .header-search input { 852 | width: 100%; 853 | } 854 | 855 | #uc-wrapper .table-header .header-actions .btn { 856 | float: none!important; 857 | } 858 | 859 | } 860 | 861 | @media (max-width: 768px) { 862 | 863 | #uc-wrapper .table thead { 864 | display: none; 865 | } 866 | 867 | #uc-wrapper .table tbody tr { 868 | border-top: 1px solid #E4E8F0; 869 | } 870 | 871 | #uc-wrapper .table .cell-sm-1, 872 | #uc-wrapper .table .cell-sm-2, 873 | #uc-wrapper .table .cell-sm-3, 874 | #uc-wrapper .table .cell-sm-4, 875 | #uc-wrapper .table .cell-sm-5, 876 | #uc-wrapper .table .cell-sm-6, 877 | #uc-wrapper .table .cell-sm-7, 878 | #uc-wrapper .table .cell-sm-8, 879 | #uc-wrapper .table .cell-sm-9, 880 | #uc-wrapper .table .cell-sm-10, 881 | #uc-wrapper .table .cell-sm-11, 882 | #uc-wrapper .table .cell-sm-12 { 883 | display: block; 884 | float: left; 885 | } 886 | 887 | #uc-wrapper .table .cell-sm-12 { 888 | width: 100%; 889 | } 890 | 891 | #uc-wrapper .table .cell-sm-11 { 892 | width: 91.66666667%; 893 | } 894 | 895 | #uc-wrapper .table .cell-sm-10 { 896 | width: 83.33333333%; 897 | } 898 | 899 | #uc-wrapper .table .cell-sm-9 { 900 | width: 75%; 901 | } 902 | 903 | #uc-wrapper .table .cell-sm-8 { 904 | width: 66.66666667%; 905 | } 906 | 907 | #uc-wrapper .table .cell-sm-7 { 908 | width: 58.33333333%; 909 | } 910 | 911 | #uc-wrapper .table .cell-sm-6 { 912 | width: 50%; 913 | } 914 | 915 | #uc-wrapper .table .cell-sm-5 { 916 | width: 41.66666667%; 917 | } 918 | 919 | #uc-wrapper .table .cell-sm-4 { 920 | width: 33.33333333%; 921 | } 922 | 923 | #uc-wrapper .table .cell-sm-3 { 924 | width: 25%; 925 | } 926 | 927 | #uc-wrapper .table .cell-sm-2 { 928 | width: 16.66666667%; 929 | } 930 | 931 | #uc-wrapper .table .cell-sm-1 { 932 | width: 8.33333333%; 933 | } 934 | 935 | #uc-wrapper .table > tbody > tr > td, #uc-wrapper .table > tfoot > tr > td { 936 | line-height: 1.2; 937 | height: auto; 938 | border: 0; 939 | } 940 | 941 | #uc-wrapper .table tr td:not(.cell-checkbox):before { 942 | content: attr(data-label); 943 | padding-right: 10px; 944 | color: #A1A5B3; 945 | } 946 | 947 | #uc-wrapper .table > tbody > tr > td.cell-actions { 948 | text-align: left!important; 949 | } 950 | 951 | #uc-wrapper .table > tbody > tr > td.cell-actions .btn-icon { 952 | width: 35px; 953 | } 954 | 955 | #uc-wrapper .table > tbody > tr > td.cell-actions .btn-icon .fa { 956 | font-size: 18px!important; 957 | } 958 | 959 | #uc-wrapper .table.table-checkbox tbody tr { 960 | padding-left: 25px; 961 | position: relative; 962 | display: inline-block; 963 | } 964 | 965 | #uc-wrapper .table > tbody > tr > .cell-checkbox { 966 | position: absolute; 967 | left: 0; 968 | top: 0; 969 | width: 34px; 970 | height: 100%; 971 | } 972 | 973 | #uc-wrapper .table > tbody > tr > .cell-checkbox input[type="checkbox"] { 974 | margin: 0; 975 | } 976 | 977 | #uc-wrapper .table > tbody > tr > td.cell-actions .dropdown-menu { 978 | left: 0; 979 | right: auto; 980 | } 981 | 982 | } 983 | 984 | #uc-wrapper .alert { 985 | overflow: hidden; 986 | } 987 | 988 | #uc-wrapper .alert .close { 989 | right: 0; 990 | } 991 | 992 | #uc-wrapper .tab-content { 993 | overflow: visible; 994 | position: relative; 995 | } 996 | 997 | #uc-wrapper .tab-content .collapse { 998 | position: static; 999 | overflow: visible; 1000 | } 1001 | 1002 | #uc-wrapper a.panel-heading.collapsed .fa-minus { 1003 | display: none; 1004 | } 1005 | 1006 | #uc-wrapper a.panel-heading:not(.collapsed) .fa-plus { 1007 | display: none; 1008 | } 1009 | 1010 | #uc-wrapper #install-new-tab #collapse-toggle { 1011 | position: absolute; 1012 | right: 10px; 1013 | top: -40px; 1014 | } 1015 | 1016 | @media (max-width: 768px) { 1017 | 1018 | #uc-wrapper .tooltip-inner { 1019 | max-width: 500px; 1020 | } 1021 | 1022 | #uc-wrapper .sidebar-header .tooltip-inner, 1023 | #install-new-tab .tooltip-inner { 1024 | min-width: 300px; 1025 | } 1026 | 1027 | #uc-wrapper .tooltip-arrow { 1028 | display: none; 1029 | } 1030 | 1031 | } 1032 | 1033 | #uc-wrapper .checkbox-inline-td { 1034 | align-items: center; 1035 | display: flex; 1036 | margin-bottom: 0px !important; 1037 | } 1038 | 1039 | #uc-wrapper .checkbox-inline-td span { 1040 | margin-left: 2px; 1041 | } 1042 | --------------------------------------------------------------------------------