Compare commits

..

139 Commits

Author SHA1 Message Date
Julien Fontanet
261c9e2240 3.5.0-alpha2 2014-08-13 16:59:27 +02:00
Olivier Lambert
c03973867c fix message in main view when you aren't connected to any host 2014-08-13 15:31:15 +02:00
Olivier Lambert
ca6984f4a5 remove events displayed in console 2014-08-13 15:21:39 +02:00
Julien Fontanet
4fbc2680ba Jade comment. 2014-08-13 14:54:35 +02:00
Julien Fontanet
9a5b0a8104 CSS autoprefixer & CSS minification. 2014-08-13 14:53:44 +02:00
Julien Fontanet
ad998f7a63 Jade comments. 2014-08-13 14:53:30 +02:00
Olivier Lambert
91043e4b52 CSS fixes 2014-08-13 14:06:12 +02:00
Julien Fontanet
8f3bd0145b 3.5.0-alpha1 2014-08-12 15:36:39 +02:00
Julien Fontanet
ace0051147 Update deps and remove gulp-coffeelint.
- Update to latest Browserify & Watchify.
- Use ngAnnotate instead of ngMin.
- Remove gulp-coffeelint which failed to install.
2014-08-12 15:33:47 +02:00
Olivier Lambert
83720c6673 Update README.md 2014-08-12 15:24:27 +02:00
Olivier Lambert
f1ce4de1eb Update README.md
Add XOA details
2014-08-12 15:24:15 +02:00
Olivier Lambert
013b23c918 fix a dropdown menu and add a caret for better UI 2014-08-12 15:22:16 +02:00
Olivier Lambert
d03debf59c Update README.md 2014-08-12 14:32:40 +02:00
Olivier Lambert
00b55d893a Update README.md 2014-08-12 14:31:22 +02:00
Olivier Lambert
085b92df9c Update README.md 2014-08-12 14:27:36 +02:00
Olivier Lambert
8516ff7cb3 add basic actions buttons in console view, fix #121 2014-08-11 17:42:51 +02:00
Julien Fontanet
56d64d8adc Minor fix. 2014-08-11 17:18:15 +02:00
Julien Fontanet
5d68e48312 Minor fix. 2014-08-11 16:53:15 +02:00
Julien Fontanet
8b0be0a8c5 Required radios in VM creation. 2014-08-11 16:50:47 +02:00
Julien Fontanet
d6e4d6d927 Required selects in VM creation. 2014-08-11 16:43:47 +02:00
Julien Fontanet
bbfe60ffd2 All pages are now in modules. 2014-08-11 16:30:57 +02:00
Julien Fontanet
c9cc0b5ce3 Do not use shrinkwrap anymore. 2014-08-11 15:22:06 +02:00
Julien Fontanet
f0bf19f286 Still more modules. 2014-08-11 15:15:34 +02:00
Julien Fontanet
6492e55954 More modules. 2014-08-11 14:49:38 +02:00
Julien Fontanet
c9fd1d5833 Fix dropdown menus. 2014-08-11 14:17:10 +02:00
Julien Fontanet
e8f73cae6c Fix generic modal. 2014-08-11 13:42:43 +02:00
Olivier Lambert
7d436b40d0 use directly host power_state 2014-08-11 13:17:30 +02:00
Olivier Lambert
55cd6d2a74 fix small display glitch on the top of vm list in main view 2014-08-05 12:48:56 +02:00
Olivier Lambert
01a2585edf propperly display state of a host in the main view. Also remove RAM bar when host is disabled or halted 2014-08-05 12:42:13 +02:00
Olivier Lambert
bb329b73ef fix issue #132 2014-07-21 12:28:54 +02:00
Julien Fontanet
9b4008f39c Deps update. 2014-07-15 12:36:12 +02:00
Julien Fontanet
ef52f20993 Update deps. 2014-07-07 13:48:10 +02:00
Olivier Lambert
f2a207e790 add fedora in linux icon: fix #131 2014-06-28 13:45:32 +02:00
Julien Fontanet
40f502492b Initial browserify. 2014-06-19 15:45:28 +02:00
Julien Fontanet
c5f1cf94ac Minor updates. 2014-06-10 15:53:10 +02:00
Julien Fontanet
2e286d4a50 Minor fix. 2014-06-10 12:34:59 +02:00
Julien Fontanet
94a27fa79e Build updates and fix index page. 2014-06-10 12:16:55 +02:00
Julien Fontanet
0adec1dbe2 Update to gulp 3.8 2014-06-10 10:07:47 +02:00
Julien Fontanet
c38c53957d Update deps. 2014-06-09 15:39:57 +02:00
Olivier Lambert
498eb0134e add suse in linux distrib list for propper display of OS icon 2014-06-02 20:01:48 +02:00
Olivier Lambert
d34f9182c2 escape sharp char 2014-05-30 14:59:44 +02:00
Olivier Lambert
324f21cf3d finish jadifying all views 2014-05-30 13:19:13 +02:00
Olivier Lambert
200c0110fd jadify modal windows 2014-05-30 13:10:56 +02:00
Olivier Lambert
5694b0054b jadify main view 2014-05-30 13:05:55 +02:00
Olivier Lambert
8825fdcdaf jadify navbar 2014-05-30 12:29:55 +02:00
Olivier Lambert
7805bc172e jadify add vm view 2014-05-30 12:25:19 +02:00
Olivier Lambert
aa321b4b22 fix missing space in SR and jadify pool view 2014-05-30 12:21:17 +02:00
Olivier Lambert
c74df7abc5 fix missing space between buttons and jadify settings view 2014-05-30 12:15:34 +02:00
Olivier Lambert
c6ffe8978d fix missing spaces and jadify sr view 2014-05-30 12:00:32 +02:00
Olivier Lambert
6761e0df15 jadify about page 2014-05-30 11:55:28 +02:00
Olivier Lambert
cc47225f24 jadify list view 2014-05-30 11:52:50 +02:00
Olivier Lambert
58c47a325f jadify host view 2014-05-30 11:48:34 +02:00
Olivier Lambert
706f7f3b7b converted vm view to a Jade template 2014-05-30 11:12:57 +02:00
Julien Fontanet
4d67f8bd0b Process for releasing a new version. 2014-05-29 16:20:16 +01:00
Julien Fontanet
d5227e00ff Prepare npm package. 2014-05-29 16:06:23 +01:00
Julien Fontanet
62fa1d570c Minor updates. 2014-05-27 20:41:10 +01:00
Olivier Lambert
899b4ab29a restore connected stuff on VM disk 2014-05-27 20:16:08 +02:00
Olivier Lambert
a2801f0b7b remove useless checkbox 2014-05-27 20:13:35 +02:00
Olivier Lambert
84694fedef fix typo 2014-05-27 20:07:05 +02:00
Olivier Lambert
c0005741bd remove req field in VM view 2014-05-27 19:03:59 +02:00
Olivier Lambert
150a341510 add recipes 2014-05-26 20:08:42 +02:00
Olivier Lambert
e486713026 add link to the bottom of each doc part 2014-05-26 10:59:08 +02:00
Olivier Lambert
0201dfa8e3 reduce image 2014-05-26 10:55:18 +02:00
Olivier Lambert
78269a1b7b doc reorg 2014-05-26 10:52:15 +02:00
Olivier Lambert
d1bf60ab17 end doc on VM 2014-05-26 10:33:06 +02:00
Olivier Lambert
6c368dc8cb re re fix broken links 2014-05-25 14:18:34 +02:00
Olivier Lambert
10ac67c1e0 re fix broken links 2014-05-25 14:17:46 +02:00
Olivier Lambert
ba3b38d941 fix broken links 2014-05-25 14:17:07 +02:00
Olivier Lambert
47044d93d3 add ToC 2014-05-25 14:15:33 +02:00
Olivier Lambert
423ac4ea04 fix icon placement 2014-05-25 14:10:03 +02:00
Olivier Lambert
b114b40f88 add snapshot doc 2014-05-25 14:09:05 +02:00
Olivier Lambert
030ce18d65 add template doc 2014-05-25 13:55:36 +02:00
Julien Fontanet
44116b3559 Minor fix. 2014-05-25 13:49:54 +02:00
Olivier Lambert
78332c7bfd add more doc 2014-05-25 13:47:30 +02:00
Julien Fontanet
dd8ac28240 Minor fix. 2014-05-25 13:46:19 +02:00
Julien Fontanet
81818222e5 Fixes. 2014-05-25 13:44:01 +02:00
Olivier Lambert
4a25af03f4 add console icon 2014-05-25 13:32:09 +02:00
Olivier Lambert
6515d73534 add console 2014-05-25 13:27:12 +02:00
Olivier Lambert
006fc9acd3 continue to add doc 2014-05-25 13:21:15 +02:00
Olivier Lambert
1aa878b3a6 fix typo 2014-05-24 17:17:32 +02:00
Olivier Lambert
23dc475905 add doc for usage 2014-05-24 17:16:37 +02:00
Olivier Lambert
a827ab6ffd oops forgot two images 2014-05-24 17:01:50 +02:00
Olivier Lambert
f55784ce6a add action image 2014-05-24 17:00:26 +02:00
Olivier Lambert
f7110ac472 add usage doc 2014-05-24 16:58:47 +02:00
Olivier Lambert
52a94e0f47 finish layout section 2014-05-24 16:47:22 +02:00
Olivier Lambert
c25eb678a8 fix typo 2014-05-24 16:42:08 +02:00
Olivier Lambert
5128efbbf4 add more doc stuff 2014-05-24 16:41:03 +02:00
Olivier Lambert
c73ee3c531 add doc 2014-05-24 16:26:24 +02:00
Olivier Lambert
54ba67f882 add admin doc 2014-05-24 16:04:41 +02:00
Julien Fontanet
395bfe6eb5 Only admins can access the setting page (fix #77). 2014-05-24 16:00:40 +02:00
Julien Fontanet
e0ecf65aac Better integration of the setting pages. 2014-05-24 15:49:33 +02:00
Olivier Lambert
a7c651c98c fix bad image name 2014-05-24 15:49:17 +02:00
Olivier Lambert
ed473f457a add images 2014-05-24 15:48:21 +02:00
Olivier Lambert
a572c1132d more doc 2014-05-24 15:47:26 +02:00
Olivier Lambert
ba314c7135 small changes in doc 2014-05-24 15:38:43 +02:00
Olivier Lambert
a0788ab050 fix minor stuff 2014-05-24 15:34:13 +02:00
Olivier Lambert
8885f0be10 add doc on config file 2014-05-24 15:33:31 +02:00
Julien Fontanet
9d433ed1d0 Go back to the homepage after deleting a VM. (fix #56). 2014-05-24 15:27:14 +02:00
Olivier Lambert
0125c9445e add doc stuff again 2014-05-24 15:13:15 +02:00
Olivier Lambert
a3454d4143 add doc stuff 2014-05-24 14:42:07 +02:00
Olivier Lambert
ad3c224b10 oops forgot images 2014-05-24 14:36:34 +02:00
Olivier Lambert
43d186d464 add doc 2014-05-24 14:34:56 +02:00
Olivier Lambert
a3671e776d start to add doc 2014-05-24 12:01:11 +02:00
Julien Fontanet
862ce07f8d 3.4.0 2014-05-22 11:43:23 +02:00
Julien Fontanet
a78bc7d195 Typos. 2014-05-22 11:39:08 +02:00
Julien Fontanet
b0ce4404ea Markdown fix. 2014-05-22 11:37:38 +02:00
Julien Fontanet
cd5641d2f3 ChangeLog. 2014-05-22 11:36:48 +02:00
Julien Fontanet
51dd9f0419 Redirect to wanted page after login. 2014-05-22 10:51:15 +02:00
Julien Fontanet
6d0a76756d Fix autofill AMAP. 2014-05-21 20:27:00 +02:00
Julien Fontanet
6e8342fbf4 Minor fix. 2014-05-21 20:12:09 +02:00
Julien Fontanet
8d227724d7 Merge branch 'events' into next-release 2014-05-21 20:00:27 +02:00
Olivier Lambert
eab91ffa83 fix icon for stopped vm 2014-05-21 19:57:23 +02:00
Julien Fontanet
6488198a06 Minor fixes. 2014-05-21 19:50:05 +02:00
Olivier Lambert
07b4c67b5c add space after the login icon 2014-05-21 18:44:45 +02:00
Julien Fontanet
70077a1eac Hide mockup. 2014-05-21 18:23:20 +02:00
Julien Fontanet
03763a6556 Merge branch 'acl' into next-release 2014-05-21 18:15:24 +02:00
Julien Fontanet
3cb8d77977 Switch to the log in page on log out. 2014-05-21 18:13:53 +02:00
Olivier Lambert
c8d7940451 fix vm description 2014-05-21 18:04:00 +02:00
Julien Fontanet
2253aa1229 Mandatory log in page. 2014-05-21 17:43:54 +02:00
Julien Fontanet
d5a16855fb Events are ok. 2014-05-21 15:25:12 +02:00
Olivier Lambert
1aec909c10 better style for connection status 2014-05-21 14:53:18 +02:00
Olivier Lambert
7b60980959 add icon for login 2014-05-21 13:50:28 +02:00
Olivier Lambert
097424e2ee add login view 2014-05-21 13:46:58 +02:00
Olivier Lambert
a99b17d240 fix ugly empty parentheses when host is not in a labelled pool 2014-05-18 18:35:50 +02:00
Olivier Lambert
7526230b69 mockup for user and group in VM view 2014-05-14 18:11:45 +02:00
Julien Fontanet
6b8f9a6319 Various updates. 2014-05-12 19:55:36 +02:00
Julien Fontanet
8e74556613 Dependencies update. 2014-05-12 18:50:50 +02:00
Julien Fontanet
26a3086b81 WIP. 2014-05-09 17:29:32 +02:00
Julien Fontanet
6dd70137db Do not use a symlink for gulp. 2014-04-25 12:19:50 +02:00
Julien Fontanet
94ab475a31 Update deps. 2014-04-25 12:19:25 +02:00
Olivier Lambert
e77b6f3b85 add convert to template in vm view 2014-04-18 13:15:47 +02:00
Olivier Lambert
4000b44d90 add possibility to clone with CoW 2014-04-16 18:00:00 +02:00
Olivier Lambert
ad09d98d81 add basic cloning 2014-04-16 17:31:51 +02:00
Julien Fontanet
567d5e7676 Merge branch 'master' into next-release 2014-04-15 18:17:20 +02:00
Julien Fontanet
123b0a331e Minor fix. 2014-04-15 17:53:58 +02:00
Julien Fontanet
11f1f3d132 Minor fix. 2014-04-15 17:50:05 +02:00
Julien Fontanet
a44ce0c538 Installation method is optional. 2014-04-15 17:45:55 +02:00
Olivier Lambert
23348c5d8c Merge pull request #115 from devopsconsulting/isNan
use standard js isNaN function.
2014-04-10 14:34:32 +02:00
Lars van de Kerkhof
176c6524a2 use standard js isNaN function. 2014-04-10 12:37:41 +02:00
113 changed files with 3948 additions and 8534 deletions

138
.jshintrc
View File

@@ -1,24 +1,118 @@
{
"node": true,
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 2,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"regexp": true,
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"smarttabs": true,
"globals": {
"angular": false
}
// --------------------------------------------------------------------
// JSHint Configuration, Node.js Edition
// --------------------------------------------------------------------
//
// This is an options template for [JSHint][1], forked from
// haschek's [JSHint template][2]:
//
// * the environment has been changed to `node`;
// * recent options were added;
// * coding style has been adapted to node (e.g. 2 spaces
// indenting, global use strict).
//
// [1]: http://www.jshint.com/
// [2]: https://gist.github.com/haschek/2595796
//
// @author Julien Fontanet <julien.fontanet@isonoe.net>
// @license http://unlicense.org/
// == Enforcing Options ===============================================
//
// These options tell JSHint to be more strict towards your code. Use
// them if you want to allow only a safe subset of JavaScript, very
// useful when your codebase is shared with a big number of developers
// with different skill levels.
"bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.).
"camelcase" : true, // Require variable names to use either camelCase or UPPER_CASE styles.
"curly" : true, // Require {} for every new block or scope.
"eqeqeq" : true, // Require triple equals i.e. `===`.
"forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`.
"freeze" : true, // Prohibit modification of native objects' prototypes.
"immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
"indent" : 2, // Specify indentation spacing
"latedef" : true, // Prohibit variable use before definition.
"newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`.
"noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`.
"noempty" : true, // Prohibit use of empty blocks.
"nonew" : true, // Prohibit use of constructors for side-effects.
"plusplus" : false, // Prohibit the use of `++` & `--`.
"quotmark" : "'", // Require single quotes.
"undef" : true, // Require all non-global variables be declared before they are used.
"unused" : true, // Prohibit unused variables.
"strict" : true, // Require `use strict` pragma in every function.
"trailing" : true, // Prohibit trailing whitespaces.
"maxparams" : 4, // Prohibit more than 4 parameters per function definition.
"maxdepth" : 3, // Prohibit nesting more than 3 control blocks.
"maxstatements" : 20, // Prohibit more than 20 statements per function.
"maxcomplexity" : 7, // Prohibit having to much branches in your code.
"maxlen" : 80, // Prohibit line with more than 80 characters.
// == Relaxing Options ================================================
//
// These options allow you to suppress certain types of warnings. Use
// them only if you are absolutely positive that you know what you are
// doing.
"asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons).
"boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.
"debug" : false, // Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // Tolerate use of `== null`.
"esnext" : false, // Allow ES.next specific features such as `const` and `let`.
"evil" : false, // Tolerate use of `eval`.
"expr" : true, // Tolerate `ExpressionStatement` as Programs. (Allowed for Mocha.)
"funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside.
"gcl" : false, // Makes JSHint compatible with Google Closure Compiler.
"globalstrict" : true, // Allow global "use strict" (also enables 'strict').
"iterator" : false, // Allow usage of __iterator__ property.
"lastsemic" : false, // Tolerate missing semicolons when the it is omitted for the last statement in a one-line block.
"laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons.
"laxcomma" : false, // Suppress warnings about comma-first coding style.
"loopfunc" : false, // Allow functions to be defined within loops.
"maxerr" : 50, // Maximum errors before stopping.
"moz" : false, // Tolerate Mozilla JavaScript extensions.
"notypeof" : false, // Tolerate invalid typeof values.
"multistr" : false, // Tolerate multi-line strings.
"proto" : false, // Tolerate __proto__ property. This property is deprecated.
"scripturl" : false, // Tolerate script-targeted URLs.
"smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignment only.
"shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`.
"sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
"supernew" : false, // Tolerate `new function () { ... };` and `new Object;`.
"validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function.
// == Environments ====================================================
//
// These options pre-define global variables that are exposed by
// popular JavaScript libraries and runtime environments—such as
// browser or node.js.
"browser" : false, // Standard browser globals e.g. `window`, `document`.
"couch" : false, // Enable globals exposed by CouchDB.
"devel" : false, // Allow development statements e.g. `console.log();`.
"dojo" : false, // Enable globals exposed by Dojo Toolkit.
"jquery" : false, // Enable globals exposed by jQuery JavaScript library.
"mocha" : true, // Enable globals exposed by the mocha test runner.
"mootools" : false, // Enable globals exposed by MooTools JavaScript framework.
"node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment.
"nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape.
"phantom" : false, // Enable globals exposed by PhantomJS.
"prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework.
"rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment.
"worker" : false, // Enable globals exposed when running inside a Web Worker.
"wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host.
"yui" : false, // Enable globals exposed by YUI.
// == JSLint Legacy ===================================================
//
// These options are legacy from JSLint. Aside from bug fixes they will
// not be improved in any way and might be removed at any point.
"nomen" : false, // Prohibit use of initial or trailing underbars in names.
"onevar" : false, // Allow only one `var` statement per function.
"passfail" : false, // Stop on first error.
"white" : false, // Check against strict whitespace and indentation rules.
"globals": {}
}

View File

@@ -1,26 +1,42 @@
# ChangeLog
## **3.4.0** (2014-05-22)
*Highlight in this release is the new events system between XO-Web
and XO-Server which results in less bandwidth consumption as well as
better performance and reactivity.*
### Enhancements
- events system between XO-Web and XO-Server ([#52](https://github.com/vatesfr/xo-web/issues/52))
- ability to clone/copy a VM ([#116](https://github.com/vatesfr/xo-web/issues/116))
- mandatory log in page ([#120](https://github.com/vatesfr/xo-web/issues/120))
### Bug fixes
- failure in VM creation ([#111](https://github.com/vatesfr/xo-web/issues/111))
## **3.3.1** (2014-03-28)
## Enhancements
### Enhancements
- console view is now prettier ([#92](https://github.com/vatesfr/xo-web/issues/92))
## Bug fixes
### Bug fixes
- VM creation fails to incorrect dependencies ([xo-server/#24](https://github.com/vatesfr/xo-server/issues/24))
- VDIs list in SR view is blinking ([#109](https://github.com/vatesfr/xo-web/issues/109))
## **3.3.0** (2014-03-07)
## Enhancements
### Enhancements
- [Grunt](http://gruntjs.com/) has been replaced by [gulp.js](http://gulpjs.com/) ([#91](https://github.com/vatesfr/xo-web/issues/91))
- a host can be detached from a pool ([#98](https://github.com/vatesfr/xo-web/issues/98))
- snapshots management in VM view ([#99](https://github.com/vatesfr/xo-web/issues/99))
- log deletion in VM view ([#100](https://github.com/vatesfr/xo-web/issues/100))
## Bug fixes
### Bug fixes
- *Snapshot* not working in VM view ([#95](https://github.com/vatesfr/xo-web/issues/95))
- Host *Reboot*/*Restart toolstack*/*Shutdown* not working in main view ([#97](https://github.com/vatesfr/xo-web/issues/97))

View File

@@ -18,3 +18,29 @@ Manual install procedure is [available here](https://github.com/vatesfr/xo/blob/
If you are certain the bug is exclusively related to XO-Web, you may use the [bugtracker of this repository](https://github.com/vatesfr/xo-web/issues).
Otherwise, please consider using the [bugtracker of the general repository](https://github.com/vatesfr/xo/issues).
## Process for new release
```bash
# Switch to the master branch.
git checkout master
# Merge changes of the next-release branch.
git merge next-release
# Increment the version (patch, minor or major).
npm version minor
# Go back to the next-release branch.
git checkout next-release
# Fetches the last changes (the merge and version bump) from master to
# next-release.
git pull --fast-forward master
# Push the changes on git.
git push origin master:master next-release:next-release
# Publish this release to npm.
npm publish
```

85
app/app.coffee Normal file
View File

@@ -0,0 +1,85 @@
window.jQuery = window.$ = require 'jquery'
require 'angular'
require 'angular-animate'
require 'angular-bootstrap'
require 'angular-ui-router'
require 'angular-ui-utils'
require 'angular-xeditable'
require 'select2'
require 'angular-ui-select2'
require 'angularjs-naturalsort'
#=====================================================================
angular.module 'xoWebApp', [
'ui.bootstrap'
'ui.indeterminate'
'ui.router'
'ui.select2'
'naturalSort'
'xeditable'
(require './directives').name
(require './filters').name
(require './services').name
(require './modules/about').name
(require './modules/console').name
(require './modules/delete-vms').name
(require './modules/generic-modal').name
(require './modules/home').name
(require './modules/host').name
(require './modules/list').name
(require './modules/login').name
(require './modules/navbar').name
(require './modules/new-sr').name
(require './modules/new-vm').name
(require './modules/pool').name
(require './modules/settings').name
(require './modules/sr').name
(require './modules/vm').name
]
.config ($urlRouterProvider, $tooltipProvider) ->
# Redirects unmatched URLs to `/`.
$urlRouterProvider.otherwise '/'
# Changes the default settings for the tooltips.
$tooltipProvider.options
appendToBody: true
placement: 'bottom'
.run ($rootScope, $state, xoApi, editableOptions, editableThemes, notify, $templateCache) ->
$rootScope.$on '$stateChangeStart', (event, state, stateParams) ->
{user} = xoApi
loggedIn = user?
if state.name is 'login'
if loggedIn
event.preventDefault()
$state.go 'home'
return
unless loggedIn
event.preventDefault()
# FIXME: find a better way to pass info to the login controller.
$rootScope._login = { state, stateParams }
$state.go 'login'
return
# The user must have the `admin` permission to access the
# settings page.
if state.name is 'settings' and user.permission isnt 'admin'
event.preventDefault()
notify.error
title: 'Restricted area'
message: 'You do not have the permission to view this page'
editableThemes.bs3.inputClass = 'input-sm'
editableThemes.bs3.buttonsClass = 'btn-sm'
editableOptions.theme = 'bs3'

View File

@@ -1,4 +1,5 @@
angular.module('xoWebApp')
# TODO: split into multiple modules.
module.exports = angular.module 'xoWebApp.directives', []
# This attribute stops the ascendant propagation of a given event.
#
@@ -6,7 +7,9 @@ angular.module('xoWebApp')
# stop.
.directive 'stopEvent', ->
(_, $element, attrs) ->
$element.on attrs.stopEvent, ($event) -> $event.stopPropagation()
$element.on attrs.stopEvent, ($event) ->
console.log $event
$event.stopPropagation()
# This attribute works similarly to `ng-click` but do not handle the
# event if the clicked element:
@@ -98,104 +101,15 @@ angular.module('xoWebApp')
true
)
.directive 'xoVnc', ($window) ->
# This helper function parses a URL and returns its components:
# protocol, hostname, port, path and query.
parseUrl = (url) ->
a = $window.document.createElement 'a'
a.href = url
{protocol, hostname, port, host, pathname, search, hash} = a
{
# Protocol lowercased postfixed with ':'.
protocol
hostname
port
# Same has hostname[:port].
host
# The path excluding the query string.
pathname
# Query string (including '?').
search
# Same has `pathname + search`.
path: "#{pathname}#{search}"
# Fragment (including '#').
hash
}
# The directive definition.
{
restrict: 'E'
scope: {
height: '@?'
width: '@?'
url: '@'
remoteControl: '='
}
replace: true
template: '''
<canvas height="{{height}}" width="{{width}}">
Sorry, your browser does not support the canvas element.
</canvas>
'''
link: ($scope, $element, attrs) ->
# Default options.
$scope.$watch 'height', -> $scope.height ?= 480
$scope.$watch 'width', -> $scope.width ?= 640
rfb = null
$scope.remoteControl = {
sendCtrlAltDel: ->
rfb.sendCtrlAltDel() if rfb?
}
# Connects to the specified URL.
$scope.$watch 'url', (url) ->
# Properly disconnects first if necessary.
if rfb?
rfb.disconnect()
rfb = null
# If the URL is empty, nothing to do.
return unless url
# Creates the new RFB object.
rfb = new $window.RFB {
# Options.
encrypt: false
target: $element[0]
wsProtocols: ['chat']
# Callbacks.
onPasswordRequired: (rfb) ->
rfb.sendPassword $window.prompt 'Password required:'
onUpdateState: (args...) -> console.log args
}
# Parse the URL.
url = parseUrl url
# Connects.
rfb.connect(
url.hostname
80 # Ignores the specified port and always use 80.
'' # TODO: comment.
url.path.substr 1 # Leading '/' is added by noVNC.
)
# Properly disconnect if the console is closed.
$scope.$on '$destroy', ->
if rfb?
rfb.disconnect()
rfb = null
}
.directive 'fixAutofill', ($timeout) ->
restrict: 'A'
require: 'ngModel'
link: ($scope, $elem, attrs, ngModel) ->
previous = $elem.val()
$timeout(
->
current = $elem.val()
if ngModel.$pristine and current isnt previous
ngModel.$setViewValue current
5e2
)

View File

@@ -1,4 +1,7 @@
angular.module('xoWebApp')
# TODO: split into multiple modules.
module.exports = angular.module 'xoWebApp.filters', [
(require './services').name
]
# The bytes filters takes a number and formats it using adapted
# units (KB, MB, etc.).
@@ -91,7 +94,7 @@ angular.module('xoWebApp')
return 'N/A' if value[1] is 0
result = 100 * value[0] / value[1]
if Number.isNaN result
if isNaN result
return 'N/A'
value = result

BIN
app/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
app/images/logo_small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,140 +0,0 @@
<!-- HTML 5 Doctype -->
<!doctype html>
<!-- The “no-js” class will be automatically removed if JavaScript is
- available.
-->
<html lang="en" dir="ltr" class="no-js">
<head>
<meta charset="utf-8">
<!--
- This file is a part of Xen Orchestra Web.
-
- Xen Orchestra Web is free software: you can redistribute it and/or
- modify it under the terms of the GNU Affero General Public License
- as published by the Free Software Foundation, either version 3 of
- the License, or (at your option) any later version.
-
- Xen Orchestra Web is distributed in the hope that it will be
- useful, but WITHOUT ANY WARRANTY; without even the implied warranty
- of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with Xen Orchestra Web. If not, see
- <http://www.gnu.org/licenses/>.
-
- @author Olivier Lambert <olivier.lambert@vates.fr>
- @license http://www.gnu.org/licenses/agpl-3.0-standalone.html GNU AGPLv3
-
- @package Xen Orchestra Web
-->
<!-- Makes sure IE is using the last engine available. -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<!-- Replaces the “no-js” class by the “js” class if JavaScript
- is available.
-->
<script>!function(d){d.className=d.className.replace(/\bno-js\b/,'js')}(document.documentElement)</script>
<!-- (To confirm.) For smartphones and tablets: sets the page
- width to the device width and prevents the page from being
- zoomed in when going to landscape mode.
-->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Xen Orchestra</title>
<meta name="description" content="Web interface for XenServer/XAPI Hosts" />
<meta name="author" content="Vates SAS" />
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<!-- build:css(.tmp) styles/main.css -->
<link rel="stylesheet" href="styles/main.css">
<!-- endbuild -->
<!-- Angular-xeditable CSS -->
<link rel="stylesheet" href="bower_components/angular-xeditable/dist/css/xeditable.css">
<!-- Angular UI Select -->
<link rel="stylesheet" href="bower_components/select2/select2.css">
<link rel="stylesheet" href="bower_components/angular-notify-toaster/toaster.css">
</head>
<body ng-app="xoWebApp">
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<!--[if lt IE 9]>
<script src="bower_components/es5-shim/es5-shim.js"></script>
<script src="bower_components/json3/lib/json3.min.js"></script>
<![endif]-->
<toaster-container></toaster-container>
<!-- Navigation bar. -->
<div ng-controller="NavBarCtrl">
<ng-include src="'views/nav-bar.html'"></ng-include>
</div>
<!-- Main content (managed by the router). -->
<div class="view-main" ui-view></div>
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-animate/angular-animate.js"></script>
<script src="bower_components/angular-cookies/angular-cookies.js"></script>
<!-- build:js scripts/plugins.js -->
<script src="bower_components/angular-ui-bootstrap-bower/ui-bootstrap-tpls.js"></script>
<script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
<script src="bower_components/angular-ui-utils/ui-utils.js"></script>
<script src="bower_components/angular-notify-toaster/toaster.js"></script>
<!-- endbuild -->
<!-- build:js scripts/modules.js -->
<script src="bower_components/angular-xeditable/dist/js/xeditable.js"></script>
<script src="bower_components/select2/select2.js"></script>
<script src="bower_components/angular-ui-select2/src/select2.js"></script>
<script src="bower_components/angularjs-naturalsort/index.js"></script>
<!-- endbuild -->
<!-- noVNC -->
<script src="bower_components/noVNC/include/util.js"></script>
<script>
var INCLUDE_URI = 'bower_components/noVNC/include/';
Util.load_scripts([
'webutil.js',
'base64.js',
'websock.js',
'des.js',
'keysymdef.js',
'keyboard.js',
'input.js',
'display.js',
'jsunzip.js',
'rfb.js',
]);
</script>
<!-- /noVNC -->
<!-- build:js({.tmp,app}) scripts/scripts.js -->
<script src="scripts/app.js"></script>
<script src="scripts/directives.js"></script>
<script src="scripts/filters.js"></script>
<script src="scripts/services.js"></script>
<script src="scripts/controllers/main.js"></script>
<script src="scripts/controllers/nav-bar.js"></script>
<script src="scripts/controllers/list.js"></script>
<script src="scripts/controllers/host.js"></script>
<script src="scripts/controllers/sr.js"></script>
<script src="scripts/controllers/pool.js"></script>
<script src="scripts/controllers/vm.js"></script>
<script src="scripts/controllers/console.js"></script>
<script src="scripts/controllers/settings.js"></script>
<script src="scripts/controllers/new_vm.js"></script>
<script src="scripts/controllers/new_sr.js"></script>
<script src="scripts/controllers/delete_vms.js"></script>
<script src="scripts/controllers/generic_modal.js"></script>
<!-- endbuild -->
</body>
</html>

93
app/index.jade Normal file
View File

@@ -0,0 +1,93 @@
//- HTML 5 Doctype
doctype html
//- The “no-js” class will be automatically removed if JavaScript is
//- available.
html.no-js(lang="en", dir="ltr")
head
meta(charset="utf-8")
//- This file is a part of Xen Orchestra Web.
//-
//- Xen Orchestra Web is free software: you can redistribute it and/or
//- modify it under the terms of the GNU Affero General Public License
//- as published by the Free Software Foundation, either version 3 of
//- the License, or (at your option) any later version.
//-
//- Xen Orchestra Web is distributed in the hope that it will be
//- useful, but WITHOUT ANY WARRANTY; without even the implied warranty
//- of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//- Affero General Public License for more details.
//-
//- You should have received a copy of the GNU Affero General Public License
//- along with Xen Orchestra Web. If not, see
//- <http://www.gnu.org/licenses/>.
//-
//- @author Olivier Lambert <olivier.lambert@vates.fr>
//- @license http://www.gnu.org/licenses/agpl-3.0-standalone.html GNU AGPLv3
//-
//- @package Xen Orchestra Web
//- Makes sure IE is using the last engine available.
meta(http-equiv="X-UA-Compatible", content="IE=edge,chrome=1")
//- Replaces the “no-js” class by the “js” class if JavaScript is
//- available.
script.
!function(d){d.className=d.className.replace(/\\bno-js\b/,'js')}(document.documentElement)
//- (To confirm.) For smartphones and tablets: sets the page
//- width to the device width and prevents the page from being
//- zoomed in when going to landscape mode.
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title Xen Orchestra
meta(name="description", content="Web interface for XenServer/XAPI Hosts")
meta(name="author", content="Vates SAS")
//- Place favicon.ico and apple-touch-icon.png in the root directory
link(rel="stylesheet", href="styles/main.css")
//- Angular-xeditable CSS
link(rel="stylesheet", href="bower_components/angular-xeditable/dist/css/xeditable.css")
//- Angular UI Select
link(rel="stylesheet", href="bower_components/select2/select2.css")
link(rel="stylesheet", href="bower_components/angular-notify-toaster/toaster.css")
body(ng-app="xoWebApp")
//- Conditional comments needs plain HTML.
| <!--[if lt IE 7]>
| <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
| <![endif]-->
| <!--[if lt IE 9]>
| <script src="bower_components/es5-shim/es5-shim.js"></script>
| <script src="bower_components/json3/lib/json3.min.js"></script>
| <![endif]-->
toaster-container
//- Navigation bar.
navbar
//- Main content (managed by the router).
.view-main(ui-view = "")
//- noVNC
script(src="bower_components/noVNC/include/util.js")
script.
var INCLUDE_URI = 'bower_components/noVNC/include/';
Util.load_scripts([
'webutil.js',
'base64.js',
'websock.js',
'des.js',
'keysymdef.js',
'keyboard.js',
'input.js',
'display.js',
'jsunzip.js',
'rfb.js',
]);
script(src="app.js")

View File

@@ -0,0 +1,20 @@
'use strict';
//====================================================================
/* global angular:false */
require('angular');
require('angular-ui-router');
//====================================================================
module.exports = angular.module('xoWebApp.about', [
'ui.router',
])
.config(function ($stateProvider) {
$stateProvider.state('about', {
url: '/about',
template: require('./view'),
});
})
;

View File

@@ -0,0 +1,46 @@
// TODO: lots of stuff.
.grid
.grid-cell.flat-panel
p.page-title About Xen Orchestra
.grid
// Vates
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-lightbulb-o(style="color: #e25440;")
| Vates
p.text-center
| We are the team behind Xen Orchestra, we are Vates! We create Open Source products and we offer commercial support for Xen and Xen Orchestra. Want to know more about us? Go to our website!
p.text-center
img(src="images/arrow.png")
br
p.text-center
a.btn.btn-success(href="https://vates.fr")
i.fa.fa-hand-o-right
| Our website
// Open Source
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-thumbs-up(style="color: #e25440;")
| Open Source
p.text-center
| This project is Open Source (AGPL), everyone is welcome aboard! You want a specific feature in XO? Report a bug? Go to our project website, read the FAQ and get involved in the project!
p.text-center
img(src="images/opensource.png")
br
p.text-center
a.btn.btn-info(href="https://xen-orchestra.com")
i.fa.fa-flask
| Project website
// Pro support
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-truck(style="color: #e25440;")
| Pro Support Delivered
p.text-center
| Our XO Appliance can be delivered with professional support: stay relaxed, we got your back! You can also have assitance for deploying or upgrade your virtualized infrastructure through our deep understanding of Xen.
p.text-center
img(src="images/support.png")
p.text-center
a.btn.btn-primary(href="https://xen-orchestra.com/services/")
i.fa.fa-envelope
| Get services

View File

@@ -0,0 +1,175 @@
require 'angular'
require 'angular-ui-router'
#=====================================================================
module.exports = angular.module 'xoWebApp.console', [
'ui.router'
]
.config ($stateProvider) ->
$stateProvider.state 'consoles_view',
url: '/consoles/:id'
controller: 'ConsoleCtrl'
template: require './view'
.controller 'ConsoleCtrl', ($scope, $stateParams, xoApi, xo) ->
{id} = $stateParams
{get} = xo
push = Array::push.apply.bind Array::push
merge = do ->
(args...) ->
result = []
for arg in args
push result, arg if arg?
result
$scope.$watch(
-> xo.revision
->
unless xoApi.user
$scope.consoleUrl = ''
$scope.VDIs = []
return
VM = $scope.VM = xo.get id
return unless VM? and VM.power_state is 'Running'
pool = get VM.poolRef
return unless pool
$scope.consoleUrl = do ->
for console in VM.consoles
if console.protocol is 'rfb'
return "#{console.location}&session_id=#{pool.$sessionId}"
''
host = get VM.$container # host because the VM is running.
return unless host
# FIXME: We should filter on connected SRs (PBDs)!
SRs = get (merge host.SRs, pool.SRs)
$scope.VDIs = do ->
VDIs = []
for SR in SRs
push VDIs, SR.VDIs if SR.content_type is 'iso'
get VDIs
cdDrive = do ->
return VBD for VBD in (get VM.$VBDs) when VBD.is_cd_drive
null
$scope.mountedIso =
if cdDrive and cdDrive.VDI and (VDI = get cdDrive.VDI)
VDI.UUID
else
''
)
$scope.startVM = xo.vm.start
$scope.stopVM = xo.vm.stop
$scope.rebootVM = xo.vm.restart
$scope.eject = ->
xo.vm.ejectCd id
$scope.insert = (disc_id) ->
xo.vm.insertCd id, disc_id, true
.directive 'xoVnc', ($window) ->
# This helper function parses a URL and returns its components:
# protocol, hostname, port, path and query.
parseUrl = (url) ->
a = $window.document.createElement 'a'
a.href = url
{protocol, hostname, port, host, pathname, search, hash} = a
{
# Protocol lowercased postfixed with ':'.
protocol
hostname
port
# Same has hostname[:port].
host
# The path excluding the query string.
pathname
# Query string (including '?').
search
# Same has `pathname + search`.
path: "#{pathname}#{search}"
# Fragment (including '#').
hash
}
# The directive definition.
{
restrict: 'E'
scope: {
height: '@?'
width: '@?'
url: '@'
remoteControl: '='
}
replace: true
template: '''
<canvas height="{{height}}" width="{{width}}">
Sorry, your browser does not support the canvas element.
</canvas>
'''
link: ($scope, $element, attrs) ->
# Default options.
$scope.$watch 'height', -> $scope.height ?= 480
$scope.$watch 'width', -> $scope.width ?= 640
rfb = null
$scope.remoteControl = {
sendCtrlAltDel: ->
rfb.sendCtrlAltDel() if rfb?
}
# Connects to the specified URL.
$scope.$watch 'url', (url) ->
# Properly disconnects first if necessary.
if rfb?
rfb.disconnect()
rfb = null
# If the URL is empty, nothing to do.
return unless url
# Creates the new RFB object.
rfb = new $window.RFB {
# Options.
encrypt: false
target: $element[0]
wsProtocols: ['chat']
# Callbacks.
onPasswordRequired: (rfb) ->
rfb.sendPassword $window.prompt 'Password required:'
onUpdateState: (args...) -> console.log args
}
# Parse the URL.
url = parseUrl url
# Connects.
rfb.connect(
url.hostname
80 # Ignores the specified port and always use 80.
'' # TODO: comment.
url.path.substr 1 # Leading '/' is added by noVNC.
)
# Properly disconnect if the console is closed.
$scope.$on '$destroy', ->
if rfb?
rfb.disconnect()
rfb = null
}

View File

@@ -13,8 +13,8 @@
.list-group
//- Toolbar
.list-group-item: .row
.col-md-10: .input-group
.list-group-item: .row.text-center
.col-sm-6: .input-group
select.form-control(
ng-model = 'mountedIso'
ng-change = 'insert(mountedIso)'
@@ -26,13 +26,21 @@
ng-disabled = '!mountedIso'
)
i.fa.fa-eject
.col-md-2: button.btn.btn-default(
.col-sm-3: button.btn.btn-default(
ng-click = 'vncRemote.sendCtrlAltDel()'
)
i.fa.fa-keyboard-o
| &nbsp;
| Ctrl+Alt+Del
// Action panel
.col-sm-3
.btn-group
button.btn.btn-default.inversed(tooltip="Stop VM", type="button", xo-click="stopVM(VM.UUID)")
i.fa.fa-stop.fa-fw
button.btn.btn-default.inversed(tooltip="Start VM", type="button", xo-click="startVM(VM.UUID)")
i.fa.fa-play.fa-fw
button.btn.btn-default.inversed(tooltip="Reboot VM", type="button", xo-click="rebootVM(VM.UUID)")
i.fa.fa-refresh.fa-fw
//- Console
.list-group-item
xo-vnc.center-block(

View File

@@ -0,0 +1,35 @@
require 'angular'
require 'angular-bootstrap'
#=====================================================================
module.exports = angular.module 'xoWebApp.deleteVms', [
'ui.bootstrap'
(require '../../services.coffee').name
]
.controller 'DeleteVmsCtrl', ($scope, $modalInstance, xo, VMsIds) ->
$scope.$watch(
-> xo.revision
->
$scope.VMs = xo.get VMsIds
)
# Do disks have to be deleted for a given VM.
disks = $scope.disks = {}
do ->
disks[id] = true for id in VMsIds
$scope.delete = ->
$modalInstance.close ([id, disks[id]] for id in VMsIds)
.service 'deleteVmsModal', ($modal, xo) ->
return (ids) ->
modal = $modal.open
controller: 'DeleteVmsCtrl'
template: require './view'
resolve: VMsIds: -> ids
return modal.result.then (toDelete) ->
for [id, deleteDisks] in toDelete
xo.vm.delete id, deleteDisks

View File

@@ -0,0 +1,24 @@
form(ng-submit="delete()")
.modal-header
h3 VMs deletion
.modal-body
p
| You are going to delete the following VMs, this is a
strong dangerous action
| !
table.table
tr
th.col-sm-3 Name
th.col-sm-6 Description
th.col-sm-3 Delete disks?
tbody
tr(ng-repeat="VM in VMs | orderBy:natural('name_label') track by VM.UUID")
td {{VM.name_label}}
td {{VM.name_description}}
td
input(type="checkbox", ng-model="disks[VM.UUID]")
.modal-footer
button.btn.btn-primary(type="submit")
| Delete
button.btn.btn-warning(type="button", ng-click="$dismiss()")
| Cancel

View File

@@ -0,0 +1,43 @@
'use strict';
//====================================================================
/* global angular:false */
require('angular');
require('angular-bootstrap');
//====================================================================
module.exports = angular.module('xoWebApp.GenericModal', [
'ui.bootstrap',
])
.controller('GenericModalCtrl', function ($scope, $modalInstance, options) {
$scope.title = options.title;
$scope.message = options.message;
$scope.yesButtonLabel = options.yesButtonLabel || 'Ok';
$scope.noButtonLabel = options.noButtonLabel;
})
.service('modal', function ($modal) {
return {
confirm: function (opts) {
var modal = $modal.open({
controller: 'GenericModalCtrl',
template: require('./view'),
resolve: {
options: function () {
return {
title: opts.title,
message: opts.message,
noButtonLabel: 'Cancel',
};
},
},
});
return modal.result;
}
};
})
;

View File

@@ -0,0 +1,11 @@
.modal-header
h3
i.fa.fa-exclamation-triangle.text-danger
| {{title}}
.modal-body
| {{message}}
.modal-footer
button.btn.btn-primary(type="button", ng-click="$close()")
| {{yesButtonLabel}}
button.btn.btn-warning(ng-if="noButtonLabel", type="button", ng-click="$dismiss()")
| {{noButtonLabel}}

View File

@@ -1,7 +1,19 @@
'use strict'
require 'angular'
require 'angular-ui-router'
angular.module('xoWebApp')
.controller 'MainCtrl', ($scope, $modal, modal, xo, dateFilter) ->
#=====================================================================
module.exports = angular.module 'xoWebApp.home', [
'ui.router'
(require '../delete-vms').name
]
.config ($stateProvider) ->
$stateProvider.state 'home',
url: '/'
controller: 'HomeCtrl'
template: require './view'
.controller 'HomeCtrl', ($scope, modal, xo, dateFilter, deleteVmsModal) ->
VMs = []
$scope.$watch(
-> xo.revision
@@ -74,18 +86,16 @@ angular.module('xoWebApp')
$scope.deleteVMs = ->
{selected_VMs} = $scope
VMsIds = (id for id, selected of selected_VMs when selected)
modal = $modal.open {
controller: 'DeleteVMsCtrl'
templateUrl: 'views/delete_vms.html'
resolve: {
VMsIds: -> VMsIds
}
}
deleteVmsModal (id for id, selected of selected_VMs when selected)
modal.result.then (toDelete) ->
for [id, deleteDisks] in toDelete
xo.vm.delete id, deleteDisks
$scope.osType = (osName) ->
switch osName
when 'debian','ubuntu','centos','suse','redhat','oracle','gentoo','suse','fedora'
'linux'
when 'windows'
'windows'
else
'other'
# VMs checkboxes.
do ->
@@ -150,11 +160,3 @@ angular.module('xoWebApp')
# Unselects all VMs.
$scope.selectVMs false
$scope.osType = (osName) ->
switch osName
when 'debian','ubuntu','centos','suse','redhat','oracle','gentoo'
'linux'
when 'windows'
'windows'
else
'other'

358
app/modules/home/view.jade Normal file
View File

@@ -0,0 +1,358 @@
//- @todo Remove code duplication for the VMs listing by using a macro.
.sub-bar
.grid
.grid-cell.overview
// Stats
i(tooltip="{{xo.pools.length}} pools")
i.small {{xo.pools.length}}x
| &nbsp;
i.xo-icon-pool
| &nbsp;
| &nbsp;
i(tooltip="{{hosts.length}} hosts")
i.small {{hosts.length}}x
| &nbsp;
i.xo-icon-host
| &nbsp;
| &nbsp;
i(tooltip="{{xo.$running_VMs.length}} of {{VMs.length}} VMs running")
i.small {{xo.$running_VMs.length}}x
| &nbsp;
i.xo-icon-vm
| &nbsp;
| &nbsp;
i(tooltip="{{xo.$vCPUs}} vCPUs used of {{xo.$CPUs}} CPUs")
i.small {{xo.$vCPUs}}x
| &nbsp;
i.xo-icon-cpu
| &nbsp;
| &nbsp;
i(tooltip="{{xo.$memory.usage | bytesToSize}} RAM allocated of {{xo.$memory.size | bytesToSize}}")
i.small {{xo.$memory.usage | bytesToSize}}
| &nbsp;
i.xo-icon-memory
.grid-cell
.btn-group.before-action-bar.dropdown
a.btn.navbar-btn.btn-default.dropdown-toggle.inversed
input.inverse(type="checkbox", ng-model="master_selection", ng-change="selectVMs(master_selection)", ui-indeterminate="!(all || none)", stop-event="click")
| &nbsp;
i.fa.fa-caret-down
ul.dropdown-menu.inverse(role="menu")
li(ng-repeat="power_state in ['Halted', 'Running']")
a(ng-click="selectVMs({power_state: power_state})")
i.fa-fw(class="xo-icon-{{power_state | lowercase}}")
| {{power_state}}
li.divider
li(ng-repeat="host in hosts | orderBy:natural('name_label') track by host.UUID", ng-if="host.VMs.length")
a(ng-click="selectVMs({$container: host.ref})")
i.xo-icon-host.fa-fw
| On {{host.name_label}}
.action-bar(ng-if="!none")
| &nbsp;
.btn-group
button.btn.navbar-btn.btn-default.inversed(tooltip="Stop VM", type="button", ng-click="bulkAction('stopVM')")
i.fa.fa-stop
button.btn.navbar-btn.btn-default.inversed(tooltip="Start VM", type="button", ng-click="bulkAction('startVM')")
i.fa.fa-play
button.btn.navbar-btn.btn-default.inversed(tooltip="Reboot VM", type="button", ng-click="bulkAction('rebootVM')")
i.fa.fa-refresh
| &nbsp;
.btn-group.dropdown
button.btn.navbar-btn.btn-default.dropdown-toggle.inversed(tooltip="Migrate VM", type="button")
i.fa.fa-share
| &nbsp;
i.fa.fa-caret-down
ul.dropdown-menu.inverse(role="menu")
li(ng-repeat="host in hosts | orderBy:natural('name_label') track by host.UUID")
a(ng-click="bulkAction('migrateVM',host.UUID)")
i.xo-icon-host.fa-fw
| To {{host.name_label}}
| &nbsp;
.btn-group.dropdown
button.btn.navbar-btn.btn-default.dropdown-toggle.inversed(type="button")
| More
| &nbsp;
i.fa.fa-caret-down
ul.dropdown-menu.inverse(role="menu")
li
a(ng-click="bulkAction('force_rebootVM')")
i.fa.fa-bolt.fa-fw
| Force reboot
li
a(ng-click="bulkAction('force_stopVM')")
i.fa.fa-power-off.fa-fw
| Force shutdown
li.divider
li
a(ng-click="bulkAction('snapshotVM')")
i.xo-icon-snapshot.fa-fw
| Take a snapshot
li
a(ng-click="deleteVMs()")
i.fa.fa-trash-o.fa-fw
| Delete
//- FIXME: Ugly trick to force the pools to be under the sub bar.
//- Add +7px to the 50px for having some space before the first pool.
div(style="margin-top: 57px; visibility: hidden; height: 0") .
//- If we haven't any data
div(ng-if="!pools.length")
.grid
.grid-cell.flat-panel.text-center
h1 Welcome on Xen Orchestra!
h3 It seems you aren't connected to any Xen host.
p You can add any XAPI capable server by clicking on the menu icon "
i.fa.fa-th
| " and choose "
i.fa.fa-cog
| Settings"
p Enjoy Xen Orchestra!
p
a.btn.btn-primary.big(ui-sref="about")
i.fa.fa-info-circle
| About us
//- If we have data
div(ng-if="pools.length")
//- Contains a pool and all its children (hosts).
.grid.pool-block(ng-repeat="pool in pools | orderBy:natural('name_label') track by pool.UUID")
//- Pseudo pool if it is not a named pool.
.grid-cell.grid--gutters.pool-cell(ng-if="!pool.name_label")
p.center(style="margin-top: 2em;") No pool connected
//- Contains information about the pool if it is a named pool.
.grid-cell.grid--gutters.pool-cell(ng-if="pool.name_label")
//- Header (name + dropdown menu).
.dropdown.dropdown-pool
a.pool-name(ui-sref="pools_view({id: pool.UUID})")
| {{pool.name_label}}
a.dropdown-toggle
| &nbsp;
i.fa.fa-caret-down.big-caret
ul.dropdown-menu.left(role="menu")
li
a(xo-click="new_sr()")
i.xo-icon-sr.fa-fw
| Add SR
li
a(xo-sref="VMs_new({container: pool.UUID})")
i.xo-icon-vm.fa-fw
| Add VM
li.divider
li
a.disabled(xo-click="pool_disconnect(pool.UUID)")
i.fa.fa-unlink.fa-fw
| Disconnect
//- /Header.
//- Stats & SRs list.
div
//- Stats.
ul.list-unstyled.stats
li
i(tooltip="{{pool.hosts.length}} hosts connected")
i.small {{pool.hosts.length}}x
| &nbsp;
i.xo-icon-host
| &nbsp;
| &nbsp;
i(tooltip="{{pool.$running_VMs.length}} of {{pool.$VMs.length}} VMs running")
i.small {{pool.$running_VMs.length}}x
| &nbsp;
i.xo-icon-vm
li(ng-if="pool.master", ng-init="master = (pool.master | resolve)")
| Master:
| &nbsp;
a(ui-sref="hosts_view({id: master.UUID})") {{master.name_label}}
li(ng-if="!pool.master") No master
//- /Stats.
//- SRs.
div(ng-if="pool.SRs.length")
p.center.small-caps SRs:
table.table.table-hover.table-condensed
tr(ng-repeat="SR in pool.SRs | resolve | orderBy:natural('name_label') track by SR.UUID", xo-sref="SRs_view({id: SR.UUID})")
td.col-md-6.sr-name.no-border(ng-class="{'default-sr': SR.ref === pool.default_SR}")
i.xo-icon-sr
| {{SR.name_label}}
td.col-md-6.right.no-border
.progress.progress-small(tooltip="Disk: {{[SR.usage, SR.size] | %}} allocated")
.progress-bar(role="progressbar", aria-valuenow="{{100*SR.usage/SR.size}}", aria-valuemin="0", aria-valuemax="100", style="width: {{[SR.usage, SR.size] | %}}")
//- Contains all the hosts of this pool.
.grid-cell.grid--gutters.hosts-vms-cells
//- Contains a host and all its children (VMs).
.grid(ng-repeat="host in pool.hosts | resolve | orderBy:natural('name_label') track by host.UUID")
//- Contains information about the host.
.grid-cell.host-cell
//- Header (name + dropdown menu).
.dropdown.dropdown-pool
a.host-name(ui-sref="hosts_view({id: host.UUID})")
| {{host.name_label}}
a.dropdown-toggle
| &nbsp;
i.fa.fa-caret-down
ul.dropdown-menu.left(role="menu")
li
a(xo-click="new_sr()")
i.xo-icon-sr.fa-fw
| Add SR
li
a(xo-sref="VMs_new({container: host.UUID})")
i.xo-icon-vm.fa-fw
| Add VM
li.divider
li
a(ng-repeat="controller in [host.controller] | resolve track by controller.UUID", xo-sref="consoles_view({id: controller.UUID})")
i.xo-icon-console.fa-fw
| Console
li
a(xo-click="rebootHost(host.UUID)")
i.fa.fa-refresh.fa-fw
| Reboot
li
a(xo-click="shutdownHost(host.UUID)")
i.fa.fa-power-off.fa-fw
| Shutdown
li
a(xo-click="restartToolStack(host.UUID)")
i.fa.fa-retweet.fa-fw
| Restart toolstack
li(ng-if="pool.name_label")
a(xo-click="pool_removeHost(host.UUID)")
i.fa.fa-cloud-upload.fa-fw
| Remove from pool
li(ng-if="!pool.name_label")
a(xo-click="pool_addHost(host.UUID)")
i.fa.fa-cloud-download.fa-fw
| Add to pool
//- /Header.
//- Stats.
ul.list-unstyled.stats
//- Warning icon if host is halted or disabled
li.text-danger(ng-if="host.power_state === 'Halted'")
i.fa.fa-warning
| Halted
li.text-warning(ng-if="!host.enabled && host.power_state === 'Running'")
i.fa.fa-warning
| Disabled
//- Memory
li(ng-if="host.power_state === 'Running' && host.enabled")
i.xo-icon-memory.i-progress
.progress.progress-small(tooltip="RAM: {{[host.memory.usage, host.memory.size] | %}} allocated")
.progress-bar(role="progressbar", aria-valuenow="{{100*host.memory.usage/host.memory.size}}", aria-valuemin="0", aria-valuemax="100", style="width: {{[host.memory.usage, host.memory.size] | %}}")
//- Host address
li.text-muted.substats
i.xo-icon-network
| {{host.address}}
//- Contains all the VMs of this host.
.grid.grid-cell.vm-cell
//- If no VMs, fill the space with a message.
.vms-notice(ng-if="!host.VMs.length")
//- TODO: put 'Halted' state in xo-server
//p(ng-if="'Halted' === host.power_state")
//- | Host halted.
p(ng-if="'shutdown' === values(host.current_operations)[0]")
| Host halted.
div(ng-if="'shutdown' !== values(host.current_operations)[0]")
p(ng-if="!host.enabled")
| Host disabled.
p(ng-if="host.enabled")
| No VMs on this host.
//- /Message if no VMs.
//- TODO: comment
.table-responsive(ng-if="host.VMs.length")
table.table.table-hover.table-condensed
//- Contains a VM.
tr(ng-repeat="VM in host.VMs | resolve | orderBy:natural('name_label') track by VM.UUID", xo-sref="VMs_view({id: VM.UUID})")
//- Handle used for drag & drop.
td.grab
//- Checkbox used for selection.
td.select-vm
input(type="checkbox", ng-model="selected_VMs[VM.UUID]", ng-change="updateVMSelection(VM.UUID)")
//- Power state
td.vm-power-state
i.xo-icon-working(ng-if="isVMWorking(VM)", tooltip="{{VM.power_state}} and {{values(VM.current_operations)[0]}}")
i(class="xo-icon-{{VM.power_state | lowercase}}",ng-if="!isVMWorking(VM)", tooltip="{{VM.power_state}}")
//- VM name.
td.vm-name.col-md-2
p.vm {{VM.name_label}}
//- Quick actions.
td.vm-quick-buttons.col-md-2
.quick-buttons
a(tooltip="Shutdown VM", xo-click="stopVM(VM.UUID)")
i.fa.fa-stop
a(tooltip="Start VM", xo-click="startVM(VM.UUID)")
i.fa.fa-play
a(tooltip="Reboot VM", xo-click="rebootVM(VM.UUID)")
i.fa.fa-refresh
a(tooltip="VM Console", xo-sref="consoles_view({id: VM.UUID})")
i.xo-icon-console
//- Description.
td.vm-description.col-md-4
i(class="xo-icon-{{osType(VM.os_version.distro)}}",ng-if="VM.os_version.distro", tooltip="{{VM.os_version.name}}")
| &nbsp;
i.fa.fa-fw(ng-if="!VM.os_version.distro")
| {{VM.name_description}}
//- Metrics.
//- Memory
td.vm-memory-stat.col-md-2
.cpu
| {{VM.memory.size | bytesToSize}}
i.fa.fa-fw(ng-if="VM.PV_drivers")
i.xo-icon-info.fa-fw(ng-if="!VM.PV_drivers", tooltip="Xen tools not installed")
//- /Metrics.
//- Address.
td.text-muted.text-right.col-md-2
| {{VM.addresses["0/ip"]}}
//- Contains a pseudo-host which contains all VMs not in any hosts.
.grid(ng-if="pool.VMs.length")
//- This is where the information about a host would be displayed.
.grid-cell.host-cell
//- Contains all the VMs of this pool.
.grid.grid-cell.vm-cell
//- TODO: comment
.table-responsive
table.table.table-hover.table-condensed
//- Contains a VM.
tr(ng-repeat="VM in pool.VMs | resolve | orderBy:natural('name_label') track by VM.UUID", xo-sref="VMs_view({id: VM.UUID})")
//- Handle used for drag & drop.
td.grab
//- Checkbox used for selection.
td.select-vm
input(type="checkbox", ng-model="selected_VMs[VM.UUID]", ng-change="updateVMSelection(VM.UUID)")
//- Power state
td.vm-power-state
i.xo-icon-working(ng-if="isVMWorking(VM)", tooltip="{{VM.power_state}} and {{values(VM.current_operations)[0]}}")
i(class="xo-icon-{{VM.power_state | lowercase}}",ng-if="!isVMWorking(VM)", tooltip="{{VM.power_state}}")
//- VM name.
td.vm-name.col-md-2
p.vm {{VM.name_label}}
//- Quick actions.
td.vm-quick-buttons.col-md-2
.quick-buttons
a(tooltip="Shutdown VM", xo-click="stopVM(VM.UUID)")
i.fa.fa-stop
a(tooltip="Start VM", xo-click="startVM(VM.UUID)")
i.fa.fa-play
a(tooltip="Reboot VM", xo-click="rebootVM(VM.UUID)")
i.fa.fa-refresh
a(tooltip="VM Console")
i.xo-icon-console
//- Description.
td.vm-description.col-md-4
i(class="xo-icon-{{osType(VM.os_version.distro)}}",ng-if="VM.os_version.distro", tooltip="{{VM.os_version.name}}")
| &nbsp;
i.fa.fa-fw(ng-if="!VM.os_version.distro")
| {{VM.name_description}}
//- Metrics.
//- Memory
td.vm-memory-stat.col-md-2
.cpu
| {{VM.memory.size | bytesToSize}}
i.fa.fa-fw(ng-if="VM.PV_drivers")
i.xo-icon-info.fa-fw(ng-if="!VM.PV_drivers", tooltip="Xen tools not installed")
//- /Metrics.
//- Address.
td.text-muted.text-right.col-md-2
| {{VM.addresses["0/ip"]}}
// /Pseudo host containing VMs not on any hosts.
// /Hosts of this pool.
// /Pool with its children.

View File

@@ -1,6 +1,17 @@
'use strict'
require 'angular'
angular.module('xoWebApp')
require 'angular-ui-router'
#=====================================================================
module.exports = angular.module 'xoWebApp.host', [
'ui.router'
]
.config ($stateProvider) ->
$stateProvider.state 'hosts_view',
url: '/hosts/:id'
controller: 'HostCtrl'
template: require './view'
.controller 'HostCtrl', ($scope, $stateParams, xoApi, xo, modal) ->
$scope.$watch(
-> xo.revision

267
app/modules/host/view.jade Normal file
View File

@@ -0,0 +1,267 @@
.grid
.grid-cell.flat-panel
p.page-title
i.xo-icon-host(class="xo-color-{{host.power_state | lowercase}}")
| {{host.name_label}}
small(ng-if="pool.name_label")
| (
a(ui-sref="pools_view({id: pool.UUID})") {{pool.name_label}}
| )
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-cogs(style="color: #e25440;")
| General
span.quick-edit(tooltip="Edit General settings", ng-click="hostSettings.$show()")
i.fa.fa-edit.fa-fw
.general-properties
form(editable-form="", name="hostSettings", onbeforesave="saveHost($data)")
dl.dl-horizontal
dt Name
dd
span(editable-text="host.name_label", e-name="name_label", e-form="hostSettings")
| {{host.name_label}}
dt Description
dd
span(editable-text="host.name_description", e-name="name_description", e-form="hostSettings")
| {{host.name_description}}
dt Enabled
dd
span(editable-checkbox="host.enabled", e-name="enabled", e-form="hostSettings")
| {{host.enabled}}
dt Tags
dd(ng-if="host.tags.length")
span(ng-repeat="tag in host.tags")
span.label.label-primary {{tag}}
dd(ng-if="!host.tags.length")
em No tags.
dt CPUs
dd {{host.CPUs["cpu_count"]}}x {{host.CPUs["modelname"]}}
dt Hostname
dd
| {{host.hostname}}
dt UUID
dd {{host.UUID}}
dt iQN
dd {{host.iSCSI_name}}
.btn-form(ng-show="hostSettings.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="hostSettings.$waiting", ng-click="hostSettings.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="submit", ng-disabled="hostSettings.$waiting")
i.fa.fa-save
| Save
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-bar-chart-o(style="color: #e25440;")
| Stats
.grid
.grid-cell
p.stat-name vCPUs/CPU usage:
p.center.big {{host.$vCPUs}}/{{host.CPUs["cpu_count"]}}
.grid-cell
p.stat-name RAM used:
p.center.big {{[host.memory.usage, host.memory.size] | %}}
.grid-cell
p.stat-name Running VMs:
p.center.big {{host.VMs.length}}
// Action panel
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-flash(style="color: #e25440;")
| Actions
.grid-cell.text-center
.grid
.grid-cell.btn-group
button.btn(tooltip="Add SR", type="button", style="width: 90%", xo-click="new_sr()")
i.xo-icon-sr.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Add VM", type="button", style="width: 90%", xo-sref="VMs_new({container: host.UUID})")
i.xo-icon-vm.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Reboot host", type="button", style="width: 90%", xo-click="rebootHost(host.UUID)")
i.fa.fa-refresh.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Shutdown host", type="button", style="width: 90%", xo-click="shutdownHost(host.UUID)")
i.fa.fa-power-off.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Restart toolstack", type="button", style="width: 90%", xo-click="restartToolStack(host.UUID)")
i.fa.fa-retweet.fa-2x.fa-fw
.grid-cell.btn-group(ng-if="pool.name_label")
button.btn(tooltip="Remove from pool", style="width: 90%", type="button", xo-click="pool_removeHost(host.UUID)")
i.fa.fa-cloud-upload.fa-2x.fa-fw
.grid-cell.btn-group(ng-if="!pool.name_label")
button.btn(tooltip="Add to pool", style="width: 90%", type="button", xo-click="pool_addHost(host.UUID)")
i.fa.fa-cloud-download.fa-2x.fa-fw
.grid-cell.btn-group(style="margin-bottom: 0.5em")
button.btn(tooltip="Host console", type="button", style="width: 90%", ng-repeat="controller in [host.controller] | resolve track by controller.UUID", xo-sref="consoles_view({id: controller.UUID})")
i.xo-icon-console.fa-2x.fa-fw
// TODO: Memory panel
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.xo-icon-memory(style="color: #e25440;")
| Memory
.progress
.progress-bar-host(ng-repeat="controller in [host.controller] | resolve track by controller.UUID", role="progressbar", aria-valuemin="0", aria-valuenow="{{controller.memory.size}}", aria-valuemax="{{host.memory.size}}", style="width: {{[controller.memory.size, host.memory.size] | %}}", tooltip="{{host.name_label}}: {{[controller.memory.size, host.memory.size] | %}}")
small {{host.name_label}}
.progress-bar.progress-bar-vm(ng-repeat="VM in host.VMs | resolve | orderBy:natural('name_label') track by VM.UUID", role="progressbar", aria-valuemin="0", aria-valuenow="{{VM.memory.size}}", aria-valuemax="{{host.memory.size}}", style="width: {{[VM.memory.size, host.memory.size] | %}}", xo-sref="VMs_view({id: VM.UUID})", tooltip="{{VM.name_label}}: {{[VM.memory.size, host.memory.size] | %}}")
small {{VM.name_label}}
ul.list-inline.text-center
li Total: {{host.memory.size | bytesToSize}}
li Currently used: {{host.memory.usage | bytesToSize}}
li Available: {{host.memory.size-host.memory.usage | bytesToSize}}
// SR panel
.grid
.grid-cell.flat-panel
form(editable-form="", name="hostSRs", onaftersave="saveTable()", oncancel="cancel()")
p.flat-panel-title
i.xo-icon-sr(style="color: #e25440;")
| Storage
span.quick-edit(tooltip="Edit storage", ng-click="hostSRs.$show()")
i.fa.fa-edit.fa-fw
table.table.table-hover
tr
th Name
th Format
th Size
th Physical/Allocated usage
th Type
th Status
// TODO: display PBD status for each SR of this host (connected or not)
// Shared SR
tr(xo-sref="SRs_view({id: SR.UUID})", ng-repeat="SR in pool.SRs | resolve | orderBy:natural('name_label') track by SR.UUID", ng-init="PBD = SRsToPBDs[SR.ref]")
td
| {{SR.name_label}}
td {{SR.SR_type}}
td {{SR.size | bytesToSize}}
td
.progress-condensed
.progress-bar(role="progressbar", aria-valuemin="0", aria-valuenow="{{SR.usage}}", aria-valuemax="{{SR.size}}", style="width: {{[SR.physical_usage, SR.size] | %}}", tooltip="Physical usage: {{[SR.physical_usage, SR.size] | %}}")
.progress-bar.progress-bar-info(role="progressbar", aria-valuemin="0", aria-valuenow="{{SR.physical_usage}}", aria-valuemax="{{SR.size}}", style="width: {{[(SR.usage-SR.physical_usage), SR.size] | %}}", tooltip="Allocated: {{[(SR.usage), SR.size] | %}}")
td
span.label.label-primary Shared
td(ng-if="PBD.attached")
span.label.label-success Connected
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="hostSRs.$visible", ng-click="disconnectSR(SR.ref)", tooltip="Disconnect this SR")
i.fa.fa-unlink.fa-lg
td(ng-if="!PBD.attached")
span.label.label-default Disconnected
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="hostSRs.$visible", ng-click="removeSR(SR.ref)", tooltip="Remove this SR")
i.fa.fa-trash-o.fa-lg
// Local SR
// TODO: migrate to SRs and not PBDs when implemented in xo-server spec
tr(xo-sref="SRs_view({id: SR.UUID})", ng-repeat="SR in host.SRs | resolve | orderBy:natural('name_label') track by SR.UUID", ng-init="PBD = SRsToPBDs[SR.ref]")
td
| {{SR.name_label}}
td {{SR.SR_type}}
td {{SR.size | bytesToSize}}
td
.progress-condensed
.progress-bar(role="progressbar", aria-valuemin="0", aria-valuenow="{{SR.usage}}", aria-valuemax="{{SR.size}}", style="width: {{[SR.physical_usage, SR.size] | %}}", tooltip="Physical usage: {{[SR.physical_usage, SR.size] | %}}")
.progress-bar.progress-bar-info(role="progressbar", aria-valuemin="0", aria-valuenow="{{SR.physical_usage}}", aria-valuemax="{{SR.size}}", style="width: {{[(SR.usage-SR.physical_usage), SR.size] | %}}", tooltip="Allocated: {{[(SR.usage), SR.size] | %}}")
td
span.label.label-info Local
td(ng-if="PBD.attached")
span.label.label-success Connected
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="hostSRs.$visible", ng-click="disconnectSR(SR.ref)", tooltip="Disconnect this SR")
i.fa.fa-unlink.fa-lg
td(ng-if="!PBD.attached")
span.label.label-default Disabled
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="hostSRs.$visible", ng-click="removeSR(SR.ref)", tooltip="Remove this SR")
i.fa.fa-trash-o.fa-lg
.btn-form(ng-show="hostSRs.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="hostSRs.$waiting", ng-click="hostSRs.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="button", ng-disabled="hostSRs.$waiting", ng-click="saveChanges()")
i.fa.fa-save
| Save
// Interfaces panel
.grid
.grid-cell.flat-panel
form(editable-form="", name="hostNetwork", onaftersave="saveTable()", oncancel="cancel()")
p.flat-panel-title
i.xo-icon-network(style="color: #e25440;")
| Interfaces
span.quick-edit(tooltip="Edit network", ng-click="hostNetwork.$show()")
i.fa.fa-edit.fa-fw
table.table.table-hover
th.col-md-1 Device
th.col-md-1 Address
th.col-md-2 MAC
th.col-md-1 MTU
th.col-md-1 Link status
tr(ng-repeat="PIF in host.$PIFs | resolve | orderBy:natural('name_label') track by PIF.UUID")
td
| {{PIF.device}}
span(ng-if="PIF.vlan > -1") .{{PIF.vlan}}
span.label.label-primary(ng-if="PIF.management") XAPI
td {{PIF.IP}} ({{PIF.mode}})
td {{PIF.MAC}}
td
span(editable-text="PIF.MTU", e-name="mtu", e-form="hostNetwork", e-required="")
| {{PIF.MTU}}
td(ng-if="PIF.attached")
span.label.label-success Connected
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="hostNetwork.$visible", ng-click="disconnectSR(Network.ref)", tooltip="Disconnect this Network")
i.fa.fa-unlink.fa-lg
td(ng-if="!PIF.attached")
span.label.label-default Disconnected
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="hostNetwork.$visible", ng-click="removeNetwork(Network.ref)", tooltip="Remove this Network")
i.fa.fa-trash-o.fa-lg
.btn-form(ng-show="hostNetwork.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="hostNetwork.$waiting", ng-click="hostNetwork.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="button", ng-disabled="hostNetwork.$waiting", ng-click="saveChanges()")
i.fa.fa-save
| Save
// CPU and Logs panels
.grid
// Task panel
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-tasks(style="color: #e25440;")
| Pending tasks
p.center(ng-if="!host.tasks.length") No recent tasks
table.table.table-hover(ng-if="host.tasks.length")
th.col-md-4 Date
th.col-md-8 Name
// TODO: working reverse order, from recent to oldest
tr(ng-repeat="task in host.tasks | resolve | orderBy:'created':true track by task.UUID")
td {{task.created}}
td
| {{task.name_label}}
a.quick-remove(xo-click="removeTask(task.UUID)")
i.fa.fa-trash-o.fa-fw
// Logs panel
.grid-cell.flat-panel
form(editable-form="", name="hostLogs", onaftersave="saveTable()", oncancel="cancel()")
p.flat-panel-title
i.fa.fa-comments(style="color: #e25440;")
| Logs
span.quick-edit(tooltip="Edit logs", ng-click="hostLogs.$show()")
i.fa.fa-edit.fa-fw
p.center(ng-if="!host.messages.length") No recent logs
table.table.table-hover(ng-if="host.messages.length")
th Date
th Name
tr(ng-repeat="message in host.messages | resolve | orderBy:'-time' track by message.UUID")
td {{message.time*1e3 | date:"medium"}}
td
| {{message.name}}
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="hostLogs.$visible", ng-click="deleteLog(message.UUID)")
i.fa.fa-trash-o.fa-lg(tooltip="Remove this log entry")
.btn-form(ng-show="hostLogs.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="hostLogs.$waiting", ng-click="hostLogs.$cancel()")
i.fa.fa-times
| Cancel

24
app/modules/list/index.js Normal file
View File

@@ -0,0 +1,24 @@
'use strict';
//====================================================================
/* global angular:false */
require('angular');
require('angular-ui-router');
//====================================================================
module.exports = angular.module('xoWebApp.list', [
'ui.router',
])
.config(function ($stateProvider) {
$stateProvider.state('list', {
url: '/list',
controller: 'ListCtrl',
template: require('./view'),
});
})
.controller('ListCtrl', function ($scope, xo) {
$scope.byTypes = xo.byTypes;
})
;

161
app/modules/list/view.jade Normal file
View File

@@ -0,0 +1,161 @@
// TODO: print a message when no entries.
// If it's a (named) pool.
.grid.flat-object(ng-repeat="pool in byTypes.pool | filter:listFilter | orderBy:natural('name_label') track by pool.UUID", ng-if="pool.name_label", xo-sref="pools_view({id: pool.UUID})")
// Icon.
.grid-cell.flat-cell.flat-cell-type
i.xo-icon-pool
// Properties & tags.
.grid-cell
// Properties.
.grid
.grid-cell
.grid
.grid-cell.flat-cell.flat-cell-name
| {{pool.name_label}}
.grid-cell.flat-cell.flat-cell-description
i {{pool.name_description}}
.grid-cell.flat-cell(ng-init="default_SR = (pool.default_SR | resolve)")
div(ng-if="default_SR")
| Default SR:
a(ui-sref="SRs_view({id: default_SR.UUID})") {{default_SR.name_label}}
div(ng-if="!default_SR")
em No default SR.
.grid-cell.flat-cell(ng-init="master = (pool.master | resolve)")
div(ng-if="master")
| Master:
a(ui-sref="hosts_view({id: master.UUID})") {{master.name_label}}
div(ng-if="!master")
em Unknown master.
.grid-cell.flat-cell
div(ng-if="pool.HA_enabled")
| HA enabled
div(ng-if="!pool.HA_enabled")
| HA disabled
.grid-cell.flat-cell
| {{pool.$running_hosts.length}}/{{pool.hosts.length}} hosts
// /Properties.
// Tags.
.grid
.grid-cell
.grid-cell.flat-cell-tag
i.fa.fa-tag
span(ng-repeat="tag in pool.tags")
span.label.label-primary {{tag}}
// /Tags.
// /Properties & tags.
// /Pool.
// If it's a host.
.grid.flat-object(ng-repeat="host in byTypes.host | filter:listFilter | orderBy:natural('name_label') track by host.UUID", xo-sref="hosts_view({id: host.UUID})")
// Icon.
.grid-cell.flat-cell.flat-cell-type
i.xo-icon-host(class="xo-color-{{host.power_state | lowercase}}")
// Properties & tags.
.grid-cell
// Properties.
.grid
.grid-cell
.grid
.grid-cell.flat-cell.flat-cell-name
| {{host.name_label}}
.grid-cell.flat-cell.flat-cell-description
i {{host.name_description}}
.grid-cell.flat-cell
| Address: {{host.address}}
.grid-cell.flat-cell
| {{host.$vCPUs}} vCPUs used on {{host.CPUs["cpu_count"]}} CPUs
.grid-cell.flat-cell
| {{host.memory.usage | bytesToSize}} used of {{host.memory.size | bytesToSize}} ({{[host.memory.usage, host.memory.size] | %}})
.grid-cell.flat-cell
| {{host.VMs.length}} VMs running
// /Properties.
// Tags.
.grid
.grid-cell
.grid-cell.flat-cell-tag
i.fa.fa-tag
span(ng-repeat="tag in host.tags")
span.label.label-primary {{tag}}
// /Tags.
// /Properties & tags.
// /Host.
// If it's a VM.
.grid.flat-object(ng-repeat="VM in byTypes.VM | filter:listFilter | orderBy:natural('name_label') track by VM.UUID", xo-sref="VMs_view({id: VM.UUID})")
// Icon.
.grid-cell.flat-cell.flat-cell-type
i.xo-icon-vm(class="xo-color-{{VM.power_state | lowercase}}")
// Properties & tags.
.grid-cell
// Properties.
.grid
.grid-cell
.grid
.grid-cell.flat-cell.flat-cell-name
| {{VM.name_label}}
.grid-cell.flat-cell.flat-cell-description
i {{VM.name_description}}
.grid-cell.flat-cell
| Address: {{VM.addresses["0/ip"]}}
.grid-cell.flat-cell
| {{VM.CPUs.number}} vCPUs
.grid-cell.flat-cell
| {{VM.memory.size | bytesToSize}} RAM
.grid-cell.flat-cell(ng-init="container = (VM.$container | resolve)")
div(ng-if="'pool' === container.type")
| Resident on:
a(ui-sref="pools_view({id: container.UUID})") {{container.name_label}}
div(ng-if="'host' === container.type", ng-init="pool = (container.poolRef | resolve)")
| Resident on:
a(ui-sref="hosts_view({id: container.UUID})") {{container.name_label}}
small(ng-if="pool.name_label")
| (
a(ui-sref="pools_view({id: pool.UUID})") {{pool.name_label}}
| )
// /Properties.
// Tags.
.grid
.grid-cell
.grid-cell.flat-cell-tag
i.fa.fa-tag
span(ng-repeat="tag in VM.tags")
span.label.label-primary {{tag}}
// /Tags.
// /Properties & tags.
// /VM.
// If it's a SR.
.grid.flat-object(ng-repeat="SR in byTypes.SR | filter:listFilter | orderBy:natural('name_label') track by SR.UUID", xo-sref="SRs_view({id: SR.UUID})")
// Icon.
.grid-cell.flat-cell.flat-cell-type
i.xo-icon-sr
// Properties & tags.
.grid-cell
// Properties.
.grid
.grid-cell
.grid
.grid-cell.flat-cell.flat-cell-name
| {{SR.name_label}}
.grid-cell.flat-cell.flat-cell-description
i {{SR.name_description}}
.grid-cell.flat-cell
| Usage: {{[SR.usage, SR.size] | %}} ({{SR.usage | bytesToSize}}/{{SR.size | bytesToSize}})
.grid-cell.flat-cell
| Type: {{SR.SR_type}}
.grid-cell.flat-cell(ng-init="container = (SR.$container | resolve)")
div(ng-if="'pool' === container.type")
strong
| Shared on
a(ui-sref="pools_view({id: container.UUID})") {{container.name_label}}
div(ng-if="'host' === container.type")
| Connected to
a(ui-sref="hosts_view({id: container.UUID})") {{container.name_label}}
// /Properties.
// Tags.
.grid
.grid-cell
.grid-cell.flat-cell-tag
i.fa.fa-tag
span(ng-repeat="tag in SR.tags")
span.label.label-primary {{tag}}
// /Tags.
// /Properties & tags.
// /SR.

View File

@@ -0,0 +1,33 @@
require 'angular'
require 'angular-ui-router'
#=====================================================================
module.exports = angular.module 'xoWebApp.login', [
'ui.router'
]
.config ($stateProvider) ->
$stateProvider.state 'login',
url: '/login'
controller: 'LoginCtrl'
template: require './view'
.controller 'LoginCtrl', ($scope, $state, $rootScope, xoApi) ->
toState = $rootScope._login?.state.name ? 'home'
toStateParams = $rootScope._login?.stateParams
delete $rootScope._login
$scope.$watch(
-> xoApi.user
(user) ->
# When the user is logged in, go the wanted view, fallbacks on
# the home view if necessary.
if user
$state.go toState, toStateParams
.catch ->
$state.go 'home'
)
Object.defineProperties $scope,
user: get: -> xoApi.get
status: get: -> xoApi.status
$scope.logIn = xoApi.logIn

View File

@@ -0,0 +1,50 @@
//- Hide the navbar for this view.
style.
.navbar {
display: none;
}
div.container
div.row-login
div.page-header
img(src = 'images/logo_small.png')
h2 Xen Orchestra
form.form-horizontal(
ng-submit = 'logIn(email, password, true)'
)
fieldset
legend.login: h3 Sign in
div.form-group
div.col-sm-12
input.form-control.input-sm(
name = 'email'
type = 'text'
placeholder = 'Username'
ng-model = 'email'
required
fix-autofill
)
div.form-group
div.col-sm-12
input.form-control.input-sm(
name = 'password'
type = 'password'
placeholder = 'Password'
ng-model = 'password'
required
fix-autofill
)
div.form-group
div.col-sm-12
button.btn.btn-login.btn-block.btn-success(
id = 'login'
name = 'login'
)
i.fa.fa-sign-in
| Login
p.status(ng-if = '"disconnected" === status')
i.xo-icon-error.fa-2x(tooltip = 'You are not connected to XO-Server')
p.status(ng-if = '"connecting" === status')
i.fa.fa-refresh.fa-spin.fa-2x(tooltip = 'Connecting to XO-Server')
p.status(ng-if = '"connected" === status')
i.xo-icon-success.fa-2x(tooltip = 'You are connected to XO-Server')

View File

@@ -0,0 +1,33 @@
require 'angular'
#=====================================================================
module.exports = angular.module 'xoWebApp.navbar', []
.controller 'NavbarCtrl', ($scope, $state, xoApi) ->
# TODO: It would make sense to inject xoApi in the scope.
$scope.$watch(
-> xoApi.status
(status) ->
$scope.status = status
)
$scope.$watch(
-> xoApi.user
(user) ->
$scope.user = user
)
$scope.logIn = xoApi.logIn
$scope.logOut = ->
xoApi.logOut()
$state.go 'login'
# When a searched is entered, we must switch to the list view if
# necessary.
$scope.ensureListView = ->
$state.go 'list'
.directive 'navbar', ->
return {
restrict: 'E'
controller: 'NavbarCtrl'
template: require './view'
replace: true
}

View File

@@ -0,0 +1,97 @@
nav.navbar.navbar-inverse.navbar-fixed-top(role="navigation")
//- Brand and toggle get grouped for better mobile display
.navbar-header
//- Button used to (un)collapse on mobile display.
button.navbar-toggle(type="button", ng-init="collapsed = true", ng-click="collapsed = !collapsed")
span.sr-only Toggle navigation
span.icon-bar
span.icon-bar
span.icon-bar
//- Brand name.
a.navbar-brand(ui-sref="home") Xen Orchestra
//- All navbar items are collapsed on mobile display.
.collapse.navbar-collapse(ng-class="!collapsed && 'in'")
//- Search form of the navbar.
form.navbar-form.navbar-left(role="search", style="width: 250px")
//- Forced width due to issue with `input`s (https://github.com/twbs/bootstrap/issues/9950.
.input-group
input.form-control.inverse(type="text", placeholder="", ng-model="$root.listFilter", ng-change="ensureListView()")
span.input-group-btn
button.btn.btn-search(type="button", ng-click="ensureListView()")
i.fa.fa-search
//- /Search form.
ul.nav.navbar-nav
li
a(href="https://xen-orchestra.com/services?from=xoa", target="_blank")
i.xo-icon-info
| Unregistered version
//- Right items of the navbar.
ul.nav.navbar-nav.navbar-right
li.navbar-text(ng-if="'disconnected' === status")
i.xo-icon-error
| Disconnected from XO-Server
li.navbar-text(ng-if="'connecting' === status")
i.fa.fa-refresh.fa-spin
| Connecting to XO-Server
//- Main menu.
li.dropdown
a.dropdown-toggle.inverse
i.fa.fa-th
ul.dropdown-menu.inverse
li(ui-sref-active="active", ui-route="/")
a(ui-sref="home")
i.fa.fa-indent
| Tree view
li(ui-sref-active="active", ui-route="/list")
a(ui-sref="list")
i.fa.fa-align-justify
| Flat view
li.disabled(ui-sref-active="active", ui-route="/graph")
a(ui-sref="graph")
i.fa.fa-sitemap
| Graphs view
li.divider
li.disabled
a
i.fa.fa-clock-o
| Scheduler
li(ui-sref-active="active", ui-route="/settings")
a(ui-sref="settings")
i.fa.fa-cog
| Settings
li.divider
li(ui-sref-active="active", ui-route="/about")
a(ui-sref="about")
i.fa.fa-info-circle(style="color:#5bc0de")
| About
//- /Main menu.
//- Displayed only when the user is connected.
li(ng-if="user")
a
i.fa.fa-user
| {{user.email}}
li(ng-if="user")
a(ng-click="logOut()")
i.fa.fa-sign-out
//- /When user is connected.
//- Displayed only when the user is not connected.
li.dropdown(ng-if="!user")
a.dropdown-toggle
| Log in
i.fa.fa-sign-in
form.dropdown-menu.login-form-dark(ng-submit="logIn(login.email, login.password, true)", ng-click="$event.stopPropagation()")
.input-group
span.input-group-addon
i.fa.fa-user.fa-fw
input.form-control(type="text", placeholder="Email", ng-model="login.email", name="email", required="")
.input-group
span.input-group-addon
i.fa.fa-key.fa-fw
input.form-control(type="password", placeholder="Password", name="password", ng-model="login.password", required="")
button.btn.btn-primary.btn-block(type="submit")
i.fa.fa-sign-in
| Log in
//- /When user is not connected.
//- /Right items.
//- /Navbar items.
//- /Navbar.

View File

@@ -0,0 +1,21 @@
require 'angular'
require 'angular-ui-router'
#=====================================================================
module.exports = angular.module 'xoWebApp.newSr', [
'ui.router'
]
.config ($stateProvider) ->
$stateProvider.state 'SRs_new',
url: '/srs/new/:container'
controller: 'NewSrCtrl'
template: require './view'
.controller 'NewSrCtrl', ($scope, $stateParams, xo) ->
$scope.$watch(
-> xo.revision
->
$scope.container = xo.get $stateParams.container
)

View File

@@ -0,0 +1,44 @@
// TODO: lots of stuff.
.grid
.grid-cell.flat-panel
p.page-title
i.xo-icon-vm
| Add SR on {{container.name_label}}
// Add server panel
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-info-circle(style="color: #e25440;")
| SR info
form.form-horizontal(role="form")
.form-group
label.col-sm-3.control-label Type
.col-sm-9
select.form-control
option(value="") -- Choose a type of SR --
optgroup(label="VDI SR")
option(value="NFS") NFS
option(value="ISCSI") Software iSCSI
option(value="HBA") HBA
optgroup(label="ISO SR")
option(value="CIFSISO") CIFS
option(value="NFSISO") NFS
.form-group
label.col-sm-3.control-label Name
.col-sm-9
input.form-control(type="text", placeholder="")
.form-group
label.col-sm-3.control-label Description
.col-sm-9
input.form-control(type="text", placeholder="SR Created by Xen Orchestra")
p.text-center
a.btn.btn-primary.big
i.fa.fa-times
| Clear
a.btn.btn-success.big
i.fa.fa-save
| Save
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-link(style="color: #e25440;")
| Connection

View File

@@ -1,7 +1,18 @@
'use strict'
require 'angular'
angular.module('xoWebApp')
.controller 'NewVmCtrl', (
require 'angular-ui-router'
#=====================================================================
module.exports = angular.module 'xoWebApp.newVms', [
'ui.router'
]
.config ($stateProvider) ->
$stateProvider.state 'VMs_new',
url: '/vms/new/:container'
controller: 'NewVms'
template: require './view'
.controller 'NewVms', (
$scope, $stateParams, $state
xoApi, xo
bytesToSizeFilter, sizeToBytesFilter
@@ -159,18 +170,21 @@ angular.module('xoWebApp')
VIF.MAC = VIF.MAC.trim()
delete VIF.MAC unless VIF.MAC
if installation_method is 'cdrom'
installation = {
method: 'cdrom'
repository: installation_cdrom
}
else
else if installation_network
matches = /^(http|ftp|nfs)/i.exec installation_network
# FIXME: handles invalid methods.
throw new Error 'invalid network URL' unless matches
installation = {
method: matches[1].toLowerCase()
repository: installation_network
}
else
installation = undefined
data = {
installation

View File

@@ -0,0 +1,197 @@
.grid
.grid-cell.flat-panel
p.page-title
i.xo-icon-vm
| Add VM on
a(ng-if="'pool' === container.type", ui-sref="pools_view({id: container.UUID})")
| {{container.name_label}}
a(ng-if="'host' === container.type", ui-sref="hosts_view({id: container.UUID})")
| {{container.name_label}}
//- Add server panel
form.form-horizontal(ng-submit="createVM()")
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-info-circle(style="color: #e25440;")
| VM info
.form-group
label.col-sm-3.control-label Template
.col-sm-9
select.form-control(ng-model="template", ng-options="template.name_label for template in templates | orderBy:natural('name_label') track by template.UUID", required="")
.form-group
label.col-sm-3.control-label Name
.col-sm-9
input.form-control(type="text", placeholder="Name of your new VM", required="", ng-model="name_label")
.form-group
label.col-sm-3.control-label Description
.col-sm-9
input.form-control(type="text", placeholder="Optional description of you new VM", ng-model="name_description")
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-dashboard(style="color: #e25440;")
| Performances
.form-group
label.col-sm-3.control-label vCPUs
.col-sm-9
input.form-control(type="text", placeholder="{{template.CPUs.number}}", ng-model="CPUs")
.form-group
label.col-sm-3.control-label RAM
.col-sm-9
input.form-control(type="text", placeholder="{{template.memory.size | bytesToSize}}", ng-model="memory")
.grid
//- Install panel
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-download(style="color: #e25440;")
| Install settings
.form-group(ng-show="availableMethods.cdrom")
label.col-sm-3.control-label ISO/DVD
.col-sm-9
.input-group
span.input-group-addon
input(
type = 'radio'
name = 'installation_method'
ng-model = 'installation_method'
value = 'cdrom'
required
)
select.form-control.disabled(
ng-disabled="'cdrom' !== installation_method"
ng-model="installation_cdrom"
required
)
option(value = '') Please select
optgroup(ng-repeat="SR in ISO_SRs | orderBy:natural('name_label') track by SR.UUID", ng-if="SR.VDIs.length", label="{{SR.name_label}}")
option(ng-repeat="VDI in SR.VDIs | resolve | orderBy:natural('name_label') track by VDI.UUID", ng-value="VDI.UUID")
| {{VDI.name_label}}
.form-group(ng-show="availableMethods.http || availableMethods.ftp || availableMethods.nfs")
label.col-sm-3.control-label Network
.col-sm-9
.input-group
span.input-group-addon
input(
type = 'radio'
name = 'installation_method'
ng-model = 'installation_method'
value = 'network'
required
)
input.form-control(type="text", ng-disabled="'network' !== installation_method", placeholder="e.g: http://ftp.debian.org/debian", ng-model="installation_network")
//- <div class="form-group"> FIXME
//- <label class="col-sm-3 control-label">Home server</label>
//- <div class="col-sm-9">
//- <select class="form-control">
//- <option>Default (auto)</option>
//- </select>
//- </div>
//- </div>
//- Interface panel
.grid-cell.flat-panel
p.flat-panel-title
i.xo-icon-network(style="color: #e25440;")
| Interfaces
table.table.table-hover
tr
th MAC
th Network
th.col-md-1 &#160;
//- Buttons
tr(ng-repeat="VIF in VIFs track by VIF.id")
td
input.form-control(type="text", ng-model="VIF.MAC", ng-pattern="/^\s*[0-9a-f]{2}(:[0-9a-f]{2}){5}\s*$/i", placeholder="00:00:00:00:00")
td
select.form-control(
ng-options = 'network.UUID as network.name_label for network in (networks | orderBy:natural("name_label"))'
ng-model = 'VIF.network'
required
)
option(value = '') Please select
td
.pull-right
button.btn.btn-default(type="button", ng-click="removeVIF($index)", title="Remove this interface")
i.fa.fa-times
.btn-form
p.center
.btn-form
p.center
button.btn.btn-success(type="button", ng-click="addVIF()")
i.fa.fa-plus
| Add interface
//- end of misc and interface panel
//- Disk panel
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.xo-icon-sr(style="color: #e25440;")
| Disks
table.table.table-hover
tr
th.col-md-2 SR
th.col-md-1 Bootable?
th.col-md-2 Size
th.col-md-2 Name
th.col-md-4 Description
th.col-md-1 &#160;
//- Buttons
tr(ng-repeat="VDI in VDIs track by VDI.id")
td
select.form-control(ng-model="VDI.SR", ng-options="SR.UUID as SR.name_label for SR in (writable_SRs | orderBy:natural('name_label'))")
td.text-center
input(type="checkbox", ng-model="VDI.bootable")
td
input.form-control(type="text", ng-model="VDI.size", required="")
td
input.form-control(type="text", placeholder="Name of this virtual disk", ng-model="VDI.name_label")
td
input.form-control(type="text", placeholder="Description of this virtual disk", ng-model="VDI.name_description")
td
.btn-group
button.btn.btn-default(type="button", ng-click="moveVDI($index, -1)", ng-disabled="$first", title="Move this disk up")
i.fa.fa-chevron-up
button.btn.btn-default(type="button", ng-click="moveVDI($index, 1)", ng-disabled="$last", title="Move this disk down")
i.fa.fa-chevron-down
.pull-right
button.btn.btn-default(type="button", ng-click="removeVDI($index)", title="Remove this disk")
i.fa.fa-times
.btn-form
p.center
.btn-form
p.center
button.btn.btn-success(type="button", ng-click="addVDI()")
i.fa.fa-plus
| Add disk
//- Confirmation panel
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-flag-checkered(style="color: #e25440;")
| Summary
.grid
.grid-cell
p.stat-name
| Name:
p.center.big {{name_label}}
.grid-cell
p.stat-name
| Template:
p.center {{template.name_label}}
.grid
.grid-cell
p.stat-name vCPUs
p.center.big {{CPUs || template.CPUs.number}}
.grid-cell
p.stat-name RAM
p.center.big {{(memory) || (template.memory.size | bytesToSize)}}
.grid-cell
p.stat-name Disks
p.center.big {{(VDIs.length) || (template.$VBDs.length) || 0}}
.grid-cell
p.stat-name Interfaces
p.center.big {{VIFs.length}}
p.center
button.btn.btn-lg.btn-primary(type="submit")
i.fa.fa-play
| Create VM

View File

@@ -1,6 +1,17 @@
'use strict'
require 'angular'
angular.module('xoWebApp')
require 'angular-ui-router'
#=====================================================================
module.exports = angular.module 'xoWebApp.pool', [
'ui.router'
]
.config ($stateProvider) ->
$stateProvider.state 'pools_view',
url: '/pools/:id'
controller: 'PoolCtrl'
template: require './view'
.controller 'PoolCtrl', ($scope, $stateParams, xoApi, xo) ->
$scope.$watch(
-> xo.revision

137
app/modules/pool/view.jade Normal file
View File

@@ -0,0 +1,137 @@
// TODO: lots of stuff.
.grid
.grid-cell.flat-panel
p.page-title
i.xo-icon-pool
| {{pool.name_label}}
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-cogs(style="color: #e25440;")
| General
span.quick-edit(tooltip="Edit General settings", ng-click="poolSettings.$show()")
i.fa.fa-edit.fa-fw
.general-properties
form(editable-form="", name="poolSettings", onbeforesave="savePool($data)")
dl.dl-horizontal
dt Name
dd
span(editable-text="pool.name_label", e-name="name_label", e-form="poolSettings")
| {{pool.name_label}}
dt Description
dd
span(editable-text="pool.name_description", e-name="name_description", e-form="poolSettings")
| {{pool.name_description}}
dt Master
dd(ng-repeat="master in [pool.master] | resolve")
a(ui-sref="hosts_view({id: master.UUID})")
| {{master.name_label}}
dt Tags
dd
span(ng-repeat="tag in pool.tags")
span.label.label-primary {{tag}}
dt(ng-if="pool.default_SR") Default SR
dd(ng-if="pool.default_SR", ng-init="default_SR = (pool.default_SR | resolve)")
a(ui-sref="SRs_view({id: default_SR.UUID})") {{default_SR.name_label}}
dt HA
dd
| {{pool.HA_enabled}}
dt UUID
dd {{pool.UUID}}
.btn-form(ng-show="poolSettings.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="poolSettings.$waiting", ng-click="poolSettings.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="submit", ng-disabled="poolSettings.$waiting")
i.fa.fa-save
| Save
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-bar-chart-o(style="color: #e25440;")
| Stats
.grid
.grid-cell
p.stat-name Hosts:
p.center.big {{pool.hosts.length}}
.grid-cell
p.stat-name Running:
p.center.big {{pool.$running_hosts.length}}
// Action panel
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-flash(style="color: #e25440;")
| Actions
.grid
.grid-cell.text-center.grid-button(tooltip="Add SR", xo-sref="SRs_new({container: pool.UUID})")
i.xo-icon-sr.fa-2x.fa-fw
.grid-cell.text-center.grid-button(tooltip="Add VM", xo-sref="VMs_new({container: pool.UUID})")
i.xo-icon-vm.fa-2x.fa-fw
.grid-cell.text-center.grid-button(tooltip="Add Host")
i.xo-icon-host.fa-2x.fa-fw
.grid-cell.text-center.grid-button(tooltip="Disconnect")
i.fa.fa-unlink.fa-2x.fa-fw
// Hosts panel
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.xo-icon-host(style="color: #e25440;")
| Hosts
table.table.table-hover.table-condensed
th Name
th.col-md-4 Description
th.col-md-6 Memory
tr(xo-sref="hosts_view({id: host.UUID})", ng-repeat="host in pool.hosts | resolve | orderBy:natural('name_label') track by host.UUID")
td {{host.name_label}}
td {{host.name_description}}
td
.progress-condensed
.progress-bar(role="progressbar", aria-valuemin="0", aria-valuenow="{{host.memory.usage}}", aria-valuemax="{{host.memory.size}}", style="width: {{[host.memory.usage, host.memory.size] | %}}")
// Shared SR panel
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.xo-icon-sr(style="color: #e25440;")
| Shared SR
table.table.table-hover
th Name
th Description
th Type
th Size
th.col-md-4 Physical/Allocated usage
tr(xo-sref="SRs_view({id: SR.UUID})", ng-repeat="SR in pool.SRs | resolve | orderBy:natural('name_label') track by SR.UUID")
td {{SR.name_label}}
td {{SR.name_description}}
td {{SR.SR_type}}
td {{SR.size | bytesToSize}}
td
.progress-condensed
.progress-bar(role="progressbar", aria-valuemin="0", aria-valuenow="{{SR.usage}}", aria-valuemax="{{SR.size}}", style="width: {{[SR.physical_usage, SR.size] | %}}", tooltip="Physical usage: {{[SR.physical_usage, SR.size] | %}}")
.progress-bar.progress-bar-info(role="progressbar", aria-valuemin="0", aria-valuenow="{{SR.physical_usage}}", aria-valuemax="{{SR.size}}", style="width: {{[(SR.usage-SR.physical_usage), SR.size] | %}}", tooltip="Allocated: {{[(SR.usage), SR.size] | %}}")
// TODO: CPU and Logs panels
.grid
// Logs panel
.grid-cell.flat-panel
form(editable-form="", name="poolLogs", onaftersave="saveTable()", oncancel="cancel()")
p.flat-panel-title
i.fa.fa-comments(style="color: #e25440;")
| Logs
span.quick-edit(tooltip="Edit logs", ng-click="poolLogs.$show()")
i.fa.fa-edit.fa-fw
p.center(ng-if="!pool.messages.length") No recent logs
table.table.table-hover(ng-if="pool.messages.length")
th Date
th Name
tr(ng-repeat="message in pool.messages | resolve | orderBy:'-time' track by message.UUID")
td {{message.time*1e3 | date:"medium"}}
td
| {{message.name}}
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="poolLogs.$visible", ng-click="deleteLog(message.UUID)")
i.fa.fa-trash-o.fa-lg(tooltip="Remove this log entry")
.btn-form(ng-show="poolLogs.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="poolLogs.$waiting", ng-click="poolLogs.$cancel()")
i.fa.fa-times
| Cancel

View File

@@ -1,9 +1,19 @@
'use strict'
require 'angular'
require 'angular-ui-router'
#=====================================================================
# FIXME: Mutualize the code between users and servers.
angular.module('xoWebApp')
.controller 'SettingsCtrl', ($scope, xoApi) ->
module.exports = angular.module 'xoWebApp.settings', [
'ui.router'
]
.config ($stateProvider) ->
$stateProvider.state 'settings',
url: '/settings'
controller: 'SettingsCtrl'
template: require './view'
.controller 'SettingsCtrl', ($scope, xo) ->
$scope.permissions = [
{
label: 'None'
@@ -27,7 +37,7 @@ angular.module('xoWebApp')
do ->
# Fetches them.
$scope.users = []
xoApi.call('user.getAll').then (users) ->
xo.user.getAll().then (users) ->
$scope.users = users
# Which ones are selected?
@@ -59,13 +69,15 @@ angular.module('xoWebApp')
{id} = user
if selected[id]
delete selected[id]
xoApi.call 'user.delete', {id}
# FIXME: this cast should not be necessary.
xo.user.delete "#{id}"
else
# Only sets the password if not empty.
delete user.password unless user.password
# TODO: only update users which have been modified.
xoApi.call 'user.set', user
xo.user.set user
# Remove the password from the interface.
delete user.password
@@ -79,7 +91,7 @@ angular.module('xoWebApp')
continue unless email
# Sends the order to XO-Server.
xoApi.call 'user.create', {email, permission, password}
xo.user.create {email, permission, password}
# The password should not be displayed.
delete user.password
@@ -96,7 +108,7 @@ angular.module('xoWebApp')
do ->
# Fetches them.
$scope.servers = []
xoApi.call('server.getAll').then (servers) ->
xo.server.getAll().then (servers) ->
$scope.servers = servers
# Which ones are selected?
@@ -125,13 +137,13 @@ angular.module('xoWebApp')
{id} = server
if selected[id]
delete selected[id]
xoApi.call 'server.remove', {id}
xo.server.remove id
else
# Only sets the password if not empty.
delete server.password unless server.password
# TODO: only update servers which have been modified.
xoApi.call 'server.set', server
xo.server.set server
# Remove the password from the interface.
delete server.password
@@ -145,7 +157,7 @@ angular.module('xoWebApp')
continue unless host
# Sends the order to XO-Server.
xoApi.call 'server.add', {host, username, password}
xo.server.add {host, username, password}
# The password should not be displayed.
delete server.password

View File

@@ -0,0 +1,78 @@
// TODO: lots of stuff.
.grid
.grid-cell.flat-panel
p.page-title
i.fa.fa-cog
| XO Settings
// Add server panel
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-link(style="color: #e25440;")
| Connected servers
form(ng-submit="saveServers()", autocomplete="off")
table.table.table-hover
tr
th Host
th User
th Password
th Delete
tr(ng-repeat="server in servers | orderBy:natural('host') track by server.id")
td
input.form-control(type="text", ng-model="server.host")
td
input.form-control(type="text", ng-model="server.username")
td
input.form-control(type="password", ng-model="server.password", placeholder="Fill to change the password")
td
input(type="checkbox", ng-model="selectedServers[server.id]")
tr(ng-repeat="server in newServers")
td
input.form-control(type="text", ng-model="server.host", placeholder="address[:port]")
td
input.form-control(type="text", ng-model="server.username", placeholder="user")
td
input.form-control(type="password", ng-model="server.password", placeholder="password")
td &#160;
p.text-center
button.btn.btn-primary(type="submit")
i.fa.fa-save
| Save
| &nbsp;
button.btn.btn-success(type="button", ng-click="addServer()")
i.fa.fa-plus
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-users(style="color: #e25440;")
| Users
form(ng-submit="saveUsers()", autocomplete="off")
table.table.table-hover
tr
th Email
th Permissions
th Password
th Delete
tr(ng-repeat="user in users | orderBy:natural('email') track by user.id")
td
input.form-control(type="text", ng-model="user.email")
td
select.form-control(ng-options="p.value as p.label for p in permissions", ng-model="user.permission")
td
input.form-control(type="password", ng-model="user.password", placeholder="Fill to change the password")
td
input(type="checkbox", ng-model="selectedUsers[user.id]")
tr(ng-repeat="user in newUsers")
td
input.form-control(type="text", ng-model="user.email", placeholder="email")
td
select.form-control(ng-options="p.value as p.label for p in permissions", ng-model="user.permission")
td
input.form-control(type="password", ng-model="user.password", placeholder="password")
td &#160;
p.text-center
button.btn.btn-primary(type="submit")
i.fa.fa-save
| Save
| &nbsp;
button.btn.btn-success(type="button", ng-click="addUser()")
i.fa.fa-plus

View File

@@ -1,6 +1,17 @@
'use strict'
require 'angular'
angular.module('xoWebApp')
require 'angular-ui-router'
#=====================================================================
module.exports = angular.module 'xoWebApp.sr', [
'ui.router'
]
.config ($stateProvider) ->
$stateProvider.state 'SRs_view',
url: '/srs/:id'
controller: 'SrCtrl'
template: require './view'
.controller 'SrCtrl', ($scope, $stateParams, xoApi, xo, modal) ->
$scope.$watch(
-> xo.revision

166
app/modules/sr/view.jade Normal file
View File

@@ -0,0 +1,166 @@
.grid
.grid-cell.flat-panel
p.page-title
i.xo-icon-sr
| {{SR.name_label}}
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-cogs(style="color: #e25440;")
| General
span.quick-edit(tooltip="Edit General settings", ng-click="srSettings.$show()")
i.fa.fa-edit.fa-fw
.general-properties
form(editable-form="", name="srSettings", onbeforesave="saveSR($data)")
dl.dl-horizontal
dt Name
dd
span(editable-text="SR.name_label", e-name="name_label", e-form="srSettings")
| {{SR.name_label}}
dt Description
dd
span(editable-text="SR.name_description", e-name="name_description", e-form="srSettings")
| {{SR.name_description}}
dt Content type:
dd {{SR.SR_type}}
dt Tags
dd(ng-if="SR.tags.length")
span(ng-repeat="tag in SR.tags")
span.label.label-primary {{tag}}
dd(ng-if="!SR.tags.length")
em No tags.
dt Shared
div(ng-repeat="container in [SR.$container] | resolve")
dd(ng-if="'pool' === container.type")
| Yes (
a(ui-sref="pools_view({id: container.UUID})") {{container.name_label}}
| )
dd(ng-if="'host' === container.type") No
dt Size
dd {{SR.size | bytesToSize}}
dt UUID
dd {{SR.UUID}}
.btn-form(ng-show="srSettings.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="srSettings.$waiting", ng-click="srSettings.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="submit", ng-disabled="srSettings.$waiting")
i.fa.fa-save
| Save
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-bar-chart-o(style="color: #e25440;")
| Stats
.grid
.grid-cell
p.stat-name Physical Alloc:
p.center.big {{[SR.physical_usage, SR.size] | %}}
.grid-cell
p.stat-name Virtual Alloc:
p.center.big {{[SR.usage, SR.size] | %}}
.grid-cell
p.stat-name VDIs:
p.center.big {{SR.VDIs.length}}
// TODO: Space panel
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.xo-icon-memory(style="color: #e25440;")
| VDI Map
.progress
.progress-bar.progress-bar-vm(ng-if="((VDI.size/SR.size)*100) > 0.5", ng-repeat="VDI in SR.VDIs | resolve | orderBy:natural('name_label') track by VDI.UUID", role="progressbar", aria-valuemin="0", aria-valuenow="{{VDI.size}}", aria-valuemax="{{SR.size}}", style="width: {{[VDI.size, SR.size] | %}}", xo-sref="VDIs_view({id: VDI.UUID})", tooltip="{{VDI.name_label}} ({{[VDI.size, SR.size] | %}})")
// display the name only if it fits in its progress bar
span(ng-if="VDI.name_label.length < ((VDI.size/SR.size)*100)") {{VDI.name_label}}
ul.list-inline.text-center
li Total: {{SR.size | bytesToSize}}
li Currently used: {{SR.usage | bytesToSize}}
li Available: {{SR.size-SR.usage | bytesToSize}}
// TODO: VDIs.
.grid
.grid-cell.flat-panel
form(editable-form="", name="srDisks", onbeforesave="saveVDI()", oncancel="cancel()")
p.flat-panel-title
i.fa.fa-hdd-o(style="color: #e25440;")
| Virtual disks
span.quick-edit(tooltip="Edit disks", ng-click="srDisks.$show()")
i.fa.fa-edit.fa-fw
span.quick-edit(tooltip="Rescan", ng-click="rescanSr(SR.UUID)")
i.fa.fa-refresh.fa-fw
table.table.table-hover
tr
th Name
th Description
th Size
th Attached to:
tr(ng-repeat="VDI in SR.VDIs | resolve | orderBy:natural('name_label')", xo-sref="VDIs_view({id: VDI.UUID})")
td
span(editable-text="VDI.name_label", e-name="name_description", e-form="srDisks")
| {{VDI.name_label}}
span.label.label-info(ng-if="VDI.$snapshot_of") snapshot
td
span(editable-text="VDI.name_description", e-name="name_description", e-form="srDisks")
| {{VDI.name_description}}
td
span(editable-text="VDI.size | bytesToSize", e-name="size", e-form="srDisks")
| {{VDI.size | bytesToSize}}
td(ng-if="((VDI.$VBD | resolve).VM)", ng-init="VBD = (VDI.$VBD | resolve)")
| {{(((VDI.$VBD | resolve).VM) | resolve).name_label}}
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="srDisks.$visible", ng-click="disconnectVBD(VBD.UUID)", tooltip="Disconnect this disk")
i.fa.fa-unlink.fa-lg
td(ng-if="!((VDI.$VBD | resolve).VM)")
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="srDisks.$visible", ng-click="deleteVDI(VDI.UUID)", tooltip="Destroy this disk")
i.fa.fa-trash-o.fa-lg
.btn-form(ng-show="srDisks.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="srDisks.$waiting", ng-click="srDisks.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="submit", ng-disabled="srDisks.$waiting")
i.fa.fa-save
| Save
button.btn.btn-success.pull-right(type="button", ng-disabled="srDisks.$waiting", ng-click="addVDI()")
i.fa.fa-plus
| Add Disk
// /VDIs.
// Hosts.
.grid
.grid-cell.flat-panel
form(editable-form="", name="srHosts", onaftersave="saveTable()", oncancel="cancel()")
p.flat-panel-title
i.fa.fa-link(style="color: #e25440;")
| Connected hosts
span.quick-edit(tooltip="Edit connected hosts", ng-click="srHosts.$show()")
i.fa.fa-edit.fa-fw
span.quick-edit(tooltip="Reconnect all hosts", ng-click="reconnectAllHosts()")
i.fa.fa-plus-square.fa-fw
table.table.table-hover
th Name
th Status
tr(ng-repeat="PBD in SR.$PBDs | resolve", ng-init="host = (PBD.host | resolve)", xo-sref="hosts_view({id: host.UUID})")
td {{host.name_label}}
td(ng-if="PBD.attached")
span.label.label-success Connected
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="srHosts.$visible", ng-click="disconnectPBD(host.UUID)", tooltip="Disconnect this host")
i.fa.fa-unlink.fa-lg
td(ng-if="!PBD.attached")
span.label.label-default Disabled
button.btn.btn-sm.btn-primary.pull-right(type="button", ng-show="srHosts.$visible", ng-click="connectPBD(host.UUID)", tooltip="Connect this host")
i.fa.fa-link.fa-lg
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-comments(style="color: #e25440;")
| Logs
p.center(ng-if="!SR.messages.length") No recent logs
table.table.table-hover(ng-if="SR.messages.length")
th.col-md-1 Date
th.col-md-1 Name
tr(ng-repeat="message in SR.messages | resolve | orderBy:'-time' track by message.UUID")
td {{message.time*1e3 | date:"medium"}}
td
| {{message.name}}
a.quick-remove(tooltip="Remove log")
i.fa.fa-trash-o.fa-fw
// /Hosts.

View File

@@ -1,8 +1,19 @@
'use strict'
require 'angular'
angular.module('xoWebApp')
.controller 'VmCtrl', (
$scope, $stateParams
require 'angular-ui-router'
#=====================================================================
module.exports = angular.module 'xoWebApp.pool', [
'ui.router'
]
.config ($stateProvider) ->
$stateProvider.state 'VMs_view',
url: '/vms/:id'
controller: 'PoolCtrl'
template: require './view'
.controller 'PoolCtrl', (
$scope, $state, $stateParams
xoApi, xo
sizeToBytesFilter, bytesToSizeFilter
modal
@@ -42,12 +53,14 @@ angular.module('xoWebApp')
$scope.force_rebootVM = (id) -> xo.vm.restart id, true
$scope.migrateVM = xo.vm.migrate
$scope.destroyVM = (id) ->
modal.confirm({
modal.confirm
title: 'VM deletion'
message: 'Are you sure you want to delete this VM? (including its disks)'
}).then ->
.then ->
# FIXME: provides a way to not delete its disks.
xo.vm.delete id, true
.then ->
$state.go 'home'
$scope.saveSnapshot = (id, $data) ->
snapshot = get (id)
@@ -106,12 +119,25 @@ angular.module('xoWebApp')
xoApi.call 'vbd.disconnect', {id: UUID}
$scope.cloneVM = (id, vm_name, full_copy) ->
clone_name = "#{vm_name}_clone"
console.log "Copy VM #{id} #{clone_name} with full copy at #{full_copy}"
xo.vm.clone id, clone_name, full_copy
$scope.snapshotVM = (id, vm_name) ->
date = dateFilter Date.now(), 'yyyy-MM-ddTHH:mmZ'
snapshot_name = "#{vm_name}_#{date}"
console.log "Snapshot #{snapshot_name} from VM #{id}"
xo.vm.createSnapshot id, snapshot_name
$scope.convertVM = (id) ->
console.log "Convert VM #{id}"
modal.confirm({
title: 'VM to template'
message: 'Are you sure you want to convert this VM into a template?'
}).then ->
xo.vm.convert id
$scope.deleteSnapshot = (id) ->
console.log "Delete snapshot #{id}"
modal.confirm({
@@ -135,9 +161,12 @@ angular.module('xoWebApp')
$scope.osType = (osName) ->
switch osName
when 'debian','ubuntu','centos','suse','redhat','oracle','gentoo'
when 'debian','ubuntu','centos','suse','redhat','oracle','gentoo','suse','fedora'
'linux'
when 'windows'
'windows'
else
'other'
$scope.saveDisks = ($data) ->
console.log $data

328
app/modules/vm/view.jade Normal file
View File

@@ -0,0 +1,328 @@
.grid
.grid-cell.flat-panel
p.page-title
i.xo-icon-vm(class="xo-color-{{VM.power_state | lowercase}}")
| {{VM.name_label}}
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-cogs(style="color: #e25440;")
| General
span.quick-edit(tooltip="Edit General settings", ng-click="vmSettings.$show()")
i.fa.fa-edit.fa-fw
.general-properties
form(editable-form="", name="vmSettings", onbeforesave="saveVM($data)")
dl.dl-horizontal
dt Name
dd
span(editable-text="VM.name_label", e-name="name_label", e-form="vmSettings")
| {{VM.name_label}}
dt Description
dd
span(editable-text="VM.name_description", e-name="name_description", e-form="vmSettings")
| {{VM.name_description}}
dt(ng-if="VM.power_state == ('Running' || 'Paused')") Running on:
dt(ng-if="VM.power_state == ('Halted')") Resident on:
dd(ng-repeat="container in [VM.$container] | resolve")
a(xo-sref="hosts_view({id: container.UUID})")
| {{container.name_label}}
dt Address
dd(ng-repeat="IP in VM.addresses") {{IP}}
dt Tags
dd
dt vCPUs
dd
span(editable-text="VM.CPUs.number", e-name="CPUs", e-form="vmSettings")
| {{VM.CPUs.number}}
dt RAM
dd
span(editable-text="memorySize", e-name="memory", e-form="vmSettings")
| {{memorySize}}
dt UUID
dd {{VM.UUID}}
.btn-form(ng-show="vmSettings.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="vmSettings.$waiting", ng-click="vmSettings.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="submit", ng-disabled="vmSettings.$waiting")
i.fa.fa-save
| Save
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-bar-chart-o(style="color: #e25440;")
| Stats
.grid
.grid-cell
p.stat-name vCPUs
p.center.big {{VM.CPUs.number}}
.grid-cell
p.stat-name RAM
p.center.big {{VM.memory.size | bytesToSize}}
.grid-cell
p.stat-name Disks
p.center.big {{VM.$VBDs.length || 0}}
br
.grid
.grid-cell(ng-if="VM.os_version.distro")
p.stat-name OS:
p.center.big
i(class="xo-icon-{{osType(VM.os_version.distro)}}",tooltip="{{VM.os_version.name}}", style="color: black;")
.grid-cell
p.stat-name Xen tools:
p.center
span(ng-if="VM.PV_drivers", style="color:green;") Installed
span(ng-if="!VM.PV_drivers") NOT installed
// Action panel
.grid
.grid-cell.flat-panel
p.flat-panel-title
i.fa.fa-flash(style="color: #e25440;")
| Actions
.grid-cell.text-center
.grid
.grid-cell.btn-group
button.btn(tooltip="Stop VM", type="button", style="width: 90%", xo-click="stopVM(VM.UUID)")
i.fa.fa-stop.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Start VM", type="button", style="width: 90%", xo-click="startVM(VM.UUID)")
i.fa.fa-play.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Reboot VM", type="button", style="width: 90%", xo-click="rebootVM(VM.UUID)")
i.fa.fa-refresh.fa-2x.fa-fw
.grid-cell.btn-group.dropdown
button.btn.dropdown-toggle(tooltip="Migrate VM", type="button", style="width: 90%", data-toggle="dropdown")
i.fa.fa-share.fa-2x.fa-fw
<span class="caret"></span>
ul.dropdown-menu.left(role="menu")
li(ng-repeat="host in hosts | orderBy:natural('name_label') track by host.UUID")
a(ng-click="migrateVM(VM.UUID, host.UUID)")
i.xo-icon-host.fa-fw
| To {{host.name_label}}
.grid-cell.btn-group
button.btn(tooltip="Force Reboot", type="button", style="width: 90%", xo-click="force_rebootVM(VM.UUID)")
i.fa.fa-flash.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Force Shutdown", type="button", style="width: 90%", xo-click="force_stopVM(VM.UUID)")
i.fa.fa-power-off.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Delete VM", type="button", style="width: 90%", xo-click="destroyVM(VM.UUID)")
i.fa.fa-trash-o.fa-2x.fa-fw
.grid-cell.btn-group.dropdown
button.btn.dropdown-toggle(tooltip="Create a clone", style="width: 90%", type="button", data-toggle="dropdown")
i.fa.fa-files-o.fa-2x.fa-fw
<span class="caret"></span>
ul.dropdown-menu.left(role="menu")
li
a(ng-click="cloneVM(VM.UUID,VM.name_label,false)")
i.fa.fa-code-fork.fa-fw
| Fast clone
li
a(ng-click="cloneVM(VM.UUID,VM.name_label,true)")
i.xo-icon-sr.fa-fw
| Full disk copy
.grid-cell.btn-group
button.btn(tooltip="Convert to template", type="button", style="width: 90%", xo-click="convertVM(VM.UUID)")
i.fa.fa-thumb-tack.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Create a snapshot", style="width: 90%", type="button", xo-click="snapshotVM(VM.UUID,VM.name_label)")
i.xo-icon-snapshot.fa-2x.fa-fw
.grid-cell.btn-group(style="margin-bottom: 0.5em")
button.btn(tooltip="VM Console", type="button", style="width: 90%", xo-sref="consoles_view({id: VM.UUID})")
i.xo-icon-console.fa-2x.fa-fw
// Disk panel
.grid
.grid-cell.flat-panel
form(editable-form="", name="vmDisks", onaftersave="saveTable()", oncancel="cancel()")
p.flat-panel-title
i.xo-icon-sr(style="color: #e25440;")
| Disk
span.quick-edit(tooltip="Edit disks", ng-click="vmDisks.$show()")
i.fa.fa-edit.fa-fw
table.table.table-hover
tr
th Name
th Description
th Size
th SR
th Status
// FIXME: ng-init seems to disrupt the implicit $watch.
tr(ng-repeat="VDI in VDIs | orderBy:natural('name_label') track by VDI.UUID", ng-init="SR = (VDI.$SR | resolve); VBD = (VDI.$VBD | resolve)", xo-sref="SRs_view({id: SR.UUID})")
td
span(editable-text="VDI.name_label", e-name="name_label", e-form="vmDisks", e-required="")
| {{VDI.name_label}}
td
span(editable-text="VDI.name_description", e-name="name_description", e-form="vmDisks")
| {{VDI.name_description}}
td
span(editable-text="VDI.size | bytesToSize", e-name="size", e-form="vmDisks")
| {{VDI.size | bytesToSize}}
td
// Are SR editable? will trigger moving VDI to the new SR
| {{SR.name_label}}
td(ng-if="VBD.attached")
span.label.label-success Connected
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="vmDisks.$visible", ng-click="disconnectVBD(VBD.UUID)")
i.fa.fa-unlink.fa-lg(tooltip="Disconnect this disk")
td(ng-if="!VBD.attached")
span.label.label-default(editable-checkbox="VBD.attached", e-title="Connected?", e-form="vmDisks", e-required="") Disconnected
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="vmDisks.$visible", ng-click="deleteVDI(VDI.UUID)")
i.fa.fa-trash-o.fa-lg(tooltip="Remove this disk")
tr(ng-repeat="VDI in newVDIs | resolve | orderBy:natural('name_label') track by VDI.UUID")
.btn-form(ng-show="vmDisks.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="vmDisks.$waiting", ng-click="vmDisks.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="submit", ng-disabled="vmDisks.$waiting", ng-click="saveChanges()")
i.fa.fa-save
| Save
button.btn.btn-success.pull-right(type="button", ng-disabled="vmDisks.$waiting", ng-click="addVDI()")
i.fa.fa-plus
| Add Disk
// TODO: Interface panel
.grid
.grid-cell.flat-panel
form(editable-form="", name="vmInterfaces", onaftersave="saveTable()", oncancel="cancel()")
p.flat-panel-title
i.xo-icon-network(style="color: #e25440;")
| Interface
span.quick-edit(tooltip="Edit interfaces", ng-click="vmInterfaces.$show()")
i.fa.fa-edit.fa-fw
table.table.table-hover
th Device
th MAC
th MTU
th Network
th Link status
tr(ng-repeat="VIF in VM.VIFs | resolve | orderBy:natural('name_label') track by VIF.UUID")
td VIF \#{{VIF.device}}
td
| {{VIF.MAC}}
td
span(editable-text="VIF.MTU", e-name="mtu", e-form="vmInterfaces", e-required="")
| {{VIF.MTU}}
td
| {{(VIF.$network | resolve).name_label}}
td(ng-if="VIF.attached")
span.label.label-success(editable-checkbox="VIF.attached") Connected
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="vmInterfaces.$visible", ng-click="disconnectVIF(VIF.UUID)")
i.fa.fa-ban.fa-lg(tooltip="Disconnect this interface")
td(ng-if="!VIF.attached")
span.label.label-default(editable-checkbox="VIF.attached") Disconnected
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="vmInterfaces.$visible", ng-click="deleteVIF(VIF.UUID)")
i.fa.fa-trash-o.fa-lg(tooltip="Remove this interface")
.btn-form(ng-show="vmInterfaces.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="vmInterfaces.$waiting", ng-click="vmInterfaces.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="button", ng-disabled="vmInterfaces.$waiting", ng-click="saveChanges()")
i.fa.fa-save
| Save
button.btn.btn-success.pull-right(type="button", ng-disabled="vmInterfaces.$waiting", ng-click="addNetwork()")
i.fa.fa-plus
| Add Interface
// TODO: User/Group panel : DISPLAY ONLY THIS PANEL IF YOU ARE ADMIN
//- .grid
//- .grid-cell.flat-panel
//- form(editable-form="", name="vmUsers", onaftersave="saveTable()", oncancel="cancel()")
//- p.flat-panel-title
//- i.fa.fa-user(style="color: #e25440;")
//- | Users
//- span.quick-edit(tooltip="Edit interfaces", ng-click="vmUsers.$show()")
//- i.fa.fa-edit.fa-fw
//- table.table.table-hover
//- th User name
//- th Permission
//- .btn-form(ng-show="vmUsers.$visible")
//- p.center
//- button.btn.btn-default(type="button", ng-disabled="vmUsers.$waiting", ng-click="vmUsers.$cancel()")
//- i.fa.fa-times
//- | Cancel
//- button.btn.btn-primary(type="button", ng-disabled="vmUsers.$waiting", ng-click="saveChanges()")
//- i.fa.fa-save
//- | Save
//- button.btn.btn-success.pull-right(type="button", ng-disabled="vmUsers.$waiting", ng-click="addNetwork()")
//- i.fa.fa-plus
//- | Add User
//- .grid-cell.flat-panel
//- form(editable-form="", name="vmGroups", onaftersave="saveTable()", oncancel="cancel()")
//- p.flat-panel-title
//- i.fa.fa-users(style="color: #e25440;")
//- | Groups
//- span.quick-edit(tooltip="Edit interfaces", ng-click="vmGroups.$show()")
//- i.fa.fa-edit.fa-fw
//- table.table.table-hover
//- th Group name
//- th Permission
//- .btn-form(ng-show="vmGroups.$visible")
//- p.center
//- button.btn.btn-default(type="button", ng-disabled="vmGroups.$waiting", ng-click="vmGroups.$cancel()")
//- i.fa.fa-times
//- | Cancel
//- button.btn.btn-primary(type="button", ng-disabled="vmGroups.$waiting", ng-click="saveChanges()")
//- i.fa.fa-save
//- | Save
//- button.btn.btn-success.pull-right(type="button", ng-disabled="vmGroups.$waiting", ng-click="addNetwork()")
//- i.fa.fa-plus
//- | Add Group
// TODO: Snapshot and Logs panels
.grid
// Snap panel
.grid-cell.flat-panel
form(editable-form="", name="vmSnap", oncancel="cancel()")
p.flat-panel-title
i.xo-icon-snapshot(style="color: #e25440;")
| Snapshots
span.quick-edit(tooltip="Edit snapshots", ng-click="vmSnap.$show()")
i.fa.fa-edit.fa-fw
p.center(ng-if="!VM.snapshots.length") No snapshots
table.table.table-hover(ng-if="VM.snapshots.length")
th Date
th Name
tr(ng-repeat="snapshot in VM.snapshots | resolve | orderBy:'-snapshot_time' track by snapshot.UUID")
td {{snapshot.snapshot_time*1e3 | date:"medium"}}
td
span(editable-text="snapshot.name_label", e-name="name_label", e-form="vmSnap", onbeforesave="saveSnapshot(snapshot.UUID, $data)")
| {{snapshot.name_label}}
span.pull-right
button.btn.btn-sm.btn-danger(type="button", ng-show="vmSnap.$visible", ng-click="deleteSnapshot(snapshot.UUID)")
i.fa.fa-trash-o.fa-lg(tooltip="Remove this snapshot")
button.btn.btn-sm.btn-danger(type="button", ng-show="vmSnap.$visible", ng-click="revertSnapshot(snapshot.UUID)")
i.fa.fa-undo.fa-lg(tooltip="Revert VM to this snapshot")
.btn-form(ng-show="vmSnap.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="vmSnap.$waiting", ng-click="vmSnap.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="submit", ng-disabled="vmSnap.$waiting", ng-click="saveChanges()")
i.fa.fa-save
| Save
// Logs panel
.grid-cell.flat-panel
form(editable-form="", name="vmLogs", onaftersave="saveTable()", oncancel="cancel()")
p.flat-panel-title
i.fa.fa-comments(style="color: #e25440;")
| Logs
span.quick-edit(tooltip="Edit logs", ng-click="vmLogs.$show()")
i.fa.fa-edit.fa-fw
p.center(ng-if="!VM.messages.length") No recent logs
table.table.table-hover(ng-if="VM.messages.length")
th Date
th Name
tr(ng-repeat="message in VM.messages | resolve | orderBy:'-time' track by message.UUID")
td {{message.time*1e3 | date:"medium"}}
td
| {{message.name}}
button.btn.btn-sm.btn-danger.pull-right(type="button", ng-show="vmLogs.$visible", ng-click="deleteLog(message.UUID)")
i.fa.fa-trash-o.fa-lg(tooltip="Remove this log entry")
.btn-form(ng-show="vmLogs.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="vmLogs.$waiting", ng-click="vmLogs.$cancel()")
i.fa.fa-times
| Cancel

View File

@@ -1,83 +0,0 @@
'use strict'
angular.module('xoWebApp', [
'ngCookies'
'ui.bootstrap'
'ui.indeterminate'
'ui.router'
'ui.select2'
'naturalSort'
'toaster'
'xeditable'
])
.config ($stateProvider, $urlRouterProvider, $tooltipProvider) ->
# Redirects unmatched URLs to `/`.
$urlRouterProvider.otherwise '/'
# Sets up the different states for our module.
$stateProvider
.state 'home',
url: '/'
controller: 'MainCtrl'
templateUrl: 'views/main.html'
.state 'list',
url: '/list'
controller: 'ListCtrl'
templateUrl: 'views/list.html'
.state 'hosts_view',
url: '/hosts/:id'
controller: 'HostCtrl'
templateUrl: 'views/host.html'
.state 'SRs_view',
url: '/srs/:id'
controller: 'SrCtrl'
templateUrl: 'views/sr.html'
.state 'SRs_new',
url: '/srs/new/:container'
controller: 'NewSrCtrl'
templateUrl: 'views/new_sr.html'
.state 'pools_view',
url: '/pools/:id'
controller: 'PoolCtrl'
templateUrl: 'views/pool.html'
.state 'VMs_new',
url: '/vms/new/:container'
controller: 'NewVmCtrl'
templateUrl: 'views/new_vm.html'
.state 'VMs_view',
url: '/vms/:id'
controller: 'VmCtrl'
templateUrl: 'views/vm.html'
.state 'consoles_view',
url: '/consoles/:id'
controller: 'ConsoleCtrl'
templateUrl: 'views/console.html'
.state 'about',
url: '/about'
templateUrl: 'views/about.html'
.state 'settings',
url: '/settings'
controller: 'SettingsCtrl'
templateUrl: 'views/settings.html'
# Changes the default settings for the tooltips.
$tooltipProvider.options
appendToBody: true
placement: 'bottom'
.run (editableOptions, editableThemes) ->
editableThemes.bs3.inputClass = 'input-sm'
editableThemes.bs3.buttonsClass = 'btn-sm'
editableOptions.theme = 'bs3'

View File

@@ -1,60 +0,0 @@
'use strict'
angular.module('xoWebApp')
.controller 'ConsoleCtrl', ($scope, $stateParams, xoApi, xo) ->
{id} = $stateParams
{get} = xo
push = Array::push.apply.bind Array::push
merge = do ->
(args...) ->
result = []
for arg in args
push result, arg if arg?
result
$scope.$watch(
-> xo.revision
->
unless xoApi.user
$scope.consoleUrl = ''
$scope.VDIs = []
return
VM = $scope.VM = xo.get id
return unless VM? and VM.power_state is 'Running'
pool = get VM.poolRef
return unless pool
$scope.consoleUrl = do ->
for console in VM.consoles
if console.protocol is 'rfb'
return "#{console.location}&session_id=#{pool.$sessionId}"
''
host = get VM.$container # host because the VM is running.
return unless host
# FIXME: We should filter on connected SRs (PBDs)!
SRs = get (merge host.SRs, pool.SRs)
$scope.VDIs = do ->
VDIs = []
for SR in SRs
push VDIs, SR.VDIs if SR.content_type is 'iso'
get VDIs
cdDrive = do ->
return VBD for VBD in (get VM.$VBDs) when VBD.is_cd_drive
null
$scope.mountedIso =
if cdDrive and cdDrive.VDI and (VDI = get cdDrive.VDI)
VDI.UUID
else
''
)
$scope.eject = ->
xo.vm.ejectCd id
$scope.insert = (disc_id) ->
xo.vm.insertCd id, disc_id, true

View File

@@ -1,17 +0,0 @@
'use strict'
angular.module('xoWebApp')
.controller 'DeleteVMsCtrl', ($scope, $modalInstance, xo, VMsIds) ->
$scope.$watch(
-> xo.revision
->
$scope.VMs = xo.get VMsIds
)
# Do disks have to be deleted for a given VM.
disks = $scope.disks = {}
do ->
disks[id] = true for id in VMsIds
$scope.delete = ->
$modalInstance.close ([id, disks[id]] for id in VMsIds)

View File

@@ -1,9 +0,0 @@
'use strict'
angular.module('xoWebApp')
.controller 'GenericModalCtrl', ($scope, $modalInstance, options) ->
$scope.title = options.title
$scope.message = options.message
$scope.yesButtonLabel = options.yesButtonLabel ? 'Ok'
$scope.noButtonLabel = options.noButtonLabel

View File

@@ -1,5 +0,0 @@
'use strict'
angular.module('xoWebApp')
.controller 'ListCtrl', ($scope, xo) ->
$scope.byTypes = xo.byTypes

View File

@@ -1,22 +0,0 @@
'use strict'
angular.module('xoWebApp')
.controller 'NavBarCtrl', ($scope, $location, xoApi) ->
# TODO: It would make sense to inject xoApi in the scope.
$scope.$watch(
-> xoApi.status
(status) ->
$scope.status = status
)
$scope.$watch(
-> xoApi.user
(user) ->
$scope.user = user
)
$scope.logIn = xoApi.logIn
$scope.logOut = xoApi.logOut
# When a searched is entered, we must switch to the list view if
# necessary.
$scope.ensureListView = ->
$location.path '/list'

View File

@@ -1,10 +0,0 @@
'use strict'
angular.module('xoWebApp')
.controller 'NewSrCtrl', ($scope, $stateParams, xo) ->
$scope.$watch(
-> xo.revision
->
$scope.container = xo.get $stateParams.container
)

View File

@@ -1,4 +1,16 @@
angular.module('xoWebApp')
require 'angular'
require 'angular-cookies'
require 'angular-notify-toaster'
#=====================================================================
# TODO: split into multiple modules.
module.exports = angular.module 'xoWebApp.services', [
'ngCookies'
'toaster'
]
# Inspired by https://github.com/MathieuTurcotte/node-backoff.
#
@@ -89,23 +101,6 @@ angular.module('xoWebApp')
# Returns the service.
BackOff
.service 'modal', ($modal) ->
{
confirm: ({title, message}) ->
modal = $modal.open {
controller: 'GenericModalCtrl'
templateUrl: 'views/generic_modal.html'
resolve: {
options: -> {
title
message
noButtonLabel: 'Cancel'
}
}
}
modal.result
}
.service 'notify', (toaster) ->
notifier = (level) ->
(options) ->
@@ -155,6 +150,9 @@ angular.module('xoWebApp')
# Promises linked to the requests.
deferreds = {}
# Listeners for notifications (grouped by method).
listeners = {}
# When the socket is closed, request are enqueued.
queue = []
@@ -239,6 +237,16 @@ angular.module('xoWebApp')
# deferred (if any).
socket.addEventListener 'message', (event) ->
response = JSON.parse event.data
unless 'id' of response
# It is not a response but a notification.
if 'method' of response and 'params' of response
xoApi.emit response.method, response.params
$rootScope.$digest()
else
console.error 'invalid message received', response
return
id = response.id
deferred = deferreds[id]
@@ -297,12 +305,31 @@ angular.module('xoWebApp')
send 'session.signOut' if socket
xoApi.user = null
$cookieStore.remove 'token'
# EventEmitter methods.
emit: (event, params...) ->
listener.apply xoApi, params for listener in listeners[event] ? []
on: (event, listener) ->
(listeners[event] ?= []).push listener
once: (event, listener) ->
onceListener = (params...) ->
xoApi.removeListener event, onceListener
listener.apply this, params
xoApi.on event, onceListener
removeAllListeners: (event) ->
delete listeners[event]
removeListener: (event, listener) ->
return unless event of listeners
for candidate, i in listeners[event]
if candidate is listener
listeners[event].splice i, 1
return
}
# This service provides access to XO objects.
#
# Deprecated: use the service `xo` instead.
.service 'xoObjects', ($timeout, xoApi) ->
.service 'xoObjects', ($timeout, xoApi, $rootScope) ->
byRefs = Object.create null
byUUIDs = Object.create null
{
@@ -320,23 +347,40 @@ angular.module('xoWebApp')
byUUIDs[key] ? byRefs[key]
}
do helper = ->
xoApi.call('xo.getAllObjects').then (objects) ->
# Empty collections.
delete byTypes[key] for key of byTypes
byRefs = Object.create null
byUUIDs = Object.create null
xoApi.call('xo.getAllObjects').then (objects) ->
# Empty collections.
delete byTypes[key] for key of byTypes
byRefs = Object.create null
byUUIDs = Object.create null
all = objects
for object in all
byUUIDs[object.UUID] = object if object.UUID?
byRefs[object.ref] = object if object.ref?
(byTypes[object.type] ?= []).push object
all = objects
for object in all
byUUIDs[object.UUID] = object if object.UUID?
byRefs[object.ref] = object if object.ref?
(byTypes[object.type] ?= []).push object
++xoObjects.revision
# Fetches objects again after 5 seconds.
$timeout helper, 5e3, false
++xoObjects.revision
xoApi.on 'all', (event) ->
switch event.type
when 'exit'
for object in event.items
delete byUUIDs[object.UUID] if 'UUID' of object
delete byRefs[object.ref] if 'ref' of object
list = byTypes[object.type] ? []
for candidate, i in list
if candidate.ref is object.ref
list.splice i, 1
break
else
for object in event.items
byUUIDs[object.UUID] = object if 'UUID' of object
byRefs[object.ref] = object if 'ref' of object
list = byTypes[object.type] ?= []
index = do ->
return i for candidate, i in list when candidate.ref is object.ref
list.length
list[index] = object
++xoObjects.revision
xoObjects
@@ -354,6 +398,7 @@ angular.module('xoWebApp')
{argsMapper, notification} = options ? {}
# FIXME: default mapper should be identity.
argsMapper ?= (id) -> {id}
(args...) ->
@@ -387,12 +432,11 @@ angular.module('xoWebApp')
get: xoObjects.get
pool: {
pool:
disconnect: action 'Disconnect pool'
new_sr: action 'New SR' #temp fix before creating SR
}
host: {
host:
attach: action 'Atach host'#, 'host.attach'
detach: action 'Detach host', 'host.detach'
restart: action 'Restart host', 'host.restart'
@@ -401,22 +445,39 @@ angular.module('xoWebApp')
stop: action 'Stop host', 'host.stop'
new_sr: action 'New SR' #temp fix before creating SR
# TODO: attach/set
}
message: {
delete: action, 'Delete message'
}
log:
delete: action 'Delete Log', 'message.delete'
pbd: {
message:
delete: action 'Delete message'
pbd:
delete: action 'Delete PBD'
disconnect: action 'Disconnect PBD'
}
task: {
delete: action, 'Delete task'
}
server:
add: action 'Add server', 'server.add', argsMapper: (params) -> params
remove: action 'Remove server', 'server.remove', argsMapper: (id) -> {id}
getAll: action 'Getting server', 'server.getAll'
set: action 'Save server', 'server.set', argsMapper: (params) -> params
vm: {
task:
delete: action 'Delete task'
user:
create: action 'Create user', 'user.create', argsMapper: (params) -> params
delete: action 'Delete user', 'user.delete', argsMapper: (id) -> {id}
getAll: action 'Getting user', 'user.getAll'
set: action 'Save user', 'user.set', argsMapper: (params) -> params
vm:
convert: action 'Convert VM', 'vm.convert', {
argsMapper: (id) -> {id}
}
clone: action 'Copy VM', 'vm.clone', {
argsMapper: (id, name, full_copy) -> {id, name, full_copy} #todo : sr ref to choose target SR
}
createSnapshot: action 'Create VM snapshot', 'vm.snapshot', {
argsMapper: (id, name) -> {id, name}
}
@@ -439,16 +500,9 @@ angular.module('xoWebApp')
}
revert: action 'Revert snapshot', 'vm.revert'
# TODO: create/set/pause/suspend
}
vdi: {
vdi:
delete: action 'Delete VDI', 'vdi.delete'
}
log: {
delete: action 'Delete Log', 'message.delete'
}
}
# Adds the revision property.

View File

@@ -87,6 +87,9 @@ a, [ng-click], [xo-click], [xo-sref]
.xo-icon-error
@extend .fa, .fa-exclamation-circle, .text-danger
.xo-icon-success
@extend .fa, .fa-check-circle, .text-success
////
// Objects
////

View File

@@ -11,45 +11,47 @@ input.form-control.inverse {background-color: #666; color: #f8f8f8; border: 0px;
.btn-search:hover {background-color: #1d4457; color: #f8f8f8;}
html {
background-color: #edece4;
/* Possibility to get a wallpaper for the background: see later in admin maybe, for the lulz */
/*background: url(bg.jpg) no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;*/
background-color: #edece4;
/* Possibility to get a wallpaper for the background: see later in admin maybe, for the lulz */
/*background: url(bg.jpg) no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;*/
}
body {
background-color: transparent;
background-color: transparent;
}
.navbar-under { background-color: #f8f8f8;}
td.vcenter {
vertical-align: middle;
vertical-align: middle;
}
input[type="checkbox"]{
padding-top: 0px;
line-height: normal;
padding-top: 0px;
line-height: normal;
}
input.inverse {margin: 0;}
/* progress bar */
.progress-small {
height: 0.6em;
width: 4em;
display: inline-block;
float: center;
margin: 3px;
height: 0.6em;
width: 4em;
display: inline-block;
float: center;
margin: 3px;
}
// FIXME: What is it?
//.i-progress { float: center; margin-right: 0em;}
.pool-block { margin-left: 1em; margin-right: 1em; margin-top: 0.5em; margin-bottom: 0;}
// Pool block
// A block display a whole pool
.pool-block { margin-left: 0.8em; margin-right: 0.8em; margin-top: 0.3em; margin-bottom: 0;}
.pool-cell { margin-bottom: 0.5em; margin-left: 2em; margin-right: 1em; min-height: 6em; max-width: 190px; background-color: white; }
/* row for all hosts/vms in a pool */
.hosts-vms-cells { min-height: 6em; margin-right: -0.5em; }
@@ -57,19 +59,19 @@ input.inverse {margin: 0;}
.host-cell { max-width: 156px; background-color: white; margin-bottom: 0.5em;}
/* pool size mini : 195 px ; host : 161 px; */
.vm-cell {
margin-left: 0.5em;
margin-bottom: 0.5em;
background-color: white;
/* allow empty space */
/*align-self: flex-start;*/
margin-left: 0.5em;
margin-bottom: 0.5em;
background-color: white;
/* allow empty space */
/*align-self: flex-start;*/
}
.grey {color: #666; font-size: 0.9em;}
/* drodown head link for pools/hosts */
.dropdown-pool { border-bottom: 1px solid #edece4; text-align: center;}
.dropdown-pool a {
text-decoration: none;
color: #333;
text-decoration: none;
color: #333;
}
.pool-name {font-size: 1.8em;}
.host-name {font-size: 20px;}
@@ -99,22 +101,22 @@ td.vm-memory-stat {text-align: right;}
/* the main bar */
.navbar-inverse
{
background-color: #242628;
border-color: #2e3133;
//font-variant:small-caps;
background-color: #242628;
border-color: #2e3133;
//font-variant:small-caps;
}
.fa {font-variant: normal;}
/* the big subbar */
.sub-bar
{
height:50px;
top:50px;
position: fixed;
background:#242628;
border-bottom:1px solid #2e3133;
width:100%;
margin:0px auto;
z-index: 1020;
height:50px;
top:50px;
position: fixed;
background:#242628;
border-bottom:1px solid #2e3133;
width:100%;
margin:0px auto;
z-index: 1020;
}
@@ -166,7 +168,7 @@ a.btn.navbar-btn.btn-default.dropdown-toggle.inversed {background-color:#444; bo
/* tooltip hack to avoid be hidden by other elements */
/*
.tooltip {
position: fixed;
position: fixed;
}
*/
@@ -179,12 +181,13 @@ a.btn.navbar-btn.btn-default.dropdown-toggle.inversed {background-color:#444; bo
/* flex */
.grid {
display: flex;
display: flex;
}
.grid-cell {
flex: 1;
border-radius: 3px 3px 3px 3px;
flex: 1;
-webkit-flex: 1;
border-radius: 3px 3px 3px 3px;
}
/* stats name in a grid cell */
.stat-name {margin-top: 1em; text-align: center; font-variant: small-caps;}
@@ -205,21 +208,65 @@ a.btn.navbar-btn.btn-default.dropdown-toggle.inversed {background-color:#444; bo
.grid-button {
margin-left: 1em;
margin-right: 1em;
margin-bottom: 0.5em;
padding: 0.5em;
background-color: #efefef;
margin-left: 1em;
margin-right: 1em;
margin-bottom: 0.5em;
padding: 0.5em;
background-color: #efefef;
}
.grid-button:hover {
background-color: #e25440;
background-color: #e25440;
}
/* With gutters */
.grid--gutters {
margin-left: -0.5em;
margin-left: -0.5em;
}
.grid--gutters > .grid-cell {
padding-left: 0.5em;
padding-left: 0.5em;
}
.page-header {
border-bottom: 1px solid #FFFFFF;
padding-bottom: 9px;
text-align: center;
}
.form-control-login {
border: 1px solid #D6D6D6;
border-radius: 0;
box-shadow: none;
height: 50px;
padding: 6px 15px;
}
.row-login {
background: #fff;
//padding: 20px 50px 20px 50px;
padding: 1em 5em 1em 5em;
}
legend.login {
border: medium none;
color: #111;
display: block;
font-size: 20px;
line-height: inherit;
margin-bottom: 15px;
padding: 0;
text-align: center;
width: 100%;
}
.btn-login {
padding: 10px;
border-radius: 0;
border: none;
font-size: 21px;
}
.status {
margin-top: 2em;
}

View File

@@ -1,32 +0,0 @@
<!-- TODO: lots of stuff. -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="page-title">About Xen Orchestra</p>
</div>
</div>
<div class="grid">
<!-- Vates -->
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-lightbulb-o" style="color: #e25440;"></i> Vates</p>
<p class="text-center">We are the team behind Xen Orchestra, we are Vates! We create Open Source products and we offer commercial support for Xen and Xen Orchestra. Want to know more about us? Go to our website!
</p>
<p class="text-center"><img src="images/arrow.png"></p><br/>
<p class="text-center"><a class="btn btn-success" href="https://vates.fr"><i class="fa fa-hand-o-right"></i> Our website</a></p>
</div>
<!-- Open Source -->
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-thumbs-up" style="color: #e25440;"></i> Open Source</p>
<p class="text-center">This project is Open Source (AGPL), everyone is welcome aboard! You want a specific feature in XO? Report a bug? Go to our project website, read the FAQ and get involved in the project!</p>
<p class="text-center"><img src="images/opensource.png"></p><br/>
<p class="text-center"><a class="btn btn-info" href="https://xen-orchestra.com"><i class="fa fa-flask"></i> Project website</a></p>
</div>
<!-- Pro support -->
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-truck" style="color: #e25440;"></i> Pro Support Delivered</p>
<p class="text-center">Our XO Appliance can be delivered with professional support: stay relaxed, we got your back! You can also have assitance for deploying or upgrade your virtualized infrastructure through our deep understanding of Xen.</p>
<p class="text-center"><img src="images/support.png"></p>
<p class="text-center"><a class="btn btn-primary" href="https://xen-orchestra.com/services/"><i class="fa fa-envelope"></i> Get services</a></p>
</div>
</div>

View File

@@ -1,41 +0,0 @@
<form ng-submit="delete()">
<div class="modal-header">
<h3>VMs deletion</h3>
</div>
<div class="modal-body">
<p>
You are going to delete the following VMs, this is a <strong>dangerous
action</strong>!
</p>
<table class="table">
<tr>
<th class="col-sm-3">Name</th>
<th class="col-sm-6">Description</th>
<th class="col-sm-3">Delete disks?</th>
</tr>
<tbody>
<tr ng-repeat="VM in VMs | orderBy:natural('name_label') track by VM.UUID">
<td>{{VM.name_label}}</td>
<td>{{VM.name_description}}</td>
<td><input type="checkbox" ng-model="disks[VM.UUID]"></td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary"
>
Delete
</button>
<button
type="button"
class="btn btn-warning"
ng-click="$dismiss()"
>
Cancel
</button>
</div>
</form>

View File

@@ -1,23 +0,0 @@
<div class="modal-header">
<h3><i class="fa fa-exclamation-triangle text-danger"></i> {{title}}</h3>
</div>
<div class="modal-body">
{{message}}
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
ng-click="$close()"
>
{{yesButtonLabel}}
</button>
<button
ng-if="noButtonLabel"
type="button"
class="btn btn-warning"
ng-click="$dismiss()"
>
{{noButtonLabel}}
</button>
</div>

View File

@@ -1,400 +0,0 @@
<!-- TODO: lots of stuff. -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="page-title">
<i class="xo-icon-host xo-color-{{host.power_state | lowercase}}"></i>
{{host.name_label}}
<small ng-if="pool.name_label">
(<a ui-sref="pools_view({id: pool.UUID})">{{pool.name_label}}</a>)
</small>
</p>
</div>
</div>
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-cogs" style="color: #e25440;"></i> General
<span class="quick-edit" tooltip="Edit General settings" ng-click="hostSettings.$show()">
<i class="fa fa-edit fa-fw"></i>
</span>
</p>
<div class="general-properties">
<form editable-form name="hostSettings" onbeforesave="saveHost($data)">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>
<span editable-text="host.name_label" e-name="name_label" e-form="hostSettings">
{{host.name_label}}
</span>
</dd>
<dt>Description</dt>
<dd>
<span editable-text="host.name_description" e-name="name_description" e-form="hostSettings">
{{host.name_description}}
</span>
</dd>
<dt>Enabled</dt>
<dd>
<span editable-checkbox="host.enabled" e-name="enabled" e-form="hostSettings">
{{host.enabled}}
</span>
</dd>
<dt>Tags</dt>
<dd ng-if="host.tags.length">
<span ng-repeat="tag in host.tags">
<span class="label label-primary">{{tag}}</span>
</span>
</dd>
<dd ng-if="!host.tags.length">
<em>No tags.</em>
</dd>
<dt>CPUs</dt>
<dd>{{host.CPUs["cpu_count"]}}x {{host.CPUs["modelname"]}}</dd>
<dt>Hostname</dt>
<dd>
{{host.hostname}}
</dd>
<dt>UUID</dt>
<dd>{{host.UUID}}</dd>
<dt>iQN</dt>
<dd>{{host.iSCSI_name}}</dd>
</dl>
<div class="btn-form" ng-show="hostSettings.$visible">
<p class="center">
<button type="button" ng-disabled="hostSettings.$waiting" ng-click="hostSettings.$cancel()" class="btn btn-default"><i class="fa fa-times"></i> Cancel</button>
<button type="submit" ng-disabled="hostSettings.$waiting" class="btn btn-primary">
<i class="fa fa-save"></i> Save
</button>
</p>
</div>
</form>
</div>
</div>
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-bar-chart-o" style="color: #e25440;"></i> Stats</p>
<div class="grid">
<div class="grid-cell">
<p class="stat-name">vCPUs/CPU usage:</p>
<p class="center big">{{host.$vCPUs}}/{{host.CPUs["cpu_count"]}}</p>
</div>
<div class="grid-cell">
<p class="stat-name">RAM used:</p>
<p class="center big">{{[host.memory.usage, host.memory.size] | %}}</p>
</div>
<div class="grid-cell">
<p class="stat-name">Running VMs:</p>
<p class="center big">{{host.VMs.length}}</p>
</div>
</div>
</div>
</div>
<!-- Action panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-flash" style="color: #e25440;"></i> Actions</p>
<div class="grid-cell text-center">
<div class="grid">
<div class="grid-cell btn-group">
<button tooltip="Add SR" type="button" style="width: 90%" class="btn" xo-click="new_sr()">
<i class="xo-icon-sr fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group">
<button tooltip="Add VM" type="button" style="width: 90%" class="btn" xo-sref="VMs_new({container: host.UUID})">
<i class="xo-icon-vm fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group">
<button tooltip="Reboot host" type="button" style="width: 90%" class="btn" xo-click="rebootHost(host.UUID)">
<i class="fa fa-refresh fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group">
<button tooltip="Shutdown host" type="button" style="width: 90%" class="btn" xo-click="shutdownHost(host.UUID)">
<i class="fa fa-power-off fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group">
<button tooltip="Restart toolstack" type="button" style="width: 90%" class="btn" xo-click="restartToolStack(host.UUID)">
<i class="fa fa-retweet fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group" ng-if="pool.name_label">
<button tooltip="Remove from pool" style="width: 90%" type="button" class="btn" xo-click="pool_removeHost(host.UUID)">
<i class="fa fa-cloud-upload fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group" ng-if="!pool.name_label">
<button tooltip="Add to pool" style="width: 90%" type="button" class="btn" xo-click="pool_addHost(host.UUID)">
<i class="fa fa-cloud-download fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group" style="margin-bottom: 0.5em">
<button
tooltip="Host console"
type="button"
style="width: 90%"
class="btn"
ng-repeat="controller in [host.controller] | resolve track by controller.UUID"
xo-sref="consoles_view({id: controller.UUID})">
<i class="xo-icon-console fa-2x fa-fw"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- TODO: Memory panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title">
<i class="xo-icon-memory" style="color: #e25440;"></i> Memory
</p>
<div class="progress">
<div
ng-repeat="controller in [host.controller] | resolve track by controller.UUID"
class="progress-bar-host"
role="progressbar"
aria-valuemin="0"
aria-valuenow="{{controller.memory.size}}"
aria-valuemax="{{host.memory.size}}"
style="width: {{[controller.memory.size, host.memory.size] | %}}"
tooltip="{{host.name_label}}: {{[controller.memory.size, host.memory.size] | %}}"
>
<small>{{host.name_label}}</small>
</div>
<div
ng-repeat="VM in host.VMs | resolve | orderBy:natural('name_label') track by VM.UUID"
class="progress-bar progress-bar-vm"
role="progressbar"
aria-valuemin="0"
aria-valuenow="{{VM.memory.size}}"
aria-valuemax="{{host.memory.size}}"
style="width: {{[VM.memory.size, host.memory.size] | %}}"
xo-sref="VMs_view({id: VM.UUID})"
tooltip="{{VM.name_label}}: {{[VM.memory.size, host.memory.size] | %}}"
>
<small>{{VM.name_label}}</small>
</div>
</div>
<ul class="list-inline text-center">
<li>Total: {{host.memory.size | bytesToSize}}</li>
<li>Currently used: {{host.memory.usage | bytesToSize}}</li>
<li>Available: {{host.memory.size-host.memory.usage | bytesToSize}}</li>
</ul>
</div>
</div>
<!-- SR panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<form editable-form name="hostSRs" onaftersave="saveTable()" oncancel="cancel()">
<p class="flat-panel-title"><i class="xo-icon-sr" style="color: #e25440;"></i> Storage
<span class="quick-edit" tooltip="Edit storage" ng-click="hostSRs.$show()">
<i class="fa fa-edit fa-fw"> </i>
</span>
</p>
<table class="table table-hover">
<tr>
<th>Name</th>
<th>Format</th>
<th>Size</th>
<th>Physical/Allocated usage</th>
<th>Type</th>
<th>Status</th>
</tr>
<!-- TODO: display PBD status for each SR of this host (connected or not) -->
<!-- Shared SR -->
<tr
xo-sref="SRs_view({id: SR.UUID})"
ng-repeat="SR in pool.SRs | resolve | orderBy:natural('name_label') track by SR.UUID"
ng-init="PBD = SRsToPBDs[SR.ref]"
>
<td>
{{SR.name_label}}
</td>
<td>{{SR.SR_type}}</td>
<td>{{SR.size | bytesToSize}}</td>
<td>
<div class="progress-condensed">
<div
class="progress-bar"
role="progressbar"
aria-valuemin="0"
aria-valuenow="{{SR.usage}}"
aria-valuemax="{{SR.size}}"
style="width: {{[SR.physical_usage, SR.size] | %}}"
tooltip="Physical usage: {{[SR.physical_usage, SR.size] | %}}"
></div>
<div
class="progress-bar progress-bar-info"
role="progressbar"
aria-valuemin="0"
aria-valuenow="{{SR.physical_usage}}"
aria-valuemax="{{SR.size}}"
style="width: {{[(SR.usage-SR.physical_usage), SR.size] | %}}"
tooltip="Allocated: {{[(SR.usage), SR.size] | %}}"
></div>
</div>
</td>
<td><span class="label label-primary">Shared</span></td>
<td ng-if="PBD.attached">
<span class="label label-success">Connected</span>
<button type="button" ng-show="hostSRs.$visible" ng-click="disconnectSR(SR.ref)" class="btn btn-sm btn-danger pull-right" tooltip="Disconnect this SR"><i class="fa fa-unlink fa-lg"></i></button>
</td>
<td ng-if="!PBD.attached">
<span class="label label-default">Disconnected</span>
<button type="button" ng-show="hostSRs.$visible" ng-click="removeSR(SR.ref)" class="btn btn-sm btn-danger pull-right" tooltip="Remove this SR"><i class="fa fa-trash-o fa-lg"></i></button>
</td>
</tr>
<!-- Local SR -->
<!-- TODO: migrate to SRs and not PBDs when implemented in xo-server spec -->
<tr
xo-sref="SRs_view({id: SR.UUID})"
ng-repeat="SR in host.SRs | resolve | orderBy:natural('name_label') track by SR.UUID"
ng-init="PBD = SRsToPBDs[SR.ref]"
>
<td>
{{SR.name_label}}
</td>
<td>{{SR.SR_type}}</td>
<td>{{SR.size | bytesToSize}}</td>
<td>
<div class="progress-condensed">
<div
class="progress-bar"
role="progressbar"
aria-valuemin="0"
aria-valuenow="{{SR.usage}}"
aria-valuemax="{{SR.size}}"
style="width: {{[SR.physical_usage, SR.size] | %}}"
tooltip="Physical usage: {{[SR.physical_usage, SR.size] | %}}"
></div>
<div
class="progress-bar progress-bar-info"
role="progressbar"
aria-valuemin="0"
aria-valuenow="{{SR.physical_usage}}"
aria-valuemax="{{SR.size}}"
style="width: {{[(SR.usage-SR.physical_usage), SR.size] | %}}"
tooltip="Allocated: {{[(SR.usage), SR.size] | %}}"
></div>
</div>
</td>
<td><span class="label label-info">Local</span></td>
<td ng-if="PBD.attached">
<span class="label label-success">Connected</span>
<button type="button" ng-show="hostSRs.$visible" ng-click="disconnectSR(SR.ref)" class="btn btn-sm btn-danger pull-right" tooltip="Disconnect this SR"><i class="fa fa-unlink fa-lg"></i></button>
</td>
<td ng-if="!PBD.attached">
<span class="label label-default">Disabled</span>
<button type="button" ng-show="hostSRs.$visible" ng-click="removeSR(SR.ref)" class="btn btn-sm btn-danger pull-right" tooltip="Remove this SR"><i class="fa fa-trash-o fa-lg"></i></button>
</td>
</tr>
</table>
<div class="btn-form" ng-show="hostSRs.$visible">
<p class="center">
<button type="button" ng-disabled="hostSRs.$waiting" ng-click="hostSRs.$cancel()" class="btn btn-default"><i class="fa fa-times"></i> Cancel</button>
<button type="button" ng-disabled="hostSRs.$waiting" ng-click="saveChanges()" class="btn btn-primary"><i class="fa fa-save"></i> Save</button>
</p>
</div>
</form>
</div>
</div>
<!-- Interfaces panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<form editable-form name="hostNetwork" onaftersave="saveTable()" oncancel="cancel()">
<p class="flat-panel-title"><i class="xo-icon-network" style="color: #e25440;"></i> Interfaces
<span class="quick-edit" tooltip="Edit network" ng-click="hostNetwork.$show()">
<i class="fa fa-edit fa-fw"> </i>
</span>
</p>
<table class="table table-hover">
<th class="col-md-1">Device</th>
<th class="col-md-1">Address</th>
<th class="col-md-2">MAC</th>
<th class="col-md-1">MTU</th>
<th class="col-md-1">Link status</th>
<tr ng-repeat="PIF in host.$PIFs | resolve | orderBy:natural('name_label') track by PIF.UUID">
<td>{{PIF.device}}<span ng-if="PIF.vlan > -1">.{{PIF.vlan}}</span> <span ng-if="PIF.management" class="label label-primary">XAPI</span></td>
<td>{{PIF.IP}} ({{PIF.mode}})</td>
<td>{{PIF.MAC}}</td>
<td>
<span editable-text="PIF.MTU" e-name="mtu" e-form="hostNetwork" e-required>
{{PIF.MTU}}
</span>
</td>
<td ng-if="PIF.attached">
<span class="label label-success">Connected</span>
<button type="button" ng-show="hostNetwork.$visible" ng-click="disconnectSR(Network.ref)" class="btn btn-sm btn-danger pull-right" tooltip="Disconnect this Network"><i class="fa fa-unlink fa-lg"></i></button>
</td>
<td ng-if="!PIF.attached">
<span class="label label-default">Disconnected</span>
<button type="button" ng-show="hostNetwork.$visible" ng-click="removeNetwork(Network.ref)" class="btn btn-sm btn-danger pull-right" tooltip="Remove this Network"><i class="fa fa-trash-o fa-lg"></i></button>
</td>
</tr>
</table>
<div class="btn-form" ng-show="hostNetwork.$visible">
<p class="center">
<button type="button" ng-disabled="hostNetwork.$waiting" ng-click="hostNetwork.$cancel()" class="btn btn-default"><i class="fa fa-times"></i> Cancel</button>
<button type="button" ng-disabled="hostNetwork.$waiting" ng-click="saveChanges()" class="btn btn-primary"><i class="fa fa-save"></i> Save</button>
</p>
</div>
</div>
</form>
</div>
<!-- CPU and Logs panels -->
<div class="grid">
<!-- Task panel -->
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-tasks" style="color: #e25440;"></i> Pending tasks</p>
<p class="center" ng-if="!host.tasks.length">No recent tasks</p>
<table class="table table-hover" ng-if="host.tasks.length">
<th class="col-md-4">Date</th>
<th class="col-md-8">Name</th>
<!-- TODO: working reverse order, from recent to oldest -->
<tr ng-repeat="task in host.tasks | resolve | orderBy:'created':true track by task.UUID">
<td>{{task.created}}</td>
<td>
{{task.name_label}}
<a class="quick-remove" xo-click="removeTask(task.UUID)">
<i class="fa fa-trash-o fa-fw"></i>
</a>
</td>
</tr>
</table>
</div>
<!-- Logs panel -->
<div class="grid-cell flat-panel">
<form editable-form name="hostLogs" onaftersave="saveTable()" oncancel="cancel()">
<p class="flat-panel-title"><i class="fa fa-comments" style="color: #e25440;"></i> Logs
<span class="quick-edit" tooltip="Edit logs" ng-click="hostLogs.$show()">
<i class="fa fa-edit fa-fw"> </i>
</span>
</p>
<p class="center" ng-if="!host.messages.length">No recent logs</p>
<table class="table table-hover" ng-if="host.messages.length">
<th>Date</th>
<th>Name</th>
<tr ng-repeat="message in host.messages | resolve | orderBy:'-time' track by message.UUID">
<td>{{message.time*1e3 | date:'medium'}}</td>
<td>
{{message.name}}
<button type="button" ng-show="hostLogs.$visible" ng-click="deleteLog(message.UUID)" class="btn btn-sm btn-danger pull-right"><i class="fa fa-trash-o fa-lg" tooltip="Remove this log entry"></i></button>
</td>
</tr>
</table>
<div class="btn-form" ng-show="hostLogs.$visible">
<p class="center">
<button type="button" ng-disabled="hostLogs.$waiting" ng-click="hostLogs.$cancel()" class="btn btn-default"><i class="fa fa-times"></i> Cancel</button>
</p>
</div>
</form>
</div>
</div>

View File

@@ -1,268 +0,0 @@
<!-- TODO: print a message when no entries. -->
<!-- If it's a (named) pool. -->
<div
ng-repeat="pool in byTypes.pool | filter:listFilter | orderBy:natural('name_label') track by pool.UUID"
ng-if="pool.name_label"
class="grid flat-object"
xo-sref="pools_view({id: pool.UUID})"
>
<!-- Icon. -->
<div class="grid-cell flat-cell flat-cell-type">
<i class="xo-icon-pool"></i>
</div>
<!-- Properties & tags. -->
<div class="grid-cell">
<!-- Properties. -->
<div class="grid">
<div class="grid-cell">
<div class="grid">
<div class="grid-cell flat-cell flat-cell-name">
{{pool.name_label}}
</div>
<div class="grid-cell flat-cell flat-cell-description">
<i>{{pool.name_description}}</i>
</div>
<div class="grid-cell flat-cell" ng-init="default_SR = (pool.default_SR | resolve)">
<div ng-if="default_SR">
Default SR: <a ui-sref="SRs_view({id: default_SR.UUID})">{{default_SR.name_label}}</a>
</div>
<div ng-if="!default_SR">
<em>No default SR.</em>
</div>
</div>
<div class="grid-cell flat-cell" ng-init="master = (pool.master | resolve)">
<div ng-if="master">
Master: <a ui-sref="hosts_view({id: master.UUID})">{{master.name_label}}</a>
</div>
<div ng-if="!master">
<em>Unknown master.</em>
</div>
</div>
<div class="grid-cell flat-cell">
<div ng-if="pool.HA_enabled">
HA enabled
</div>
<div ng-if="!pool.HA_enabled">
HA disabled
</div>
</div>
<div class="grid-cell flat-cell">
{{pool.$running_hosts.length}}/{{pool.hosts.length}} hosts
</div>
</div>
</div>
</div>
<!-- /Properties. -->
<!-- Tags. -->
<div class="grid">
<div class=" grid-cell">
<div class="grid-cell flat-cell-tag">
<i class="fa fa-tag"></i>&nbsp;&nbsp;
<span ng-repeat="tag in pool.tags">
<span class="label label-primary">{{tag}}</span>
</span>
</div>
</div>
</div>
<!-- /Tags. -->
</div>
<!-- /Properties & tags. -->
</div>
<!-- /Pool. -->
<!-- If it's a host. -->
<div
ng-repeat="host in byTypes.host | filter:listFilter | orderBy:natural('name_label') track by host.UUID"
class="grid flat-object"
xo-sref="hosts_view({id: host.UUID})"
>
<!-- Icon. -->
<div class="grid-cell flat-cell flat-cell-type">
<i class="xo-icon-host xo-color-{{host.power_state | lowercase}}"></i>
</div>
<!-- Properties & tags. -->
<div class="grid-cell">
<!-- Properties. -->
<div class="grid ">
<div class="grid-cell">
<div class="grid">
<div class="grid-cell flat-cell flat-cell-name">
{{host.name_label}}
</div>
<div class="grid-cell flat-cell flat-cell-description">
<i>{{host.name_description}}</i>
</div>
<div class="grid-cell flat-cell">
Address: {{host.address}}
</div>
<div class="grid-cell flat-cell">
{{host.$vCPUs}} vCPUs used on {{host.CPUs["cpu_count"]}} CPUs
</div>
<div class="grid-cell flat-cell">
{{host.memory.usage | bytesToSize}} used of {{host.memory.size | bytesToSize}} ({{[host.memory.usage, host.memory.size] | %}})
</div>
<div class="grid-cell flat-cell">
{{host.VMs.length}} VMs running
</div>
</div>
</div>
</div>
<!-- /Properties. -->
<!-- Tags. -->
<div class="grid">
<div class=" grid-cell">
<div class="grid-cell flat-cell-tag">
<i class="fa fa-tag"></i>&nbsp;&nbsp;
<span ng-repeat="tag in host.tags">
<span class="label label-primary">{{tag}}</span>
</span>
</div>
</div>
</div>
<!-- /Tags. -->
</div>
<!-- /Properties & tags. -->
</div>
<!-- /Host. -->
<!-- If it's a VM. -->
<div
ng-repeat="VM in byTypes.VM | filter:listFilter | orderBy:natural('name_label') track by VM.UUID"
class="grid flat-object"
xo-sref="VMs_view({id: VM.UUID})"
>
<!-- Icon. -->
<div class="grid-cell flat-cell flat-cell-type">
<i class="xo-icon-vm xo-color-{{VM.power_state | lowercase}}"></i>
</div>
<!-- Properties & tags. -->
<div class="grid-cell">
<!-- Properties. -->
<div class="grid ">
<div class="grid-cell">
<div class="grid">
<div class="grid-cell flat-cell flat-cell-name">
{{VM.name_label}}
</div>
<div class="grid-cell flat-cell flat-cell-description">
<i>{{VM.name_description}}</i>
</div>
<div class="grid-cell flat-cell">
Address: {{VM.addresses['0/ip']}}
</div>
<div class="grid-cell flat-cell">
{{VM.CPUs.number}} vCPUs
</div>
<div class="grid-cell flat-cell">
{{VM.memory.size | bytesToSize}} RAM
</div>
<div class="grid-cell flat-cell" ng-init="container = (VM.$container | resolve)">
<div ng-if="'pool' === container.type">
Resident on: <a ui-sref="pools_view({id: container.UUID})">{{container.name_label}}</a>
</div>
<div ng-if="'host' === container.type" ng-init="pool = (container.poolRef | resolve)">
Resident on: <a ui-sref="hosts_view({id: container.UUID})">{{container.name_label}}</a> (<a ui-sref="pools_view({id: pool.UUID})">{{pool.name_label}}</a>)
</div>
</div>
</div>
</div>
</div>
<!-- /Properties. -->
<!-- Tags. -->
<div class="grid">
<div class=" grid-cell">
<div class="grid-cell flat-cell-tag">
<i class="fa fa-tag"></i>&nbsp;&nbsp;
<span ng-repeat="tag in VM.tags">
<span class="label label-primary">{{tag}}</span>
</span>
</div>
</div>
</div>
<!-- /Tags. -->
</div>
<!-- /Properties & tags. -->
</div>
<!-- /VM. -->
<!-- If it's a SR. -->
<div
ng-repeat="SR in byTypes.SR | filter:listFilter | orderBy:natural('name_label') track by SR.UUID"
class="grid flat-object"
xo-sref="SRs_view({id: SR.UUID})"
>
<!-- Icon. -->
<div class="grid-cell flat-cell flat-cell-type">
<i class="xo-icon-sr"></i>
</div>
<!-- Properties & tags. -->
<div class="grid-cell">
<!-- Properties. -->
<div class="grid ">
<div class="grid-cell">
<div class="grid">
<div class="grid-cell flat-cell flat-cell-name">
{{SR.name_label}}
</div>
<div class="grid-cell flat-cell flat-cell-description">
<i>{{SR.name_description}}</i>
</div>
<div class="grid-cell flat-cell">
Usage: {{[SR.usage, SR.size] | %}} ({{SR.usage | bytesToSize}}/{{SR.size | bytesToSize}})
</div>
<div class="grid-cell flat-cell">
Type: {{SR.SR_type}}
</div>
<div class="grid-cell flat-cell" ng-init="container = (SR.$container | resolve)">
<div ng-if="'pool' === container.type">
<strong>Shared on <a ui-sref="pools_view({id: container.UUID})">{{container.name_label}}</a></strong>
</div>
<div ng-if="'host' === container.type">
Connected to <a ui-sref="hosts_view({id: container.UUID})">{{container.name_label}}</a>
</div>
</div>
</div>
</div>
</div>
<!-- /Properties. -->
<!-- Tags. -->
<div class="grid">
<div class=" grid-cell">
<div class="grid-cell flat-cell-tag">
<i class="fa fa-tag"></i>&nbsp;&nbsp;
<span ng-repeat="tag in SR.tags">
<span class="label label-primary">{{tag}}</span>
</span>
</div>
</div>
</div>
<!-- /Tags. -->
</div>
<!-- /Properties & tags. -->
</div>
<!-- /SR. -->

View File

@@ -1,512 +0,0 @@
<!-- @todo Remove code duplication for the VMs listing by using a macro. -->
<div class="sub-bar">
<div class="grid">
<div class="grid-cell overview">
<!--Stats-->
<i tooltip="{{xo.pools.length}} pools">
<i class="small">{{xo.pools.length}}x</i>
<i class="xo-icon-pool"></i>
</i>
&nbsp;
<i tooltip="{{hosts.length}} hosts">
<i class="small">{{hosts.length}}x</i>
<i class="xo-icon-host"></i>
</i>
&nbsp;
<i tooltip="{{xo.$running_VMs.length}} of {{VMs.length}} VMs running">
<i class="small">{{xo.$running_VMs.length}}x</i>
<i class="xo-icon-vm"></i>
</i>
&nbsp;
<i tooltip="{{xo.$vCPUs}} vCPUs used of {{xo.$CPUs}} CPUs">
<i class="small">{{xo.$vCPUs}}x</i>
<i class="xo-icon-cpu"></i>
</i>
&nbsp;
<i tooltip="{{xo.$memory.usage | bytesToSize}} RAM allocated of {{xo.$memory.size | bytesToSize}}">
<i class="small">{{xo.$memory.usage | bytesToSize}}</i>
<i class="xo-icon-memory"></i>
</i>
</div>
<div class="grid-cell">
<div class="btn-group before-action-bar">
<a class="btn navbar-btn btn-default dropdown-toggle inversed">
<input
type="checkbox"
class="inverse"
ng-model="master_selection"
ng-change="selectVMs(master_selection)"
ui-indeterminate="!(all || none)"
stop-event="click"
/>
<i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu inverse" role="menu">
<li ng-repeat="power_state in ['Halted', 'Running']">
<a ng-click="selectVMs({power_state: power_state})">
<i class="fa-fw xo-icon-{{power_state | lowercase}}"></i>
{{power_state}}
</a>
</li>
<li class="divider"></li>
<li ng-repeat="host in hosts | orderBy:natural('name_label') track by host.UUID" ng-if="host.VMs.length">
<a ng-click="selectVMs({$container: host.ref})">
<i class="xo-icon-host fa-fw"></i>
On {{host.name_label}}
</a>
</li>
</ul>
</div>
<div class="action-bar" ng-if="!none">
<div class="btn-group">
<button tooltip="Stop VM" type="button" class="btn navbar-btn btn-default inversed" ng-click="bulkAction('stopVM')">
<i class="fa fa-stop"></i>
</button>
<button tooltip="Start VM" type="button" class="btn navbar-btn btn-default inversed" ng-click="bulkAction('startVM')">
<i class="fa fa-play"></i>
</button>
<button tooltip="Reboot VM" type="button" class="btn navbar-btn btn-default inversed" ng-click="bulkAction('rebootVM')">
<i class="fa fa-refresh"></i>
</button>
</div>
<div class="btn-group">
<button tooltip="Migrate VM" type="button" class="btn navbar-btn btn-default dropdown-toggle inversed">
<i class="fa fa-share"></i> <i class="fa fa-caret-down"></i>
</button>
<ul class="dropdown-menu inverse" role="menu">
<li ng-repeat="host in hosts | orderBy:natural('name_label') track by host.UUID">
<a ng-click="bulkAction('migrateVM',host.UUID)">
<i class="xo-icon-host fa-fw"></i>
To {{host.name_label}}
</a>
</li>
</ul>
</div>
<div class="btn-group">
<button type="button" class="btn navbar-btn btn-default dropdown-toggle inversed">
More <i class="fa fa-caret-down"></i>
</button>
<ul class="dropdown-menu inverse" role="menu">
<li><a ng-click="bulkAction('force_rebootVM')"><i class="fa fa-bolt fa-fw"></i> Force reboot</a></li>
<li><a ng-click="bulkAction('force_stopVM')"><i class="fa fa-power-off fa-fw"></i> Force shutdown</a></li>
<li class="divider"></li>
<li><a ng-click="deleteVMs()"><i class="fa fa-trash-o fa-fw"></i> Delete</a></li>
<li><a ng-click="bulkAction('snapshotVM')"><i class="xo-icon-snapshot fa-fw"></i> Take a snapshot</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- FIXME: Ugly trick to force the pools to be under the sub bar. -->
<div style="margin-top: 50px; visibility: hidden; height: 0">.</div>
<!-- If we haven't any data -->
<div ng-if="!pools.length">
<div class="grid">
<div class="grid-cell flat-panel text-center">
<h1>Welcome on Xen Orchestra!</h1>
<h3>It seems you aren't connected to any Xen host.</h3>
<p>You can add any XAPI capable server by following these steps:</p>
<ul class="list-unstyled">
<li>Login with pre-filled account (top right of this screen)</li>
<li>Click on the menu icon " <i class="fa fa-th"></i> " and choose " <i class="fa fa-cog"></i> Settings "</li>
<li>Then you can add a server (no need to reload, refresh is auto)</li>
<li>Enjoy Xen Orchestra!</li>
</ul>
<p><a class="btn btn-primary big" ui-sref="about"><i class="fa fa-info-circle"></i> About us</a></p>
</div>
</div>
</div>
<!-- If we have data -->
<div ng-if="pools.length">
<!-- Contains a pool and all its children (hosts). -->
<div ng-repeat="pool in pools | orderBy:natural('name_label') track by pool.UUID" class="grid pool-block">
<!-- Pseudo pool if it is not a named pool. -->
<div ng-if="!pool.name_label" class="grid-cell grid--gutters pool-cell">
<p class="center" style="margin-top: 2em;">No pool connected</p>
</div>
<!-- Contains information about the pool if it is a named pool. -->
<div class="grid-cell grid--gutters pool-cell" ng-if="pool.name_label">
<!-- Header (name + dropdown menu). -->
<div class="dropdown dropdown-pool">
<a class="pool-name" ui-sref="pools_view({id: pool.UUID})">
{{pool.name_label}}
</a>
<a class="dropdown-toggle">&nbsp;<i class="fa fa-caret-down big-caret"></i>&nbsp;</a>
<ul class="dropdown-menu left" role="menu">
<li><a xo-click="new_sr()"><i class="xo-icon-sr fa-fw"></i> Add SR</a></li>
<li><a xo-sref="VMs_new({container: pool.UUID})"><i class="xo-icon-vm fa-fw"></i> Add VM</a></li>
<li class="divider"></li>
<li><a xo-click="pool_disconnect(pool.UUID)" class="disabled"><i class="fa fa-unlink fa-fw"></i> Disconnect</a></li>
</ul>
</div>
<!-- /Header. -->
<!-- Stats & SRs list. -->
<div>
<!-- Stats. -->
<ul class="list-unstyled stats">
<li>
<i tooltip="{{pool.hosts.length}} hosts connected">
<i class="small">{{pool.hosts.length}}x</i>
<i class="xo-icon-host"></i>
</i>
&nbsp;
<i tooltip="{{pool.$running_VMs.length}} of {{pool.$VMs.length}} VMs running">
<i class="small">{{pool.$running_VMs.length}}x</i>
<i class="xo-icon-vm"></i>
</i>
</li>
<li ng-if="pool.master" ng-init="master = (pool.master | resolve)">
Master : <a ui-sref="hosts_view({id: master.UUID})">{{master.name_label}}</a>
</li>
<li ng-if="!pool.master">No master</li>
</ul>
<!-- /Stats. -->
<!-- SRs. -->
<div ng-if="pool.SRs.length">
<p class="center small-caps">SRs:</p>
<table class="table table-hover table-condensed">
<tr
ng-repeat="SR in pool.SRs | resolve | orderBy:natural('name_label') track by SR.UUID"
xo-sref="SRs_view({id: SR.UUID})"
>
<td
class="col-md-6 sr-name no-border"
ng-class="{'default-sr': SR.ref === pool.default_SR}"
>
<i class="xo-icon-sr"></i>
{{SR.name_label}}
</td>
<td class="col-md-6 right no-border">
<div class="progress progress-small" tooltip="Disk: {{[SR.usage, SR.size] | %}} allocated">
<div
class="progress-bar"
role="progressbar"
aria-valuenow="{{100*SR.usage/SR.size}}"
aria-valuemin="0"
aria-valuemax="100"
style="width: {{[SR.usage, SR.size] | %}}"
></div>
</div>
</td>
</tr>
</table>
</div>
<!-- /SRs. -->
</div>
<!-- /Stats & SRs list. -->
</div>
<!-- /Information about the pool. -->
<!-- Contains all the hosts of this pool. -->
<div class="grid-cell grid--gutters hosts-vms-cells">
<!-- Contains a host and all its children (VMs). -->
<div
ng-repeat="host in pool.hosts | resolve | orderBy:natural('name_label') track by host.UUID"
class="grid"
>
<!-- Contains information about the host. -->
<div class="grid-cell host-cell">
<!-- Header (name + dropdown menu). -->
<div class="dropdown dropdown-pool">
<a class="host-name" ui-sref="hosts_view({id: host.UUID})">
{{host.name_label}}
</a>
<a class="dropdown-toggle">&nbsp;<i class="fa fa-caret-down"></i>&nbsp;</a>
<ul class="dropdown-menu left" role="menu">
<li><a xo-click="new_sr()"><i class="xo-icon-sr fa-fw"></i> Add SR</a></li>
<li><a xo-sref="VMs_new({container: host.UUID})"><i class="xo-icon-vm fa-fw"></i> Add VM</a></li>
<li class="divider"></li>
<li >
<a
ng-repeat="controller in [host.controller] | resolve track by controller.UUID"
xo-sref="consoles_view({id: controller.UUID})">
<i class="xo-icon-console fa-fw"></i> Console
</a>
</li>
<li><a xo-click="rebootHost(host.UUID)"><i class="fa fa-refresh fa-fw"></i> Reboot</a></li>
<li><a xo-click="shutdownHost(host.UUID)"><i class="fa fa-power-off fa-fw"></i> Shutdown</a></li>
<li><a xo-click="restartToolStack(host.UUID)"><i class="fa fa-retweet fa-fw"></i> Restart toolstack</a></li>
<li ng-if="pool.name_label"><a xo-click="pool_removeHost(host.UUID)"><i class="fa fa-cloud-upload fa-fw"></i> Remove from pool</a></li>
<li ng-if="!pool.name_label"><a xo-click="pool_addHost(host.UUID)"><i class="fa fa-cloud-download fa-fw"></i> Add to pool</a></li>
</ul>
</div>
<!-- /Header. -->
<!-- Stats. -->
<ul class="list-unstyled stats">
<!-- Warning icon if host is halted or disabled -->
<li ng-if="'Halted' === host.power_state" class="text-danger">
<i class="fa fa-warning"></i> Halted
</li>
<li ng-if="'Halted' !== host.power_state &amp;&amp; !host.enabled" class="text-warning">
<i class="fa fa-warning"></i> Disabled
</li>
<!-- Memory -->
<li ng-if="host.power_state === 'Running'">
<i class="xo-icon-memory i-progress"></i>
<div
class="progress progress-small"
tooltip="RAM: {{[host.memory.usage, host.memory.size] | %}} allocated">
<div
class="progress-bar"
role="progressbar"
aria-valuenow="{{100*host.memory.usage/host.memory.size}}"
aria-valuemin="0"
aria-valuemax="100"
style="width: {{[host.memory.usage, host.memory.size] | %}}"
></div>
</div>
</li>
<!-- Host address -->
<li class="text-muted substats">
<i class="xo-icon-network"></i>
{{host.address}}
</li>
</ul>
<!-- /Stats. -->
</div>
<!-- /Information about the host. -->
<!-- Contains all the VMs of this host. -->
<div class="grid grid-cell vm-cell">
<!-- If no VMs, fill the space with a message. -->
<div ng-if="!host.VMs.length" class="vms-notice">
<p ng-if="'Halted' === host.power_state">
Host halted.
</p>
<div ng-if="'Halted' !== host.power_state">
<p ng-if="!host.enabled">
Host disabled.
</p>
<p ng-if="host.enabled">
No VMs on this host.
</p>
</div>
</div>
<!-- /Message if no VMs. -->
<!-- TODO: comment -->
<div class="table-responsive" ng-if="host.VMs.length">
<table class="table table-hover table-condensed">
<!-- Contains a VM. -->
<tr
ng-repeat="VM in host.VMs | resolve | orderBy:natural('name_label') track by VM.UUID"
xo-sref="VMs_view({id: VM.UUID})"
>
<!-- Handle used for drag & drop. -->
<td class="grab"></td>
<!-- Checkbox used for selection. -->
<td class="select-vm">
<input
type="checkbox"
ng-model="selected_VMs[VM.UUID]"
ng-change="updateVMSelection(VM.UUID)"
/>
</td>
<!-- Power state -->
<td class="vm-power-state">
<i ng-if="isVMWorking(VM)"
class="xo-icon-working"
tooltip="{{VM.power_state}} and {{values(VM.current_operations)[0]}}"
></i>
<i ng-if="!isVMWorking(VM)"
class="xo-icon-{{VM.power_state | lowercase}}"
tooltip="{{VM.power_state}}"
></i>
</td>
<!-- VM name. -->
<td class="vm-name col-md-2"><p class="vm">{{VM.name_label}}</p></td>
<!-- Quick actions. -->
<td class="vm-quick-buttons col-md-2">
<div class="quick-buttons">
<a tooltip="Shutdown VM" xo-click="stopVM(VM.UUID)">
<i class="fa fa-stop"></i>
</a>
<a tooltip="Start VM" xo-click="startVM(VM.UUID)">
<i class="fa fa-play"></i>
</a>
<a tooltip="Reboot VM" xo-click="rebootVM(VM.UUID)">
<i class="fa fa-refresh"></i>
</a>
<a tooltip="VM Console" xo-sref="consoles_view({id: VM.UUID})">
<i class="xo-icon-console"></i>
</a>
</div>
</td>
<!-- Description. -->
<td class="vm-description col-md-4">
<i ng-if="VM.os_version.distro" tooltip="{{VM.os_version.name}}" class="xo-icon-{{osType(VM.os_version.distro)}}">&nbsp;</i>
<i ng-if="!VM.os_version.distro" class="fa fa-fw"></i>
{{VM.name_description}}
</td>
<!-- Metrics. -->
<!-- Memory -->
<td class="vm-memory-stat col-md-2">
<div class="cpu">
{{VM.memory.size | bytesToSize}}
</div>
<i
ng-if="VM.PV_drivers"
class="fa fa-fw"
></i>
<i
ng-if="!VM.PV_drivers"
class="xo-icon-info fa-fw"
tooltip="Xen tools not installed"
></i>
</td>
<!-- /Metrics. -->
<!-- Address. -->
<td class="text-muted text-right col-md-2">
{{VM.addresses['0/ip']}}
</td>
</tr>
</table>
</div>
</div>
<!-- /VMs of this host. -->
</div>
<!-- /Host with its children. -->
<!-- Contains a pseudo-host which contains all VMs not in any hosts. -->
<div ng-if="pool.VMs.length" class="grid">
<!-- This is where the information about a host would be displayed. -->
<div class="grid-cell host-cell"></div>
<!-- Contains all the VMs of this pool. -->
<div class="grid grid-cell vm-cell">
<!-- TODO: comment -->
<div class="table-responsive">
<table class="table table-hover table-condensed">
<!-- Contains a VM. -->
<tr
ng-repeat="VM in pool.VMs | resolve | orderBy:natural('name_label') track by VM.UUID"
xo-sref="VMs_view({id: VM.UUID})"
>
<!-- Handle used for drag & drop. -->
<td class="grab"></td>
<!-- Checkbox used for selection. -->
<td class="select-vm">
<input
type="checkbox"
ng-model="selected_VMs[VM.UUID]"
ng-change="updateVMSelection(VM.UUID)"
/>
</td>
<!-- Power state -->
<td class="vm-power-state">
<i
class="xo-icon-{{VM.power_state | lowercase}}"
tooltip="{{VM.power_state}}"
></i>
</td>
<!-- VM name. -->
<td class="vm-name col-md-2">
<p class="vm">{{VM.name_label}}</p>
</td>
<!-- Quick actions. -->
<td class="vm-quick-buttons col-md-2">
<div class="quick-buttons">
<a tooltip="Shutdown VM" xo-click="stopVM(VM.UUID)">
<i class="fa fa-stop"></i>
</a>
<a tooltip="Start VM" xo-click="startVM(VM.UUID)">
<i class="fa fa-play"></i>
</a>
<a tooltip="Reboot VM" xo-click="rebootVM(VM.UUID)">
<i class="fa fa-refresh"></i>
</a>
<a tooltip="VM Console">
<i class="xo-icon-console"></i>
</a>
</div>
</td>
<!-- Description. -->
<td class="vm-description col-md-4">
<i ng-if="VM.os_version.distro" tooltip="{{VM.os_version.name}}" class="xo-icon-{{osType(VM.os_version.distro)}}">&nbsp;</i>
<i ng-if="!VM.os_version.distro" class="fa fa-fw"></i>
{{VM.name_description}}
</td>
<!-- Metrics. -->
<!-- Memory -->
<td class="vm-memory-stat col-md-2">
<div class="cpu">
{{VM.memory.size | bytesToSize}}
</div>
<i
ng-if="VM.PV_drivers"
class="fa fa-fw"
></i>
<i
ng-if="!VM.PV_drivers"
class="xo-icon-info fa-fw"
tooltip="Xen tools not installed"
></i>
</td>
<!-- /Metrics. -->
<!-- Address. -->
<td class="text-muted text-right col-md-2">
{{VM.addresses['0/ip']}}
</td>
</tr>
</table>
</div>
</div>
<!-- /Pseudo host containing VMs not on any hosts. -->
</div>
<!-- /Hosts of this pool. -->
</div>
<!-- /Pool with its children. -->
</div>

View File

@@ -1,174 +0,0 @@
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<!-- Button used to (un)collapse on mobile display. -->
<button
type="button"
class="navbar-toggle"
ng-init="collapsed = true"
ng-click="collapsed = !collapsed"
>
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Brand name. -->
<a class="navbar-brand" ui-sref="home">Xen Orchestra</a>
</div>
<!-- All navbar items are collapsed on mobile display. -->
<div class="collapse navbar-collapse" ng-class="!collapsed &amp;&amp; 'in'">
<!-- Search form of the navbar. -->
<form class="navbar-form navbar-left" role="search" style="width: 250px">
<!-- Forced width due to issue with `input`s
(https://github.com/twbs/bootstrap/issues/9950. -->
<div class="input-group">
<input
type="text"
class="form-control inverse"
placeholder=""
ng-model="$root.listFilter"
ng-change="ensureListView()"
>
<span class="input-group-btn">
<button type="button" class="btn btn-search" ng-click="ensureListView()">
<i class="fa fa-search"></i>
</button>
</span>
</div>
</form>
<!-- /Search form. -->
<ul class="nav navbar-nav">
<li><a href="https://xen-orchestra.com/services?from=xoa" target="_blank">
<i class="xo-icon-info"></i> Unregistered version
</a></li>
</ul>
<!-- Right items of the navbar. -->
<ul class="nav navbar-nav navbar-right">
<li ng-if="'disconnected' === status" class="navbar-text">
<i class="xo-icon-error"></i> Disconnected from XO-Server
</li>
<li ng-if="'connecting' === status" class="navbar-text">
<i class="fa fa-refresh fa-spin"></i> Connecting to XO-Server
</li>
<!-- Main menu. -->
<li class="dropdown">
<a class="dropdown-toggle inverse">
<i class="fa fa-th"></i>
</a>
<ul class="dropdown-menu inverse">
<li ui-sref-active="active" ui-route="/">
<a ui-sref="home">
<i class="fa fa-indent"></i>
Tree view
</a>
</li>
<li ui-sref-active="active" ui-route="/list">
<a ui-sref="list">
<i class="fa fa-align-justify"></i>
Flat view
</a>
</li>
<li class="disabled" ui-sref-active="active" ui-route="/graph">
<a ui-sref="graph">
<i class="fa fa-sitemap"></i>
Graphs view
</a>
</li>
<li class="divider"></li>
<li class="disabled">
<a>
<i class="fa fa-clock-o"></i>
Scheduler
</a>
</li>
<li ui-sref-active="active" ui-route="/settings">
<a ui-sref="settings">
<i class="fa fa-cog"></i>
Settings
</a>
</li>
<li class="divider"></li>
<li ui-sref-active="active" ui-route="/about">
<a ui-sref="about">
<i class="fa fa-info-circle" style="color:#5bc0de"></i>
About
</a>
</li>
</ul>
</li>
<!-- /Main menu. -->
<!-- Displayed only when the user is connected. -->
<li ng-if="user"><a><i class="fa fa-user"></i> {{user.email}}</a></li>
<li ng-if="user"><a ng-click="logOut()"><i class="fa fa-sign-out"></i></a></li>
<!-- /When user is connected. -->
<!-- Displayed only when the user is not connected. -->
<li ng-if="!user" class="dropdown">
<a class="dropdown-toggle">
Log in&nbsp;&nbsp;<i class="fa fa-sign-in"></i>
</a>
<form
class="dropdown-menu login-form-dark"
ng-submit="logIn(login.email, login.password, true)"
ng-click="$event.stopPropagation()"
>
<div class="input-group">
<span class="input-group-addon">
<i class="fa fa-user fa-fw"></i>
</span>
<input
type="text"
class="form-control"
placeholder="Email"
ng-model="login.email"
name="email"
required
/>
</div>
<div class="input-group">
<span class="input-group-addon">
<i class="fa fa-key fa-fw"></i>
</span>
<input
type="password"
class="form-control"
placeholder="Password"
name="password"
ng-model="login.password"
required
/>
</div>
<button type="submit" class="btn btn-primary btn-block">
<i class="fa fa-sign-in"></i> Log in
</button>
</form>
</li>
<!-- /When user is not connected. -->
</ul>
<!-- /Right items. -->
</div>
<!-- /Navbar items. -->
</nav>
<!-- /Navbar. -->

View File

@@ -1,49 +0,0 @@
<!-- TODO: lots of stuff. -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="page-title"><i class="xo-icon-vm"></i> Add SR on {{container.name_label}}</p>
</div>
</div>
<!-- Add server panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-info-circle" style="color: #e25440;"></i> SR info</p>
<form class="form-horizontal" role="form">
<div class="form-group">
<label class="col-sm-3 control-label">Type</label>
<div class="col-sm-9">
<select class="form-control">
<option value="">-- Choose a type of SR --</option>
<optgroup label="VDI SR">
<option value="NFS">NFS</option>
<option value="ISCSI">Software iSCSI</option>
<option value="HBA">HBA</option>
</optgroup>
<optgroup label="ISO SR">
<option value="CIFSISO">CIFS</option>
<option value="NFSISO">NFS</option>
</optgroup>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Name</label>
<div class="col-sm-9">
<input type="text" class="form-control" placeholder="">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Description</label>
<div class="col-sm-9">
<input type="text" class="form-control" placeholder="SR Created by Xen Orchestra">
</div>
</div>
</form>
<p class="text-center"><a class="btn btn-primary big"><i class="fa fa-times"></i> Clear</a> <a class="btn btn-success big"><i class="fa fa-save"></i> Save</a></p>
</form>
</div>
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-link" style="color: #e25440;"></i> Connection</p>
</div>
</div>

View File

@@ -1,350 +0,0 @@
<div class="grid">
<div class="grid-cell flat-panel">
<p class="page-title">
<i class="xo-icon-vm"></i>
Add VM on
<a
ng-if="'pool' === container.type"
ui-sref="pools_view({id: container.UUID})"
>
{{container.name_label}}
</a>
<a
ng-if="'host' === container.type"
ui-sref="hosts_view({id: container.UUID})"
>
{{container.name_label}}
</a>
</p>
</div>
</div>
<!-- Add server panel -->
<form class="form-horizontal" ng-submit="createVM()">
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title">
<i class="fa fa-info-circle" style="color: #e25440;"></i> VM info
</p>
<div class="form-group">
<label class="col-sm-3 control-label">Template</label>
<div class="col-sm-9">
<select
class="form-control"
ng-model="template"
ng-options="template.name_label for template in templates | orderBy:natural('name_label') track by template.UUID"
required
>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Name</label>
<div class="col-sm-9">
<input
type="text"
class="form-control"
placeholder="Name of your new VM"
required
ng-model="name_label"
>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Description</label>
<div class="col-sm-9">
<input
type="text"
class="form-control"
placeholder="Optional description of you new VM"
ng-model="name_description"
>
</div>
</div>
</div>
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-dashboard" style="color: #e25440;"></i> Performances</p>
<div class="form-group">
<label class="col-sm-3 control-label">vCPUs</label>
<div class="col-sm-9">
<input
type="text"
class="form-control"
placeholder="{{template.CPUs.number}}"
ng-model="CPUs"
>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">RAM</label>
<div class="col-sm-9">
<input
type="text"
class="form-control"
placeholder="{{template.memory.size | bytesToSize}}"
ng-model="memory"
>
</div>
</div>
</div>
</div>
<div class="grid">
<!-- Install panel -->
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-download" style="color: #e25440;"></i> Install settings</p>
<div
class="form-group"
ng-show="availableMethods.cdrom"
>
<label class="col-sm-3 control-label">ISO/DVD</label>
<div class="col-sm-9"><div class="input-group">
<span class="input-group-addon"><input
type="radio"
value="cdrom"
ng-model="installation_method"
></span>
<select
ng-disabled="'cdrom' !== installation_method"
class="form-control disabled"
ng-model="installation_cdrom"
>
<optgroup
ng-repeat="SR in ISO_SRs | orderBy:natural('name_label') track by SR.UUID"
ng-if="SR.VDIs.length"
label="{{SR.name_label}}"
>
<option
ng-repeat="VDI in SR.VDIs | resolve | orderBy:natural('name_label') track by VDI.UUID"
ng-value="VDI.UUID"
>
{{VDI.name_label}}
</option>
</optgroup>
</select>
</div></div>
</div>
<div
class="form-group"
ng-show="availableMethods.http || availableMethods.ftp || availableMethods.nfs"
>
<label class="col-sm-3 control-label">Network</label>
<div class="col-sm-9"><div class="input-group">
<span class="input-group-addon"><input
type="radio"
ng-model="installation_method"
value="network"
></span>
<input
ng-disabled="'network' !== installation_method"
type="text"
class="form-control"
placeholder="e.g: http://ftp.debian.org/debian"
ng-model="installation_network"
>
</div></div>
</div>
<!-- <div class="form-group"> FIXME
<label class="col-sm-3 control-label">Home server</label>
<div class="col-sm-9">
<select class="form-control">
<option>Default (auto)</option>
</select>
</div>
</div> -->
</div>
<!-- Interface panel -->
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="xo-icon-network" style="color: #e25440;"></i> Interfaces
</p>
<table class="table table-hover">
<tr>
<th>MAC</th>
<th>Network</th>
<th class="col-md-1">&nbsp;</th><!-- Buttons -->
</tr>
<tr ng-repeat="VIF in VIFs track by VIF.id">
<td>
<input
type="text"
class="form-control"
ng-model="VIF.MAC"
ng-pattern="/^\s*[0-9a-f]{2}(:[0-9a-f]{2}){5}\s*$/i"
placeholder="00:00:00:00:00"
/>
</td>
<td>
<select
class="form-control"
ng-options="network.UUID as network.name_label for network in (networks | orderBy:natural('name_label'))"
ng-model="VIF.network"
required
>
</select>
</td>
<td>
<div class="pull-right">
<button
type="button"
class="btn btn-default"
ng-click="removeVIF($index)"
title="Remove this interface"
>
<i class="fa fa-times"></i>
</button>
</div>
</td>
</tr>
</table>
<div class="btn-form">
<p class="center">
<div class="btn-form">
<p class="center">
<button type="button" ng-click="addVIF()" class="btn btn-success">
<i class="fa fa-plus"></i> Add interface
</button>
</p>
</div>
</p>
</div>
</div>
</div>
<!-- end of misc and interface panel -->
<!-- Disk panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title">
<i class="xo-icon-sr" style="color: #e25440;"></i> Disks
</p>
<table class="table table-hover">
<tr>
<th class="col-md-2">SR</th>
<th class="col-md-1">Bootable?</th>
<th class="col-md-2">Size</th>
<th class="col-md-2">Name</th>
<th class="col-md-4">Description</th>
<th class="col-md-1">&nbsp;</th><!-- Buttons -->
</tr>
<tr ng-repeat="VDI in VDIs track by VDI.id">
<td>
<select
class="form-control"
ng-model="VDI.SR"
ng-options="SR.UUID as SR.name_label for SR in (writable_SRs | orderBy:natural('name_label'))"
>
</select>
</td>
<td class="text-center">
<input
type="checkbox"
ng-model="VDI.bootable"
>
</td>
<td>
<input
type="text"
class="form-control"
ng-model="VDI.size"
required
>
</td>
<td>
<input
type="text"
class="form-control"
placeholder="Name of this virtual disk"
ng-model="VDI.name_label"
>
</td>
<td>
<input
type="text"
class="form-control"
placeholder="Description of this virtual disk"
ng-model="VDI.name_description"
>
</td>
<td>
<div class="btn-group">
<button
type="button"
class="btn btn-default"
ng-click="moveVDI($index, -1)"
ng-disabled="$first"
title="Move this disk up"
>
<i class="fa fa-chevron-up"></i>
</button>
<button
type="button"
class="btn btn-default"
ng-click="moveVDI($index, 1)"
ng-disabled="$last"
title="Move this disk down"
>
<i class="fa fa-chevron-down"></i>
</button>
</div>
<div class="pull-right">
<button
type="button"
class="btn btn-default"
ng-click="removeVDI($index)"
title="Remove this disk"
>
<i class="fa fa-times"></i>
</button>
</div>
</td>
</tr>
</table>
<div class="btn-form">
<p class="center">
<div class="btn-form">
<p class="center">
<button type="button" ng-click="addVDI()" class="btn btn-success"><i class="fa fa-plus"></i> Add disk</button>
</p>
</div>
</p>
</div>
</div>
</div>
<!-- Confirmation panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-flag-checkered" style="color: #e25440;"></i> Summary</p>
<div class="grid">
<div class="grid-cell">
<p class="stat-name">Name:
<p class="center big">{{name_label}}</p>
</div>
<div class="grid-cell">
<p class="stat-name">Template:
<p class="center">{{template.name_label}}</p>
</div>
</div>
<div class="grid">
<div class="grid-cell">
<p class="stat-name">vCPUs</p>
<p class="center big">{{CPUs || template.CPUs.number}}</p>
</div>
<div class="grid-cell">
<p class="stat-name">RAM</p>
<p class="center big">{{(memory) || (template.memory.size | bytesToSize)}}</p>
</div>
<div class="grid-cell">
<p class="stat-name">Disks</p>
<p class="center big">{{(VDIs.length) || (template.$VBDs.length) || 0}}</p>
</div>
<div class="grid-cell">
<p class="stat-name">Interfaces</p>
<p class="center big">{{VIFs.length}}</p>
</div>
</div>
<p class="center"><button type="submit" class="btn btn-lg btn-primary"><i class="fa fa-play"></i> Create VM</button></p>
</div>
</div>
</form>

View File

@@ -1,220 +0,0 @@
<!-- TODO: lots of stuff. -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="page-title"><i class="xo-icon-pool"></i> {{pool.name_label}}</p>
</div>
</div>
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title">
<i class="fa fa-cogs" style="color: #e25440;"></i> General
<span class="quick-edit" tooltip="Edit General settings" ng-click="poolSettings.$show()">
<i class="fa fa-edit fa-fw"></i>
</span>
</p>
<div class="general-properties">
<form editable-form name="poolSettings" onbeforesave="savePool($data)">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>
<span editable-text="pool.name_label" e-name="name_label" e-form="poolSettings">
{{pool.name_label}}
</span>
</dd>
<dt>Description</dt>
<dd>
<span editable-text="pool.name_description" e-name="name_description" e-form="poolSettings">
{{pool.name_description}}
</span>
</dd>
<dt>Master</dt>
<dd ng-repeat="master in [pool.master] | resolve">
<a ui-sref="hosts_view({id: master.UUID})">
{{master.name_label}}
</a>
</dd>
<dt>Tags</dt>
<dd>
<span ng-repeat="tag in pool.tags">
<span class="label label-primary">{{tag}}</span>
</span>
</dd>
<dt ng-if="pool.default_SR">Default SR</dt>
<dd ng-if="pool.default_SR" ng-init="default_SR = (pool.default_SR | resolve)">
<a ui-sref="SRs_view({id: default_SR.UUID})">{{default_SR.name_label}}</a>
</dd>
<dt>HA</dt>
<dd>
{{pool.HA_enabled}}
</dd>
<dt>UUID</dt>
<dd>{{pool.UUID}}</dd>
</dl>
<div class="btn-form" ng-show="poolSettings.$visible">
<p class="center">
<button type="button" ng-disabled="poolSettings.$waiting" ng-click="poolSettings.$cancel()" class="btn btn-default">
<i class="fa fa-times"></i> Cancel
</button>
<button type="submit" ng-disabled="poolSettings.$waiting" class="btn btn-primary">
<i class="fa fa-save"></i> Save
</button>
</p>
</div>
</form>
</div>
</div>
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-bar-chart-o" style="color: #e25440;"></i> Stats</p>
<div class="grid">
<div class="grid-cell">
<p class="stat-name">Hosts:</p>
<p class="center big">{{pool.hosts.length}}</p>
</div>
<div class="grid-cell">
<p class="stat-name">Running:</p>
<p class="center big">{{pool.$running_hosts.length}}</p>
</div>
</div>
</div>
</div>
<!-- Action panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-flash" style="color: #e25440;"></i> Actions</p>
<div class="grid">
<div class="grid-cell text-center grid-button" tooltip="Add SR" xo-sref="SRs_new({container: pool.UUID})">
<i class="xo-icon-sr fa-2x fa-fw"></i>
</div>
<div class="grid-cell text-center grid-button" tooltip="Add VM" xo-sref="VMs_new({container: pool.UUID})">
<i class="xo-icon-vm fa-2x fa-fw"></i>
</div>
<div class="grid-cell text-center grid-button" tooltip="Add Host">
<i class="xo-icon-host fa-2x fa-fw"></i>
</div>
<div class="grid-cell text-center grid-button" tooltip="Disconnect">
<i class="fa fa-unlink fa-2x fa-fw"></i>
</div>
</div>
</div>
</div>
<!-- Hosts panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="xo-icon-host" style="color: #e25440;"></i> Hosts</p>
<table class="table table-hover table-condensed">
<th>Name</th>
<th class="col-md-4">Description</th>
<th class="col-md-6">Memory</th>
<tr
xo-sref="hosts_view({id: host.UUID})"
ng-repeat="host in pool.hosts | resolve | orderBy:natural('name_label') track by host.UUID"
>
<td>{{host.name_label}}</td>
<td>{{host.name_description}}</td>
<td>
<div class="progress-condensed">
<div
class="progress-bar"
role="progressbar"
aria-valuemin="0"
aria-valuenow="{{host.memory.usage}}"
aria-valuemax="{{host.memory.size}}"
style="width: {{[host.memory.usage, host.memory.size] | %}}"
></div>
</div>
</td>
</tr>
</table>
</div>
</div>
<!-- Shared SR panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="xo-icon-sr" style="color: #e25440;"></i> Shared SR
<table class="table table-hover">
<th>Name</th>
<th>Description</th>
<th>Type</th>
<th>Size</th>
<th class="col-md-4">Physical/Allocated usage</th>
<tr
xo-sref="SRs_view({id: SR.UUID})"
ng-repeat="SR in pool.SRs | resolve | orderBy:natural('name_label') track by SR.UUID"
>
<td>{{SR.name_label}}</td>
<td>{{SR.name_description}}</td>
<td>{{SR.SR_type}}</td>
<td>{{SR.size | bytesToSize}}</td>
<td>
<div class="progress-condensed">
<div
class="progress-bar"
role="progressbar"
aria-valuemin="0"
aria-valuenow="{{SR.usage}}"
aria-valuemax="{{SR.size}}"
style="width: {{[SR.physical_usage, SR.size] | %}}"
tooltip="Physical usage: {{[SR.physical_usage, SR.size] | %}}"
></div>
<div
class="progress-bar progress-bar-info"
role="progressbar"
aria-valuemin="0"
aria-valuenow="{{SR.physical_usage}}"
aria-valuemax="{{SR.size}}"
style="width: {{[(SR.usage-SR.physical_usage), SR.size] | %}}"
tooltip="Allocated: {{[(SR.usage), SR.size] | %}}"
></div>
</div>
</td>
</tr>
</table>
</div>
</div>
<!-- TODO: CPU and Logs panels -->
<div class="grid">
<!-- Logs panel -->
<div class="grid-cell flat-panel">
<form editable-form name="poolLogs" onaftersave="saveTable()" oncancel="cancel()">
<p class="flat-panel-title"><i class="fa fa-comments" style="color: #e25440;"></i> Logs
<span class="quick-edit" tooltip="Edit logs" ng-click="poolLogs.$show()">
<i class="fa fa-edit fa-fw"> </i>
</span>
</p>
<p class="center" ng-if="!pool.messages.length">No recent logs</p>
<table class="table table-hover" ng-if="pool.messages.length">
<th>Date</th>
<th>Name</th>
<tr ng-repeat="message in pool.messages | resolve | orderBy:'-time' track by message.UUID">
<td>{{message.time*1e3 | date:'medium'}}</td>
<td>
{{message.name}}
<button type="button" ng-show="poolLogs.$visible" ng-click="deleteLog(message.UUID)" class="btn btn-sm btn-danger pull-right"><i class="fa fa-trash-o fa-lg" tooltip="Remove this log entry"></i></button>
</td>
</tr>
</table>
<div class="btn-form" ng-show="poolLogs.$visible">
<p class="center">
<button type="button" ng-disabled="poolLogs.$waiting" ng-click="poolLogs.$cancel()" class="btn btn-default"><i class="fa fa-times"></i> Cancel</button>
</p>
</div>
</form>
</div>
<!-- Template panel -->
<!-- FIXME: error on accordion display, DOES NOT WORK SO FAR -->
<!-- <div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-copy" style="color: #e25440;"></i> Templates</p>
<p class="center" ng-if="!pool.templates.length">No templates</p>
<accordion close-others="true">
<accordion-group heading="{{template.name_label}}" ng-repeat="template in pool.templates | resolve | orderBy:natural('name_label') track by template.UUID">
{{template.name_description}}
</accordion-group>
</accordion>
</div> -->
</div>

View File

@@ -1,104 +0,0 @@
<!-- TODO: lots of stuff. -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="page-title"><i class="fa fa-cog"></i> XO Settings</p>
</div>
</div>
<!-- Add server panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-link" style="color: #e25440;"></i> Connected servers</p>
<form ng-submit="saveServers()" autocomplete="off">
<table class="table table-hover">
<tr>
<th>Host</th>
<th>User</th>
<th>Password</th>
<th>Delete</th>
</tr>
<tr ng-repeat="server in servers | orderBy:natural('host') track by server.id">
<td><input type="text" ng-model="server.host" class="form-control" /></td>
<td><input type="text" ng-model="server.username" class="form-control"/></td>
<td>
<input type="password" ng-model="server.password" class="form-control" placeholder="Fill to change the password"/>
</td>
<td><input type="checkbox" ng-model="selectedServers[server.id]"/></td>
</tr>
<tr ng-repeat="server in newServers">
<td>
<input type="text" ng-model="server.host" class="form-control" placeholder="address[:port]"/>
</td>
<td>
<input type="text" ng-model="server.username" class="form-control" placeholder="user"/>
</td>
<td>
<input type="password" ng-model="server.password" class="form-control" placeholder="password"/>
</td>
<td>&nbsp;</td>
</tr>
</table>
<p class="text-center">
<button type="submit" class="btn btn-primary">
<i class="fa fa-save"></i> Save
</button>
<button type="button" class="btn btn-success" ng-click="addServer()">
<i class="fa fa-plus"></i>
</button>
</p>
</form>
</div>
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-users" style="color: #e25440;"></i> Users</p>
<form ng-submit="saveUsers()" autocomplete="off">
<table class="table table-hover">
<tr>
<th>Email</th>
<th>Permissions</th>
<th>Password</th>
<th>Delete</th>
</tr>
<tr ng-repeat="user in users | orderBy:natural('email') track by user.id">
<td><input type="text" ng-model="user.email" class="form-control"/></td>
<td>
<select
ng-options="p.value as p.label for p in permissions"
ng-model="user.permission"
class="form-control"
>
</select>
</td>
<td>
<input type="password" ng-model="user.password" class="form-control" placeholder="Fill to change the password"/>
</td>
<td><input type="checkbox" ng-model="selectedUsers[user.id]" /></td>
</tr>
<tr ng-repeat="user in newUsers">
<td>
<input type="text" ng-model="user.email" class="form-control" placeholder="email"/>
</td>
<td>
<select
ng-options="p.value as p.label for p in permissions"
ng-model="user.permission"
class="form-control"
>
</select>
</td>
<td>
<input type="password" ng-model="user.password" class="form-control" placeholder="password"/>
</td>
<td>&nbsp;</td>
</tr>
</table>
<p class="text-center">
<button type="submit" class="btn btn-primary">
<i class="fa fa-save"></i> Save
</button>
<button type="button" class="btn btn-success" ng-click="addUser()">
<i class="fa fa-plus"></i>
</button>
</p>
</form>
</div>
</div>

View File

@@ -1,228 +0,0 @@
<div class="grid">
<div class="grid-cell flat-panel">
<p class="page-title"><i class="xo-icon-sr"></i> {{SR.name_label}}</p>
</div>
</div>
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title">
<i class="fa fa-cogs" style="color: #e25440;"></i> General
<span class="quick-edit" tooltip="Edit General settings" ng-click="srSettings.$show()">
<i class="fa fa-edit fa-fw"></i>
</span>
</p>
<div class="general-properties">
<form editable-form name="srSettings" onbeforesave="saveSR($data)">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>
<span editable-text="SR.name_label" e-name="name_label" e-form="srSettings">
{{SR.name_label}}
</span>
</dd>
<dt>Description</dt>
<dd>
<span editable-text="SR.name_description" e-name="name_description" e-form="srSettings">
{{SR.name_description}}
</span>
</dd>
<dt>Content type:</dt>
<dd>{{SR.SR_type}}</dd>
<dt>Tags</dt>
<dd ng-if="SR.tags.length">
<span ng-repeat="tag in SR.tags">
<span class="label label-primary">{{tag}}</span>
</span>
</dd>
<dd ng-if="!SR.tags.length">
<em>No tags.</em>
</dd>
<dt>Shared</dt>
<div ng-repeat="container in [SR.$container] | resolve">
<dd ng-if="'pool' === container.type">Yes (<a ui-sref="pools_view({id: container.UUID})">{{container.name_label}}</a>)</dd>
<dd ng-if="'host' === container.type">No</dd>
</div>
<dt>Size</dt>
<dd>{{SR.size | bytesToSize}}</dd>
<dt>UUID</dt>
<dd>{{SR.UUID}}</dd>
</dl>
<div class="btn-form" ng-show="srSettings.$visible">
<p class="center">
<button type="button" ng-disabled="srSettings.$waiting" ng-click="srSettings.$cancel()" class="btn btn-default">
<i class="fa fa-times"></i> Cancel
</button>
<button type="submit" ng-disabled="srSettings.$waiting" class="btn btn-primary">
<i class="fa fa-save"></i> Save
</button>
</p>
</div>
</form>
</div>
</div>
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-bar-chart-o" style="color: #e25440;"></i> Stats</p>
<div class="grid">
<div class="grid-cell">
<p class="stat-name">Physical Alloc:</p>
<p class="center big">{{[SR.physical_usage, SR.size] | %}}</p>
</div>
<div class="grid-cell">
<p class="stat-name">Virtual Alloc:</p>
<p class="center big">{{[SR.usage, SR.size] | %}}</p>
</div>
<div class="grid-cell">
<p class="stat-name">VDIs:</p>
<p class="center big">{{SR.VDIs.length}}</p>
</div>
</div>
</div>
</div>
<!-- TODO: Space panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title">
<i class="xo-icon-memory" style="color: #e25440;"></i> VDI Map
</p>
<div class="progress">
<div
ng-if="((VDI.size/SR.size)*100) > 0.5"
ng-repeat="VDI in SR.VDIs | resolve | orderBy:natural('name_label') track by VDI.UUID"
class="progress-bar progress-bar-vm"
role="progressbar"
aria-valuemin="0"
aria-valuenow="{{VDI.size}}"
aria-valuemax="{{SR.size}}"
style="width: {{[VDI.size, SR.size] | %}}"
xo-sref="VDIs_view({id: VDI.UUID})"
tooltip="{{VDI.name_label}} ({{[VDI.size, SR.size] | %}})"
>
<!-- display the name only if it fits in its progress bar -->
<span ng-if="VDI.name_label.length < ((VDI.size/SR.size)*100)">{{VDI.name_label}}</span>
</div>
</div>
<ul class="list-inline text-center">
<li>Total: {{SR.size | bytesToSize}}</li>
<li>Currently used: {{SR.usage | bytesToSize}}</li>
<li>Available: {{SR.size-SR.usage | bytesToSize}}</li>
</ul>
</div>
</div>
<!-- TODO: VDIs. -->
<div class="grid">
<div class="grid-cell flat-panel">
<form editable-form name="srDisks" onbeforesave="saveVDI()" oncancel="cancel()">
<p class="flat-panel-title">
<i class="fa fa-hdd-o" style="color: #e25440;"></i> Virtual disks
<span class="quick-edit" tooltip="Edit disks" ng-click="srDisks.$show()">
<i class="fa fa-edit fa-fw"> </i>
</span>
<span class="quick-edit" tooltip="Rescan" ng-click="rescanSr(SR.UUID)">
<i class="fa fa-refresh fa-fw"> </i>
</span>
</p>
<table class="table table-hover">
<tr>
<th>Name</th>
<th>Description</th>
<th>Size</th>
<th>Attached to:</th>
</tr>
<tr
ng-repeat="VDI in SR.VDIs | resolve | orderBy:natural('name_label')"
xo-sref="VDIs_view({id: VDI.UUID})"
>
<td>
<span editable-text="VDI.name_label" e-name="name_description" e-form="srDisks">
{{VDI.name_label}} <span ng-if="VDI.$snapshot_of" class="label label-info">snapshot</span>
</span>
</td>
<td>
<span editable-text="VDI.name_description" e-name="name_description" e-form="srDisks">
{{VDI.name_description}}
</span>
</td>
<td>
<span editable-text="VDI.size | bytesToSize" e-name="size" e-form="srDisks">
{{VDI.size | bytesToSize}}
</span>
</td>
<td
ng-if="((VDI.$VBD | resolve).VM)"
ng-init="VBD = (VDI.$VBD | resolve)"
>
{{(((VDI.$VBD | resolve).VM) | resolve).name_label}}
<button type="button" ng-show="srDisks.$visible" ng-click="disconnectVBD(VBD.UUID)" class="btn btn-sm btn-danger pull-right" tooltip="Disconnect this disk"><i class="fa fa-unlink fa-lg"></i></button>
</td>
<td ng-if="!((VDI.$VBD | resolve).VM)">
<button type="button" ng-show="srDisks.$visible" ng-click="deleteVDI(VDI.UUID)" class="btn btn-sm btn-danger pull-right" tooltip="Destroy this disk"><i class="fa fa-trash-o fa-lg"></i></button>
</td>
</tr>
</table>
<div class="btn-form" ng-show="srDisks.$visible">
<p class="center">
<button type="button" ng-disabled="srDisks.$waiting" ng-click="srDisks.$cancel()" class="btn btn-default"><i class="fa fa-times"></i> Cancel</button>
<button type="submit" ng-disabled="srDisks.$waiting" class="btn btn-primary"><i class="fa fa-save"></i> Save</button>
<button type="button" ng-disabled="srDisks.$waiting" ng-click="addVDI()" class="btn btn-success pull-right"><i class="fa fa-plus"></i> Add Disk</button>
</p>
</div>
</form>
</div>
</div>
<!-- /VDIs. -->
<!-- Hosts. -->
<div class="grid">
<div class="grid-cell flat-panel">
<form editable-form name="srHosts" onaftersave="saveTable()" oncancel="cancel()">
<p class="flat-panel-title">
<i class="fa fa-link" style="color: #e25440;"></i> Connected hosts
<span class="quick-edit" tooltip="Edit connected hosts" ng-click="srHosts.$show()">
<i class="fa fa-edit fa-fw"> </i>
</span>
<span class="quick-edit" tooltip="Reconnect all hosts" ng-click="reconnectAllHosts()">
<i class="fa fa-plus-square fa-fw"> </i>
</span>
</p>
<table class="table table-hover">
<th>Name</th>
<th>Status</th>
<tr
ng-repeat="PBD in SR.$PBDs | resolve"
ng-init="host = (PBD.host | resolve)"
xo-sref="hosts_view({id: host.UUID})"
>
<td>{{host.name_label}}</td>
<td ng-if="PBD.attached">
<span class="label label-success">Connected</span>
<button type="button" ng-show="srHosts.$visible" ng-click="disconnectPBD(host.UUID)" class="btn btn-sm btn-danger pull-right" tooltip="Disconnect this host"><i class="fa fa-unlink fa-lg"></i></button>
</td>
<td ng-if="!PBD.attached">
<span class="label label-default">Disabled</span>
<button type="button" ng-show="srHosts.$visible" ng-click="connectPBD(host.UUID)" class="btn btn-sm btn-primary pull-right" tooltip="Connect this host"><i class="fa fa-link fa-lg"></i></button>
</td>
</tr>
</table>
</form>
</div>
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-comments" style="color: #e25440;"></i> Logs</p>
<p class="center" ng-if="!SR.messages.length">No recent logs</p>
<table class="table table-hover" ng-if="SR.messages.length">
<th class="col-md-1">Date</th>
<th class="col-md-1">Name</th>
<tr ng-repeat="message in SR.messages | resolve | orderBy:'-time' track by message.UUID">
<td>{{message.time*1e3 | date:'medium'}}</td>
<td>
{{message.name}}
<a class="quick-remove" tooltip="Remove log">
<i class="fa fa-trash-o fa-fw"> </i>
</a>
</td>
</tr>
</table>
</div>
</div>
<!-- /Hosts. -->

View File

@@ -1,349 +0,0 @@
<!-- TODO: lots of stuff. -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="page-title"><i class="xo-icon-vm xo-color-{{VM.power_state | lowercase}}"></i> {{VM.name_label}}</p>
</div>
</div>
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-cogs" style="color: #e25440;"></i> General
<span class="quick-edit" tooltip="Edit General settings" ng-click="vmSettings.$show()">
<i class="fa fa-edit fa-fw"></i>
</span>
</p>
<div class="general-properties">
<form editable-form name="vmSettings" onbeforesave="saveVM($data)">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>
<span editable-text="VM.name_label" e-name="name_label" e-form="vmSettings">
{{VM.name_label}}
</span>
</dd>
<dt>Description</dt>
<dd>
<span editable-text="VM.name_description" e-name="name_description" e-form="vmSettings">
{{VM.name_description}}
</span>
</dd>
<dt ng-if="VM.power_state == ('Running' || 'Paused')">Running on:</dt>
<dd ng-repeat="container in [VM.$container] | resolve">
<a xo-sref="hosts_view({id: container.UUID})">
{{container.name_label}}
</a>
</dd>
<dt>Address</dt>
<dd ng-repeat="IP in VM.addresses">{{IP}}</dd>
<dt>Tags</dt>
<dd>
<!-- TODO angular select2 in angular xeditable
<input type="text" ui-select2="select2Options" ng-model="VM.tags" data-placeholder="tags">
-->
</dd>
<dt>vCPUs</dt>
<dd>
<span editable-text="VM.CPUs.number" e-name="CPUs" e-form="vmSettings">
{{VM.CPUs.number}}
</span>
</dd>
<dt>RAM</dt>
<dd>
<span editable-text="memorySize" e-name="memory" e-form="vmSettings">
{{memorySize}}
</span>
</dd>
<dt>UUID</dt>
<dd>{{VM.UUID}}</dd>
</dl>
<div class="btn-form" ng-show="vmSettings.$visible">
<p class="center">
<button type="button" ng-disabled="vmSettings.$waiting" ng-click="vmSettings.$cancel()" class="btn btn-default">
<i class="fa fa-times"></i> Cancel
</button>
<button type="submit" ng-disabled="vmSettings.$waiting" class="btn btn-primary">
<i class="fa fa-save"></i> Save
</button>
</p>
</div>
</form>
</div>
</div>
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-bar-chart-o" style="color: #e25440;"></i> Stats</p>
<div class="grid">
<div class="grid-cell">
<p class="stat-name">vCPUs</p>
<p class="center big">{{VM.CPUs.number}}</p>
</div>
<div class="grid-cell">
<p class="stat-name">RAM</p>
<p class="center big">{{VM.memory.size | bytesToSize}}</p>
</div>
<div class="grid-cell">
<p class="stat-name">Disks</p>
<p class="center big">{{VM.$VBDs.length || 0}}</p>
</div>
</div>
<br/>
<div class="grid">
<div ng-if="VM.os_version.distro" class="grid-cell">
<p class="stat-name">OS:</p>
<p class="center big">
<i tooltip="{{VM.os_version.name}}" style="color: black;" class="xo-icon-{{osType(VM.os_version.distro)}}"></i>
</p>
</div>
<div class="grid-cell">
<p class="stat-name">Xen tools:</p>
<p class="center">
<span ng-if="VM.PV_drivers" style="color:green;">Installed</span>
<span ng-if="!VM.PV_drivers">NOT installed</span>
</p>
</div>
</div>
</div>
</div>
<!-- Action panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<p class="flat-panel-title"><i class="fa fa-flash" style="color: #e25440;"></i> Actions</p>
<div class="grid-cell text-center">
<div class="grid">
<div class="grid-cell btn-group">
<button tooltip="Stop VM" type="button" style="width: 90%" class="btn" xo-click="stopVM(VM.UUID)">
<i class="fa fa-stop fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group">
<button tooltip="Start VM" type="button" style="width: 90%" class="btn" xo-click="startVM(VM.UUID)">
<i class="fa fa-play fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group">
<button tooltip="Reboot VM" type="button" style="width: 90%" class="btn" xo-click="rebootVM(VM.UUID)">
<i class="fa fa-refresh fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group">
<button tooltip="Migrate VM" type="button" style="width: 90%" class="btn dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-share fa-2x fa-fw"></i>
</button>
<ul class="dropdown-menu left" role="menu">
<li ng-repeat="host in hosts | orderBy:natural('name_label') track by host.UUID">
<a ng-click="migrateVM(VM.UUID, host.UUID)">
<i class="xo-icon-host fa-fw"></i>
To {{host.name_label}}
</a>
</li>
</ul>
</div>
<div class="grid-cell btn-group">
<button tooltip="Force Reboot" type="button" style="width: 90%" class="btn" xo-click="force_rebootVM(VM.UUID)">
<i class="fa fa-flash fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group">
<button tooltip="Force Shutdown" type="button" style="width: 90%" class="btn" xo-click="force_stopVM(VM.UUID)">
<i class="fa fa-power-off fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group">
<button tooltip="Delete VM" type="button" style="width: 90%" class="btn" xo-click="destroyVM(VM.UUID)">
<i class="fa fa-trash-o fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group">
<button tooltip="Create a snapshot" style="width: 90%" type="button" class="btn" xo-click="snapshotVM(VM.UUID,VM.name_label)">
<i class="xo-icon-snapshot fa-2x fa-fw"></i>
</button>
</div>
<div class="grid-cell btn-group" style="margin-bottom: 0.5em">
<button tooltip="VM Console" type="button" style="width: 90%" class="btn" xo-sref="consoles_view({id: VM.UUID})">
<i class="xo-icon-console fa-2x fa-fw"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- TODO: Disk panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<form editable-form name="vmDisks" onaftersave="saveTable()" oncancel="cancel()">
<p class="flat-panel-title">
<i class="xo-icon-sr" style="color: #e25440;"></i> Disk
<span class="quick-edit" tooltip="Edit disks" ng-click="vmDisks.$show()">
<i class="fa fa-edit fa-fw"> </i>
</span>
</p>
<table class="table table-hover">
<tr>
<th>Name</th>
<th>Description</th>
<th>Size</th>
<th>SR</th>
<th>Status</th>
</tr>
<tr
ng-repeat="VDI in VDIs | orderBy:natural('name_label') track by VDI.UUID"
ng-init="SR = (VDI.$SR | resolve); VBD = (VDI.$VBD | resolve)"
xo-sref="SRs_view({id: SR.UUID})"
>
<td>
<span editable-text="VDI.name_label" e-name="name_label" e-form="vmDisks" e-required>
{{VDI.name_label}}
</span>
</td>
<td>
<span editable-text="VDI.name_description" e-name="name_description" e-form="vmDisks">
{{VDI.name_description}}
</span>
</td>
<td>
<span editable-text="VDI.size | bytesToSize" e-name="size" e-form="vmDisks" e-required>
{{VDI.size | bytesToSize}}
</span>
</td>
<td>
<!-- Are SR editable? will trigger moving VDI to the new SR -->
{{SR.name_label}}
</td>
<td ng-if="VBD.attached">
<span editable-checkbox="VBD.attached" e-title="Connected?" class="label label-success" e-form="vmDisks" e-required>Connected</span>
<button type="button" ng-show="vmDisks.$visible" ng-click="disconnectVBD(VBD.UUID)" class="btn btn-sm btn-danger pull-right"><i class="fa fa-unlink fa-lg" tooltip="Disconnect this disk"></i></button>
</td>
<td ng-if="!VBD.attached">
<span editable-checkbox="VBD.attached" e-title="Connected?" class="label label-default" e-form="vmDisks" e-required>Disconnected</span>
<button type="button" ng-show="vmDisks.$visible" ng-click="deleteVDI(VDI.UUID)" class="btn btn-sm btn-danger pull-right"><i class="fa fa-trash-o fa-lg" tooltip="Remove this disk"></i></button>
</td>
</tr>
<tr ng-repeat="VDI in newVDIs | resolve | orderBy:natural('name_label') track by VDI.UUID">
</tr>
</table>
<div class="btn-form" ng-show="vmDisks.$visible">
<p class="center">
<button type="button" ng-disabled="vmDisks.$waiting" ng-click="vmDisks.$cancel()" class="btn btn-default"><i class="fa fa-times"></i> Cancel</button>
<button type="submit" ng-disabled="vmDisks.$waiting" ng-click="saveChanges()" class="btn btn-primary"><i class="fa fa-save"></i> Save</button>
<button type="button" ng-disabled="vmDisks.$waiting" ng-click="addVDI()" class="btn btn-success pull-right"><i class="fa fa-plus"></i> Add Disk</button>
</p>
</div>
</form>
</div>
</div>
</div>
<!-- TODO: Interface panel -->
<div class="grid">
<div class="grid-cell flat-panel">
<form editable-form name="vmInterfaces" onaftersave="saveTable()" oncancel="cancel()">
<p class="flat-panel-title"><i class="xo-icon-network" style="color: #e25440;"></i> Interface
<span class="quick-edit" tooltip="Edit interfaces" ng-click="vmInterfaces.$show()">
<i class="fa fa-edit fa-fw"> </i>
</span>
</p>
<table class="table table-hover">
<th>Device</th>
<th>MAC</th>
<th>MTU</th>
<th>Network</th>
<th>Link status</th>
<tr ng-repeat="VIF in VM.VIFs | resolve | orderBy:natural('name_label') track by VIF.UUID">
<td>VIF #{{VIF.device}}</td>
<td>
{{VIF.MAC}}
</td>
<td>
<span editable-text="VIF.MTU" e-name="mtu" e-form="vmInterfaces" e-required>
{{VIF.MTU}}
</span>
</td>
<td>
{{(VIF.$network | resolve).name_label}}
</td>
<td ng-if="VIF.attached">
<span editable-checkbox="VIF.attached" class="label label-success">Connected</span>
<button type="button" ng-show="vmInterfaces.$visible" ng-click="disconnectVIF(VIF.UUID)" class="btn btn-sm btn-danger pull-right"><i class="fa fa-ban fa-lg" tooltip="Disconnect this interface"></i></button>
</td>
<td ng-if="!VIF.attached">
<span editable-checkbox="VIF.attached" class="label label-default">Disconnected</span>
<button type="button" ng-show="vmInterfaces.$visible" ng-click="deleteVIF(VIF.UUID)" class="btn btn-sm btn-danger pull-right"><i class="fa fa-trash-o fa-lg" tooltip="Remove this interface"></i></button>
</td>
</tr>
</table>
<div class="btn-form" ng-show="vmInterfaces.$visible">
<p class="center">
<button type="button" ng-disabled="vmInterfaces.$waiting" ng-click="vmInterfaces.$cancel()" class="btn btn-default"><i class="fa fa-times"></i> Cancel</button>
<button type="button" ng-disabled="vmInterfaces.$waiting" ng-click="saveChanges()" class="btn btn-primary"><i class="fa fa-save"></i> Save</button>
<button type="button" ng-disabled="vmInterfaces.$waiting" ng-click="addNetwork()" class="btn btn-success pull-right"><i class="fa fa-plus"></i> Add Interface</button>
</p>
</div>
</form>
</div>
</div>
<!-- TODO: Snapshot and Logs panels -->
<div class="grid">
<!-- Snap panel -->
<div class="grid-cell flat-panel">
<form editable-form name="vmSnap" oncancel="cancel()">
<p class="flat-panel-title"><i class="xo-icon-snapshot" style="color: #e25440;"></i> Snapshots
<span class="quick-edit" tooltip="Edit snapshots" ng-click="vmSnap.$show()">
<i class="fa fa-edit fa-fw"> </i>
</span>
</p>
<p class="center" ng-if="!VM.snapshots.length">No snapshots</p>
<table class="table table-hover" ng-if="VM.snapshots.length">
<th>Date</th>
<th>Name</th>
<tr ng-repeat="snapshot in VM.snapshots | resolve | orderBy:'-snapshot_time' track by snapshot.UUID">
<td>{{snapshot.snapshot_time*1e3 | date:'medium'}}</td>
<td>
<span editable-text="snapshot.name_label" e-name="name_label" e-form="vmSnap" onbeforesave="saveSnapshot(snapshot.UUID, $data)" >
{{snapshot.name_label}}
</span>
<span class="pull-right">
<button type="button" ng-show="vmSnap.$visible" ng-click="deleteSnapshot(snapshot.UUID)" class="btn btn-sm btn-danger"><i class="fa fa-trash-o fa-lg" tooltip="Remove this snapshot"></i></button>
<button type="button" ng-show="vmSnap.$visible" ng-click="revertSnapshot(snapshot.UUID)" class="btn btn-sm btn-danger"><i class="fa fa-undo fa-lg" tooltip="Revert VM to this snapshot"></i></button>
</span>
</td>
</tr>
</table>
<div class="btn-form" ng-show="vmSnap.$visible">
<p class="center">
<button type="button" ng-disabled="vmSnap.$waiting" ng-click="vmSnap.$cancel()" class="btn btn-default"><i class="fa fa-times"></i> Cancel</button>
<button type="submit" ng-disabled="vmSnap.$waiting" ng-click="saveChanges()" class="btn btn-primary"><i class="fa fa-save"></i> Save</button>
</p>
</div>
</form>
</div>
<!-- Logs panel -->
<div class="grid-cell flat-panel">
<form editable-form name="vmLogs" onaftersave="saveTable()" oncancel="cancel()">
<p class="flat-panel-title"><i class="fa fa-comments" style="color: #e25440;"></i> Logs
<span class="quick-edit" tooltip="Edit logs" ng-click="vmLogs.$show()">
<i class="fa fa-edit fa-fw"> </i>
</span>
</p>
<p class="center" ng-if="!VM.messages.length">No recent logs</p>
<table class="table table-hover" ng-if="VM.messages.length">
<th>Date</th>
<th>Name</th>
<tr ng-repeat="message in VM.messages | resolve | orderBy:'-time' track by message.UUID">
<td>{{message.time*1e3 | date:'medium'}}</td>
<td>
{{message.name}}
<button type="button" ng-show="vmLogs.$visible" ng-click="deleteLog(message.UUID)" class="btn btn-sm btn-danger pull-right"><i class="fa fa-trash-o fa-lg" tooltip="Remove this log entry"></i></button>
</td>
</tr>
</table>
<div class="btn-form" ng-show="vmLogs.$visible">
<p class="center">
<button type="button" ng-disabled="vmLogs.$waiting" ng-click="vmLogs.$cancel()" class="btn btn-default"><i class="fa fa-times"></i> Cancel</button>
</p>
</div>
</form>
</div>
</div>

View File

@@ -4,9 +4,9 @@
"dependencies": {
"angular": "1.2.13",
"angular-animate": "1.2.13",
"angular-bootstrap": "~0.11.0",
"angular-cookies": "1.2.13",
"angular-notify-toaster": "0.4.3",
"angular-ui-bootstrap-bower": "0.10.0",
"angular-ui-router": "0.2.8-bowratic-tedium",
"angular-ui-select2": "0.0.5",
"angular-ui-utils": "0.1.1",
@@ -18,8 +18,5 @@
"json3": "3.3.0",
"noVNC": "https://github.com/kanaka/noVNC.git",
"sass-bootstrap": "3.0.2"
},
"resolutions": {
"angular": "1.2.13"
}
}

89
doc/README.md Normal file
View File

@@ -0,0 +1,89 @@
# Documentation
![](./assets/xo.png)
## Introduction
Welcome on the Xen Orchestra documentation. This document has multiple purposes, it explains:
- what is Xen Orchestra
- how to install it
- how to use it
## [Architecture](./architecture/README.md)
## [Installation](./installation/README.md)
## [Administration](./administration/README.md)
## [Layout](./layout/README.md)
## [VM Usage](./vm/README.md)
## [Recipes](./recipes/README.md)
<!--
This is what's left to do
### Hosts
#### Life-cycle
#### Memory Map
#### Edit host characteristics
#### Restart tool-stack
#### Remove from pool
#### Host console
#### Attached storage repository
#### Network (interface) management
#### Pending tasks
#### Logs
### Storage repositories
#### Edit SR characteristics
#### VDI Map
#### Virtual disks management
##### Rescan the repository
#### Connected hosts
#### Logs
### Pools
#### Edit pool characteristics
#### Hosts list
#### Shared SR list
#### Logs
## Troubleshooting
### Connection between XO-server and XO-web
### Consoles
#### With HTTPS
#### Behind a NAT or a firewall
## How to contribute
### Report bugs
### Fork us!
-->

View File

@@ -0,0 +1,101 @@
# Administration
Once Xen Orchestra is installed, you can configure some parameters in the configuration file. Let's see how to do that.
## Configuration
The configuration file is in XO-server folder (for XOA users, it's in `/root/xo-server/config/local.yaml.dist`). If it's not already done, copy this file to `local.yaml` in the same folder. Now, you can edit the configuration safely (if you destroy it, you can reuse the dist file).
WARNING: YAML is very strict with indentation: use spaces for it, not tabs.
### User to run XO-server as
By default, XO-server is running with 'nobody' user and 'nogroup' group. You can change it by uncommenting these lines and choose whatever user/group you want:
```yaml
user: 'nobody'
group: 'nogroup'
```
### HTTP listen address and port
By default, XO-server listens to all addresses (0.0.0.0) and runs on port 80. You can change this if you want in the `# Basic HTTP` section:
```yaml
host: '0.0.0.0'
port: 80
```
### HTTPS
XO-server can also run in HTTPS (both HTTP and HTTPS can cohabit), just modify what's needed in the `# Basic HTTPS` section, this time with certificates/keys you want and their path:
```yaml
host: '0.0.0.0'
port: 443
certificate: './certificate.pem'
key: './key.pem'
```
### Link to XO-web
On XOA, you shouldn't have to change this. On manual install, you need to link files served by XO-server for XO-web. That's the mount section. In this example, "xo-web" folder is in the same folder than "xo-server":
```yaml
mounts:
'/':
- '../xo-web/dist/'
```
### Redis server
By default, XO-server will try to contact Redis server on `localhost`, with the port `6379`. But you can define anything else you want:
```yaml
uri: 'tcp://db:password@hostname:port'
```
### Log file
On XOA, the log file for XO-server is in `/var/log/xo`: it has all of the server informations. Can be a real help when you have trouble.
## First connection
### Login screen
This is the login screen:
![](./assets/loginok.png)
Note the green *check* icons: it indicates that you are correctly connected to XO-server. If you see this icon: ![](./assets/loginbad.png), that's not good. Please check the Troubleshooting section if it's the case.
The default user login/password is `admin@admin.net` with `admin` password. This is what you should see after been logged:
![](./assets/welcome.png)
You should change your password now.
## Users and passwords
You can access users ans servers management in the Setting view. It's accessible from the main menu:
![](./assets/gosettings.png)
From there, you can modify your current password, then Save:
![](./assets/users.png)
You can add new users with limited rights. So far, **read** permission allow to see everything but not to interact with any objects. It's pretty basic for now, but [check how it will evolve soon](https://xen-orchestra.com/users-roles-in-xen-orchestra/) on our website.
## Add Xen hosts
Adding Xen hosts is in the same view (Settings) as users:
![](./assets/servers.png)
When you add a server, you just have to wait to be displayed (e.g: in the main view. Removing is less trivial, you need to restart XO-server (or it will not disappear).
**Congrats! You've reached the end of this doc. See the next part, [about how the interface works](../layout/README.md).**

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -0,0 +1,60 @@
# Architecture
This part is dedicated to the architecture of Xen Orchestra. It will give you hints about how we build this software.
## Overview
Here is a diagram giving an overview of what is Xen Orchestra:
![](./assets/xo-arch.jpg)
Xen Orchestra is split in modules:
- the core is "[xo-server](https://github.com/vatesfr/xo-server)", a daemon dealing directly with XenServer or XAPI capable hosts. This is where are stored users, and it's the center point for talking to your whole Xen infrastructure.
- the Web interface is in "[xo-web](https://github.com/vatesfr/xo-web)": you are running it directly in your browser. The connection with "xo-server" is done via *WebSockets*
- "[xo-cli](https://github.com/vatesfr/xo-cli)" is a new module allowing to send order directly in command line
We will use this modular architecture to add further parts later. It's completely flexible, allowing us to adapt Xen Orchestra in every existing work-flow.
## XO-server
XO-Server is the core of Xen Orchestra. It's central role opens a lot of possibilities versus other solutions. Let's see why.
### Daemon mode
As a daemon, XO-server is always up. In this way, it can listen and record every events occurring on your whole Xen infrastructure. Connections are always open and it can cache informations before serve it to another client (CLI, Web or anything else).
### Central point
Contrary to XenCenter, each Xen Orchestra's client is connected to one XO-Server, and not all the Xen servers. With a traditional architecture:
![](./assets/without-xo.jpg)
You can see how we avoid a lost of resources and bandwidth waste with a central point:
![](./assets/with-xo.jpg)
### Pluggable
It's really easy to plug other modules to XO-server, thus extend or adapt the solution to your needs (see XO-web and XO-cli for real examples).
### NodeJS under the hood
[NodeJS](https://en.wikipedia.org/wiki/Nodejs) is a software platform for scalable server-side and networking applications. It's famous for its efficiency, scalability and its asynchronous capabilities. Exactly what we need! Thus, XO-server is written in JavaScript.
## XO-web
This is probably the first thing you'll see of Xen Orchestra. It's the Web interface, allowing to interact with your virtual infrastructure. As a module for XO-web, it facilitates the everyday Xen administrator work, but also provide a solution to delegate some part of your infrastructure to other people.
### JavaScript
We are also using JavaScript for XO-web: we stay consistent from the back-end to the front-end with one main language. [AngularJS](https://en.wikipedia.org/wiki/Angularjs) and [Twitter Bootstrap](https://en.wikipedia.org/wiki/Bootstrap_%28front-end_framework%29) are also powerful allies to our everyday development.
## XO-cli
After [a request from someone on our Github repositoryy](https://github.com/vatesfr/xo-server/issues/23), we decided to add the possibility to do some actions with CLI. Just few hours after the request, [we created XO-cli](https://github.com/vatesfr/xo-cli). It's a real example of the Xen Orchestra modularity, and how we can be flexible.
## Other modules
In our vision, we think Xen Orchestra can be more than "just an Web GUI for Xen". With modularity, we can imagine a lot of things: because XO-server is a daemon, it can record any activity and thus create performance reports. The next step is auto-migrate VM for adapting the need to the demand (reducing the costs to only what you need). Everything is possible!
**Congrats! You've reached the end of this doc. See the next part, [about how to install Xen Orchestra](../installation/README.md).**

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
doc/assets/xo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

166
doc/installation/README.md Normal file
View File

@@ -0,0 +1,166 @@
# Installation
There is two ways to install Xen Orchestra. If you are just a user and not a developer, please consider using the easier way (XOA).
## Default credentials
Be advised that our default user and password for a fresh install are **admin@admin.net** and **admin**. Do not forget to change it to avoid troubles.
## Xen Orchestra Appliance (XOA)
The fastest way to install Xen Orchestra is to use our Appliance. You can [download it from here](https://xen-orchestra.com/install-and-update-xo-from-git/). Basically, it's a Debian VM with all the stuff needed to run Xen Orchestra and a update script. No more, no less.
Once you got it, you can import it with `xe vm-import filename=xoa_version_number.xva` or via XenCenter.
After the VM is imported, you just need to start it with a `xe vm-start vm=XOA` or with XenCenter.
XOA is in **DHCP** by default, so if you need to configure the IP, you need to edit `/etc/network/interfaces` as explain in the [Debian documentation](https://wiki.debian.org/NetworkConfiguration#Configuring_the_interface_manually). You can access the VM console through XenCenter or directly on your Xen hosts the `xe console vm=XOA` command.
Xen Orchestra is now accessible in your browser on ` http://your-vm-ip` or in HTTPS on the same URL.
### XOA credentials
So far, system/SSH user and password are **root**/**root**. Be smart, change the root password as soon as possible!
### Restart and update process in XOA
You can restart XOA by going in XOA on SSH (or console) and type `service xo restart`. If it fails, try it twice.
You can also update XOA with latest version with the integrated update script. This time, a `service xo update` do the job.
## Manual installation
This installation is validated on a fresh Debian 7 (Wheezy) 64 bits. It should be almost the same on others dpkg systems. For RPMs based OS, it should be close, because most of our dependencies came from NPM and not the OS itself.
As you may have seen, XO is composed of two parts: [XO-Server](https://github.com/vatesfr/xo-server/) and [XO-Web](https://github.com/vatesfr/xo-web/). They can be installed separately, even on different machines, but for the sake of simplicity we will set them up together.
## Packages and Pre-requisites
### NodeJS
XO needs Node.js. You can install it:
- by [following this procedure](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager).
- on Wheezy, the build from source was tested and working well.
- by using *n*, documented just below.
We'll use `n` because it's powerful and flexible. First, you need `wget` and `curl`. Then, install it as root:
```bash
wget https://raw.githubusercontent.com/visionmedia/n/master/bin/n -O /usr/local/bin/n
chmod +x /usr/local/bin/n
n stable
```
We'll consider at this point that you've got a working node on your box. E.g:
```
$ node -v
v0.10.30
```
### Packages
```
apt-get install build-essential redis-server libpng-dev ruby git
```
We also need compass in Ruby (we want to [remove this dependency as soon as possible](https://github.com/vatesfr/xo-web/issues/44))
```
gem install compass
```
## Fetching the Code
You may either download them [here](https://github.com/vatesfr/xo-server/archive/master.zip) and [here](https://github.com/vatesfr/xo-web/archive/master.zip) or use `git` with these repositories from `http://github.com/vatesfr/xo-server` and `http://github.com/vatesfr/xo-web`:
```
git clone http://github.com/vatesfr/xo-server
git clone http://github.com/vatesfr/xo-web
```
## Installing dependencies
### XO-Server
Once you have it, you can use `npm` to install the other dependencies: go into XO-Server directory and launch the following command:
```
npm install
```
Then, you have to create a config file for XO-Server:
```
cp config/local.yaml.dist config/local.yaml
```
Edit it to have the right path to deliver XO-Web, because XO-Server embeds an HTTP server (we assume that XO-Server and XO-Web are on the same directory). It's near the end of the file:
```yaml
mounts:
'/':
- '../xo-web/dist/'
```
WARNING: YAML is very strict with indentation: use spaces for it, not tabs.
In this config file, you can also change default ports (80 and 443) for XO-Server.
You can try to start XO-Server to see if it works. You should have something like that:
```
$ ./xo-server
WebServer listening on 0.0.0.0:80
[INFO] Default user: "admin@admin.net" with password "admin"
```
### XO-Web
First, we'll also install dependencies:
```
npm install
```
You can now install `bower` dependencies and build the application:
```
./gulp --production
```
## Running XO
The sole part you have to launch is XO-Server which is quite easy to do, just launch the `xo-server` script, which is in the root of XO-Server's directory':
```
$ ./xo-server
```
That's it! Go on your browser to the XO-Server IP address, and it works :)
## Misc
- You can also consider using [forever](https://github.com/nodejitsu/forever) to have always the process running.
```
npm install -g forever
forever start -c ./node_modules/.bin/coffee src/main.coffee
```
- Our stable branch is "master" and the beta branch is "next-release". You can change it if you want to test our latest features (on both XO-Server and XO-Web, do NOT mix them):
```
git checkout next-release
```
- If you want to update your current version, do this on both repositories:
```
git pull --ff-only
npm install
```
And this in XO-Web:
```
./gulp --production
```
**Congrats! You've reached the end of this doc. See the next part, [about administrate Xen Orchestra](../administration/README.md).**

62
doc/layout/README.md Normal file
View File

@@ -0,0 +1,62 @@
# Layout
In this section, we'll make a tour of the global layout of XO-web.
## Navigation bar
The navigation bar, on the top, is displayed on every views/pages. You can go to the main view by clicking on "Xen Orchestra", use the search bar, access to the main menu and disconnect your session from there.
![](./assets/navbar.png)
### Main menu
It's represented by the ![](./assets/iconmain.png). It gives you access to the main zones of XO-web.
![](./assets/gosettings.png)
### Search bar
The search bar is very useful to filter information, to quickly find what you need. The live filter works with every data on every object. Check the result in the Flat view section.
## Main view
The main view is a global view of all your pools, servers and VMs. The concept is to display all the important information with an horizontal hierarchy.
### Extended navigation bar
The first change is an extended navigation bar, with statistics and the "master checkbox":
![](./assets/navbarhome.png)
This master checkbox can be use to select multiple VM in one action: all VM running, halted, or VM on one host.
The bar change when you select a VM with its checkbox (or *via* the master checkbox):
![](./assets/navbarselected.png)
From this bar, you've got also Migrate VM display.
### Quick actions
We hovering a VM, you can see buttons for stop, start, reboot VM, and also access its console:
![](./assets/quickactions.png)
### Host and pools submenu
Don't forget to expand Pool and hosts submenus to explore what you can do:
![](./assets/submenus.png)
## Flat view
Flat view is non hierarchical view, with the main goal to serve the filtered results of the search field.
Here is a example with an IP address:
![](./assets/filter.png)
It also work for names, descriptions, everything else!
**Congrats! You've reached the end of this doc. See the next part, [about how to manage VMs](../vm/README.md).**

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

5
doc/recipes/README.md Normal file
View File

@@ -0,0 +1,5 @@
# Recipes
The place to find how to do cool things with Xen Orchestra :)
- [Xen Orchestra behind an Apache reverse proxy](apache-reverse.md) (thanks to *schn0052* [on our forum](https://xen-orchestra.com/forum/93-apache-reverse-proxy)!)

View File

@@ -0,0 +1,14 @@
# Xen Orchestra behind an Apache reverse proxy
As XO-web and XO-server communicates with *WebSockets*, you need to have the `mod_proxy_tunnel` in Apache (please [check the Apache documentation](http://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html) about it). It's available for Apache 2.4.5 and later.
Please use this configuration in this order or it will not work:
```
ProxyPass /api/ ws://x.x.x.x/api/
ProxyPassReverse /api/ ws://x.x.x.x/api/
ProxyPass / http://x.x.x.x/
ProxyPassReverse / http://x.x.x.x/
```

153
doc/vm/README.md Normal file
View File

@@ -0,0 +1,153 @@
# Virtual machines
VM are the core interest of virtualization. That's why they are first in this documentation. The VM view contains all you need to interact with a virtual machine:
![](./assets/vmview.png)
## Life-cycle (stop, start, reboot)
This is trivial, it can be done in the main view:
![](./assets/quickactions.png)
But also in the VM view, in the **Actions** panel:
![](./assets/actionbar.png)
## Live migration
Live migration allows to move your VM from an host to another without interrupting service. You can do it in the main view, by checking the needed VM, and choose the Migrate button in the extended navigation bar:
![](./assets/migrate2.png)
Or in the VM view in the **Actions** panel:
![](./assets/migrate1.png)
## Console
Xen Orchestra allows console access from the web. For that, we use the NoVNC project. Check the troubleshooting section if you have any problem for display it (it relies on a direct network connection, but it will be fixed soon).
Consoles are accessible from different views, but always with the same symbol: ![](./assets/consoleicon.png)
You can send *Ctrl+Alt+Del* key with the dedicated button. And you can also insert a virtual CD (from an ISO repository or a local DVD drive) in any VM you need:
![](./assets/consoleview.png)
## Edit VM characteristics
On the VM view, you can edit any characteristics using the edit icon: ![](./assets/edit.png)
E.g with **General** panel:
![](./assets/editpanel.png)
## Create a VM
You can create a VM from the pool submenu or the host submenu:
![](./assets/submenus.png)
Then, here you go, on the create vm view:
![](./assets/createvm.png)
Fill the required fields:
- choose a template
- pick a name
- choose install settings
- choose an interface and a Network
- you can modify disk size and add disks if you want
The summary is here to check if you are sure about your settings. Let's create a VM by clicking on "Create VM" button!
**WARNING**: if you create a VM from a special template (a previously existing VM converted in a template), you should remove all interfaces and disks! Because they are already existing in the template.
## Copy/clone a VM
Copying (or cloning, same thing) a VM allows you to "fork" an existing VM. You have 2 choices:
- Fast clone: it uses a snapshot on your storage back-end. Pro: it's very fast to create a fast clone. Cons: snapshots are less suitable for a long-term usage (important performance drawback).
- Full disk copy: it creates a new disk and copy the whole content of the VM in it. Pro: it's very reliable, and there is no link between this disk and its parent. Cons: disk creation is longer.
![](./assets/clone.png)
When to use *Fast clone*:
- create a temporary VM, e.g for development or test purpose
- disk performance is not needed
- you storage system is occupied at less than 80% (performance impact is very high after this average limit)
For production use, please consider *Full disk copy*.
## Create a template
When you create a template from an existing VM, it will **convert** your current VM to a template. **There is no turning back!**
After the conversion, you can create a new VM, and see in the template list, a new template with the name of the converted VM. The VM creation process is slightly different from a "classical" template because:
- your converted VM contains already a network interface
- and a disk
Thus, you don't need to create both of them (remove the network interface and let disks section empty).
This is very powerful for creating the same VM basis for a type of usage (e.g: a development stack pre-configured in the templated VM).
You can use this feature in the VM view, in the "Actions panel", using this icon: ![](./assets/totemplate.png)
## Delete
Remove a VM can be done from the main view but also the VM view.
In the main view, you need to check the VM you want to delete (can be multiple VM), then in the "More" menu, select the Delete action:
![](./assets/more_menu.png)
It will ask you if you are sure to delete the VM, and also the possibility to delete its associated disks:
![](./assets/confirmdeletevm.png)
In the VM view, the process is the same with the trash icon: ![](./assets/vmdelete.png)
## Snapshots management
Snapshotting is a very powerful feature. It allows the possibility to quickly save a VM state (e.g: for backups) but also to rollback to a previous state if needed.
The classical example is to create a snapshot before a VM OS update, check if it works, and revert/rollback if not. As snapshots are really fast, you can avoid a too long service interruption.
You can create a snapshot from the main view, with the "More" menu on each VM selected (can be multiples VM at one time):
![](./assets/more_menu.png)
Or in the VM view with the snapshot icon: ![](./assets/snapshoticon.png)
Created snapshots are visible in the VM view in the "Snapshots" panel:
![](./assets/snapshotpanel.png)
As you can see, the snapshot name is auto-generated (with the snapshot date). You can change the name, revert of delete snapshot by editing the panel:
![](./assets/edit_snap.png)
Snapshots operations are possible without power state distinction.
<!--
## Disk management
## Network (interface) management
-->
## Logs
Logs are XAPI events, like when a VM is started or shutdown:
![](./assets/log.png)
You can remove a log by editing the "Logs" panel and use this icon: ![](./assets/removelogicon.png)
## Group actions
You can make "group actions" in the main view, e.g migrate a bunch of VM in another host. For that, you have to use the check box for each VM you want or use the "master checkbox" for select VM in the same host or power state:
![](./assets/mastercheckbox.png)
The, choose the button you need in the [extended navigation bar](#extended-navigation-bar).

BIN
doc/vm/assets/actionbar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
doc/vm/assets/clone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
doc/vm/assets/createvm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
doc/vm/assets/edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

BIN
doc/vm/assets/edit_snap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
doc/vm/assets/editpanel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
doc/vm/assets/log.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Some files were not shown because too many files have changed in this diff Show More