├── en
└── welcomeRes.js
├── .gitignore
├── config.json.sample
├── .gitattributes
├── web
├── js
│ ├── directives.js
│ ├── app.js
│ ├── filters.js
│ ├── services.js
│ ├── vendor
│ │ └── xml2json.js
│ └── controllers.js
├── partials
│ ├── vm-list.html
│ ├── home.html
│ └── vm.html
├── index.html
└── css
│ └── app.css
├── encrypt.php
├── README.md
└── proxy.php
/en/welcomeRes.js:
--------------------------------------------------------------------------------
1 | var ID_EESX = "VMware ESXi 5";
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .buildpath
2 | .idea
3 | .project
4 | .settings
5 | config.json
6 | tmp
7 |
--------------------------------------------------------------------------------
/config.json.sample:
--------------------------------------------------------------------------------
1 | {
2 | "host" : "",
3 | // generate a encrypted string with mob/encrypt.php
4 | "username" : "",
5 | // generate a encrypted string with mob/encrypt.php
6 | "password" : ""
7 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * eol=lf
2 |
3 | *.eot binary
4 | *.gif binary
5 | *.ico binary
6 | *.jpg binary
7 | *.phar binary
8 | *.png binary
9 | *.svg binary
10 | *.swf binary
11 | *.ttf binary
12 | *.woff binary
13 |
14 | *.php diff=php
15 | *.html diff=html
16 | *.js diff=js
17 | *.css diff=css
18 |
--------------------------------------------------------------------------------
/web/js/directives.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* Directives */
4 |
5 | (function(module) {
6 |
7 | module.directive("preventDefault", function() {
8 | return function(scope, element, attrs) {
9 | $(element).on("click", function(event) {
10 | event.preventDefault();
11 | });
12 | };
13 | });
14 |
15 | })(angular.module("esxiApp.directives", []));
16 |
--------------------------------------------------------------------------------
/web/js/app.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | (function(window) {
4 |
5 | var depends = [ "ngRoute", "esxiApp.filters", "esxiApp.services", "esxiApp.directives", "esxiApp.controllers" ];
6 | // Declare app level module which depends on filters, and services
7 | var app = angular.module("esxiApp", depends);
8 | app.config([ "$routeProvider", "$compileProvider", function($routeProvider, $compileProvider) {
9 | $routeProvider.when("/", {
10 | templateUrl : "partials/home.html",
11 | controller : "HomeController"
12 | });
13 |
14 | $routeProvider.when("/vm", {
15 | templateUrl : "partials/vm-list.html",
16 | controller : "VmListController"
17 | });
18 | $routeProvider.when("/vm/:id", {
19 | templateUrl : "partials/vm.html",
20 | controller : "VmController"
21 | });
22 |
23 | $routeProvider.otherwise({
24 | redirectTo : "/"
25 | });
26 |
27 | $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|vmrc):/);
28 | } ]);
29 |
30 | // REPLACING BROKEN IMAGES WITH A 1x1 GIF
31 | // --------------------------------------------------
32 | if (window.addEventListener) {
33 | window.addEventListener("error", function(e) {
34 | if (e.target.nodeName === "IMG") {
35 | e.target.src = "data:image/gif;base64," + "R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
36 | }
37 | }, true);
38 | }
39 |
40 | })(this);
41 |
--------------------------------------------------------------------------------
/encrypt.php:
--------------------------------------------------------------------------------
1 | $_POST['hostname'],
24 | 'username' => encrypt($_POST['username']),
25 | 'password' => encrypt($_POST['password']),
26 | )
27 | )
28 | );
29 | $generated = true;
30 | }
31 |
32 | ?>
33 |
34 |
35 |
36 |
37 |
56 |
57 |
--------------------------------------------------------------------------------
/web/partials/vm-list.html:
--------------------------------------------------------------------------------
1 |
2 |
Total Vms: {{totalVms}}
3 |
4 |
5 |
6 |
7 | | Name |
8 | Host CPU |
9 | Guest Mem |
10 | Host Mem |
11 | Disk Space |
12 |
13 |
14 |
15 |
16 |
17 |
18 | |
19 |
23 | |
24 |
25 | {{vm.info.quickStats.overallCpuUsage || '0'}} MHz
26 | |
27 |
28 | {{vm.info.quickStats.guestMemoryUsage || '0'}} MB
29 | |
30 |
31 | {{vm.info.quickStats.hostMemoryUsage || '0'}} MB
32 | |
33 |
34 | {{(vm.info.storage.committed | roundSpace:2) || ""}}
35 | / {{(((vm.info.storage.uncommitted * 1) + (vm.info.storage.committed * 1)) | roundSpace:2) || ""}}
36 | |
37 |
38 |
39 |
40 |
41 |
42 | Refresh
43 |
44 |
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | esxi-simple-web
2 | ===============
3 |
4 | Basic controls straight from the esxi host in the browser
5 |
6 | This allows you to run basic operations against the ESXi MOB api from a simple
7 | web interface straight from the ESXi host without the need for the vCenter web client.
8 |
9 | Inspired by virtuallyGhetto's [Ghetto webAccess for ESXi](http://www.virtuallyghetto.com/2011/12/ghetto-webaccess-for-esxi.html)
10 |
11 | ## Deprecation Notice
12 |
13 | This method is now deprecated as ESXi 6+ has a web ui built in now using this fling: [ESXi Embedded Host Client](https://labs.vmware.com/flings/esxi-embedded-host-client)
14 |
15 | ## Installation & Usage
16 |
17 | Copy over the web directory to your esxi host with
18 |
19 | ```bash
20 | scp -r web {ESXi hostname}:/usr/lib/vmware/hostd/docroot/
21 | ```
22 |
23 | Then navigate to `https://{ESXi hostname}/web/`, accept the certificate error and
24 | enter your username/password for the host.
25 |
26 | ## Development
27 |
28 | Requires php >= 5.4.0
29 |
30 | Start the development server with
31 |
32 | ```bash
33 | php -S 127.0.0.1:8080 proxy.php
34 | ```
35 |
36 | In your browser go to `http://localhost:8080/encrypt.php` to generate the config file.
37 | Afterwards go to your browser and visit `http://localhost:8080/web/` and start developing!
38 |
39 | ## Notes
40 |
41 | In ESXi 6, the vSphere MOB is disabled by default, this web view requires that it be enabled. virtuallyGhetto
42 | has already solved this issue in [Quick Tip - vSphere MOB is disabled by default in ESXi 6.0](http://www.virtuallyghetto.com/2015/02/quick-tip-vsphere-mob-is-disabled-by-default-in-esxi-6-0.html).
43 |
44 | In the ESXi shell, enable it with the command:
45 | ```bash
46 | vim-cmd hostsvc/advopt/update Config.HostAgent.plugins.solo.enableMob bool true
47 | ```
48 |
49 | Verify that it's true with the command:
50 | ```bash
51 | vim-cmd hostsvc/advopt/view Config.HostAgent.plugins.solo.enableMob
52 | ```
53 |
54 | You can also enable it manually using vsphere by selecting the parent node of the vm tree, configuration tab, click Advanced Settings under Software, expand Config > HostAgent > Plugins > solo > enableMob, and set it to true.
55 |
--------------------------------------------------------------------------------
/proxy.php:
--------------------------------------------------------------------------------
1 | true,
23 | CURLOPT_SSL_VERIFYPEER => false,
24 | CURLOPT_SSL_VERIFYHOST => 0,
25 | CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
26 | CURLOPT_RETURNTRANSFER => true,
27 | CURLOPT_CONNECTTIMEOUT => 5,
28 | CURLOPT_AUTOREFERER => true,
29 | CURLOPT_REFERER => '',
30 | CURLOPT_HEADER => false,
31 | CURLOPT_FRESH_CONNECT => true,
32 | CURLOPT_HEADERFUNCTION => function ($curl, $header)
33 | {
34 | if (strpos($header, 'Set-Cookie:') === 0
35 | || strpos($header, 'Content-Type:') === 0
36 | ) {
37 | header($header);
38 | }
39 | return strlen($header);
40 | }
41 | );
42 | if ($config['username']) {
43 | $options[CURLOPT_USERPWD] = trim(decrypt($config['username'])) . ':' . trim(decrypt($config['password']));
44 | $options[CURLOPT_HTTPAUTH] = CURLAUTH_ANY;
45 | }
46 |
47 | // initializes this curl object
48 | $curl = curl_init();
49 | $url = 'https://' . $config['host'] . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
50 | // append get parameters
51 | if ($_GET) {
52 | $url .= '?' . http_build_query($_GET);
53 | }
54 | $options[CURLOPT_URL] = $url;
55 | if ($_POST) {
56 | $options[CURLOPT_POST] = true;
57 | $options[CURLOPT_POSTFIELDS] = http_build_query($_POST);
58 | }
59 | if ($_COOKIE) {
60 | $options[CURLOPT_COOKIE] = urldecode(http_build_query($_COOKIE, null, '; '));
61 | }
62 |
63 | // set all options
64 | curl_setopt_array($curl, $options);
65 | // save debugging information
66 | echo curl_exec($curl);
67 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
41 |
42 |
45 |
46 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/web/js/filters.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* Filters */
4 | (function(module) {
5 |
6 | var hasOwn = Object.prototype.hasOwnProperty;
7 | module.filter("toArray", function() {
8 | return function(o) {
9 | if (o === null || typeof o !== "object" || o.length) {
10 | return o;
11 | }
12 | var k = [], i;
13 | for (i in o) {
14 | if (hasOwn.call(o, i)) {
15 | k[k.length] = o[i];
16 | }
17 | }
18 | return k;
19 | };
20 | });
21 |
22 | module.filter("timeWords", function() {
23 | var pattern = /\$/g, f = {
24 | s : "less than a minute",
25 | i : "about a minute",
26 | ii : "$ minutes",
27 | h : "about an hour",
28 | hh : "about $ hours",
29 | d : "a day",
30 | dd : "$ days",
31 | m : "about a month",
32 | mm : "$ months",
33 | y : "about a year",
34 | yy : "$ years"
35 | };
36 |
37 | return function(distance) {
38 | if (!distance) {
39 | return distance;
40 | }
41 | distance = parseInt(distance, 10) * 1000;
42 | var s = Math.abs(distance) / 1000, m = s / 60, h = m / 60, d = h / 24, y = d / 365;
43 |
44 | return s < 45 && f.s.replace(pattern, s) || s < 90 && f.i.replace(pattern, 1) || m < 45 && f.ii.replace(pattern, Math.round(m)) || m < 90 && f.h.replace(pattern, 1) || h < 24 && f.hh.replace(pattern, Math.round(h)) || h < 48
45 | && f.d.replace(pattern, 1) || d < 30 && f.dd.replace(pattern, Math.floor(d)) || d < 60 && f.m.replace(pattern, 1) || d < 365 && f.mm.replace(pattern, Math.floor(d / 30)) || y < 2 && f.y.replace(pattern, 1)
46 | || f.yy.replace(pattern, Math.floor(y));
47 | };
48 | });
49 |
50 | module.filter("capitalize", function() {
51 | return function(str) {
52 | if (!str) {
53 | return str;
54 | }
55 | return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
56 | };
57 | });
58 |
59 | module.filter("ucwords", function() {
60 | return function(str) {
61 | if (!str) {
62 | return str;
63 | }
64 | return str.replace(/\b[a-z]/g, function(match) {
65 | return match.toUpperCase();
66 | });
67 | };
68 | });
69 |
70 | module.filter("roundMemory", function() {
71 | return function(memory, divisor, precision) {
72 | if (!memory) {
73 | return memory;
74 | }
75 | return (parseInt(memory, 10) / Math.pow(1024, divisor || 0)).toFixed(precision || 0);
76 | };
77 | });
78 |
79 | module.filter("roundSpace", function() {
80 | return function(space, decimal) {
81 | if (!space) {
82 | return space;
83 | }
84 | var i = 0, b = parseInt(space, 10);
85 | while ((b / 1024) > 1) {
86 | b /= 1024;
87 | i++;
88 | }
89 | return b.toFixed(decimal === 0 || decimal ? decimal : 2) + " " + [ "B", "KB", "MB", "GB", "TB" ][i];
90 | };
91 | });
92 |
93 | module.filter("camelcaseToSpace", function() {
94 | return function(str) {
95 | if (!str) {
96 | return str;
97 | }
98 | return str.split(/(?=[A-Z])/).join(" ").toLowerCase();
99 | };
100 | });
101 |
102 | })(angular.module("esxiApp.filters", []));
103 |
--------------------------------------------------------------------------------
/web/js/services.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* Services */
4 |
5 | (function(module) {
6 |
7 | module.value("version", "0.1");
8 |
9 | module.value("hostname", document.location.hostname);
10 |
11 | module.value("esxiVersionMajor", (/^VMware ESXi (\d+)/.exec(window.ID_EESX) || [])[1] || 0);
12 | module.value("esxiVersionMinor", (/^VMware ESXi \d+\.(\d+)/.exec(window.ID_EESX) || [])[1] || 0);
13 |
14 | module.factory("esxApi", function($http) {
15 | function parseXml(xml) {
16 | var dom = null;
17 | if (window.DOMParser) {
18 | try {
19 | dom = (new DOMParser()).parseFromString(xml, "text/xml");
20 | } catch (e) {
21 | dom = null;
22 | }
23 | } else if (window.ActiveXObject) {
24 | try {
25 | dom = new ActiveXObject('Microsoft.XMLDOM');
26 | dom.async = false;
27 | if (!dom.loadXML(xml)) {
28 | // parse error ..
29 | window.alert(dom.parseError.reason + dom.parseError.srcText);
30 | }
31 | } catch (e) {
32 | dom = null;
33 | }
34 | } else {
35 | alert("cannot parse xml string!");
36 | }
37 | return dom;
38 | }
39 |
40 | var xmlExtract = /\s?([\s\S]+?)\s?<\/xml>/;
41 | var htmlExtract = /\s?([\s\S]+?)\s?<\/body>/;
42 |
43 | function transformResponse(data) {
44 | var htmlData = ((htmlExtract.exec(data) || [])[1] || "").replace(/(?:)/, "").replace(/(?: