Compare commits
215 Commits
xo-web/3.0
...
v3.5.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
261c9e2240 | ||
|
|
c03973867c | ||
|
|
ca6984f4a5 | ||
|
|
4fbc2680ba | ||
|
|
9a5b0a8104 | ||
|
|
ad998f7a63 | ||
|
|
91043e4b52 | ||
|
|
8f3bd0145b | ||
|
|
ace0051147 | ||
|
|
83720c6673 | ||
|
|
f1ce4de1eb | ||
|
|
013b23c918 | ||
|
|
d03debf59c | ||
|
|
00b55d893a | ||
|
|
085b92df9c | ||
|
|
8516ff7cb3 | ||
|
|
56d64d8adc | ||
|
|
5d68e48312 | ||
|
|
8b0be0a8c5 | ||
|
|
d6e4d6d927 | ||
|
|
bbfe60ffd2 | ||
|
|
c9cc0b5ce3 | ||
|
|
f0bf19f286 | ||
|
|
6492e55954 | ||
|
|
c9fd1d5833 | ||
|
|
e8f73cae6c | ||
|
|
7d436b40d0 | ||
|
|
55cd6d2a74 | ||
|
|
01a2585edf | ||
|
|
bb329b73ef | ||
|
|
9b4008f39c | ||
|
|
ef52f20993 | ||
|
|
f2a207e790 | ||
|
|
40f502492b | ||
|
|
c5f1cf94ac | ||
|
|
2e286d4a50 | ||
|
|
94a27fa79e | ||
|
|
0adec1dbe2 | ||
|
|
c38c53957d | ||
|
|
498eb0134e | ||
|
|
d34f9182c2 | ||
|
|
324f21cf3d | ||
|
|
200c0110fd | ||
|
|
5694b0054b | ||
|
|
8825fdcdaf | ||
|
|
7805bc172e | ||
|
|
aa321b4b22 | ||
|
|
c74df7abc5 | ||
|
|
c6ffe8978d | ||
|
|
6761e0df15 | ||
|
|
cc47225f24 | ||
|
|
58c47a325f | ||
|
|
706f7f3b7b | ||
|
|
4d67f8bd0b | ||
|
|
d5227e00ff | ||
|
|
62fa1d570c | ||
|
|
899b4ab29a | ||
|
|
a2801f0b7b | ||
|
|
84694fedef | ||
|
|
c0005741bd | ||
|
|
150a341510 | ||
|
|
e486713026 | ||
|
|
0201dfa8e3 | ||
|
|
78269a1b7b | ||
|
|
d1bf60ab17 | ||
|
|
6c368dc8cb | ||
|
|
10ac67c1e0 | ||
|
|
ba3b38d941 | ||
|
|
47044d93d3 | ||
|
|
423ac4ea04 | ||
|
|
b114b40f88 | ||
|
|
030ce18d65 | ||
|
|
44116b3559 | ||
|
|
78332c7bfd | ||
|
|
dd8ac28240 | ||
|
|
81818222e5 | ||
|
|
4a25af03f4 | ||
|
|
6515d73534 | ||
|
|
006fc9acd3 | ||
|
|
1aa878b3a6 | ||
|
|
23dc475905 | ||
|
|
a827ab6ffd | ||
|
|
f55784ce6a | ||
|
|
f7110ac472 | ||
|
|
52a94e0f47 | ||
|
|
c25eb678a8 | ||
|
|
5128efbbf4 | ||
|
|
c73ee3c531 | ||
|
|
54ba67f882 | ||
|
|
395bfe6eb5 | ||
|
|
e0ecf65aac | ||
|
|
a7c651c98c | ||
|
|
ed473f457a | ||
|
|
a572c1132d | ||
|
|
ba314c7135 | ||
|
|
a0788ab050 | ||
|
|
8885f0be10 | ||
|
|
9d433ed1d0 | ||
|
|
0125c9445e | ||
|
|
a3454d4143 | ||
|
|
ad3c224b10 | ||
|
|
43d186d464 | ||
|
|
a3671e776d | ||
|
|
862ce07f8d | ||
|
|
a78bc7d195 | ||
|
|
b0ce4404ea | ||
|
|
cd5641d2f3 | ||
|
|
51dd9f0419 | ||
|
|
6d0a76756d | ||
|
|
6e8342fbf4 | ||
|
|
8d227724d7 | ||
|
|
eab91ffa83 | ||
|
|
6488198a06 | ||
|
|
07b4c67b5c | ||
|
|
70077a1eac | ||
|
|
03763a6556 | ||
|
|
3cb8d77977 | ||
|
|
c8d7940451 | ||
|
|
2253aa1229 | ||
|
|
d5a16855fb | ||
|
|
1aec909c10 | ||
|
|
7b60980959 | ||
|
|
097424e2ee | ||
|
|
a99b17d240 | ||
|
|
7526230b69 | ||
|
|
6b8f9a6319 | ||
|
|
8e74556613 | ||
|
|
26a3086b81 | ||
|
|
6dd70137db | ||
|
|
94ab475a31 | ||
|
|
e77b6f3b85 | ||
|
|
4000b44d90 | ||
|
|
ad09d98d81 | ||
|
|
567d5e7676 | ||
|
|
123b0a331e | ||
|
|
11f1f3d132 | ||
|
|
a44ce0c538 | ||
|
|
23348c5d8c | ||
|
|
176c6524a2 | ||
|
|
6765b0218a | ||
|
|
b7fd209987 | ||
|
|
e11f0595d8 | ||
|
|
a01410f287 | ||
|
|
6e53682854 | ||
|
|
e5a19d73e1 | ||
|
|
42d72a7786 | ||
|
|
30a29b179b | ||
|
|
6a7ae710c5 | ||
|
|
7622fa88cb | ||
|
|
2f7aee02cc | ||
|
|
75848f0d6e | ||
|
|
c81c4ffc97 | ||
|
|
5bbd8e7102 | ||
|
|
f8fb69ac03 | ||
|
|
a2b703d450 | ||
|
|
25739d9f6f | ||
|
|
1b12be55b2 | ||
|
|
ae7bfa6ee2 | ||
|
|
c012149b63 | ||
|
|
0210d87573 | ||
|
|
35b8cf3769 | ||
|
|
a1bd76d5fd | ||
|
|
c0551eb5dc | ||
|
|
f9e6118bfa | ||
|
|
69052c1de9 | ||
|
|
050dd7b47c | ||
|
|
7d5e1009cb | ||
|
|
9b3b0d22f8 | ||
|
|
d2035a0609 | ||
|
|
4dcd9e3066 | ||
|
|
744cf692aa | ||
|
|
eb2a85ff1a | ||
|
|
5eaf40b84e | ||
|
|
9dc12b4b8e | ||
|
|
2a3e6a9599 | ||
|
|
3ad7d6dfed | ||
|
|
28f82e2572 | ||
|
|
89274268ea | ||
|
|
116048d7a2 | ||
|
|
9f3c97c9bc | ||
|
|
79e7c56419 | ||
|
|
d00c390e03 | ||
|
|
ce39ee7b29 | ||
|
|
df1596b785 | ||
|
|
12aaf86a8e | ||
|
|
62bca19beb | ||
|
|
5551fa6612 | ||
|
|
73ef8c8c09 | ||
|
|
da695dffb3 | ||
|
|
efad2c3461 | ||
|
|
b5cb8d3c16 | ||
|
|
e9f81a8a50 | ||
|
|
4d75cce22a | ||
|
|
4c5f4dd443 | ||
|
|
ddb8d1d8f6 | ||
|
|
0da59a8339 | ||
|
|
4323bc8c11 | ||
|
|
90a69bd063 | ||
|
|
d55332d999 | ||
|
|
aac3a02a89 | ||
|
|
6c79b74657 | ||
|
|
bf79f13689 | ||
|
|
fa3cb5d725 | ||
|
|
b48e40e5ab | ||
|
|
f4d5b30e85 | ||
|
|
2fcf590355 | ||
|
|
6f29ee74da | ||
|
|
70ed981957 | ||
|
|
04dbcb05bd | ||
|
|
b649d507df | ||
|
|
803abf4448 | ||
|
|
de92dbdbdd | ||
|
|
be28fdcd9f | ||
|
|
5b840b41fe | ||
|
|
678064baba |
2
.bowerrc
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"directory": "app/bower_components"
|
||||
"directory": "dist/bower_components"
|
||||
}
|
||||
|
||||
1
.gitattributes
vendored
@@ -1 +0,0 @@
|
||||
* text=auto
|
||||
138
.jshintrc
@@ -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": {}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '0.8'
|
||||
- '0.10'
|
||||
before_script:
|
||||
- 'npm install -g bower grunt-cli'
|
||||
- 'bower install'
|
||||
81
CHANGELOG.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# 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
|
||||
|
||||
- console view is now prettier ([#92](https://github.com/vatesfr/xo-web/issues/92))
|
||||
|
||||
### 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
|
||||
|
||||
- [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
|
||||
|
||||
- *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))
|
||||
- Bower cannot install `angular` automatically due to a version conflict ([#101](https://github.com/vatesfr/xo-web/issues/101))
|
||||
- Bower installs an incorrect version of `angular-animate` ([#102](https://github.com/vatesfr/xo-web/issues/102))
|
||||
|
||||
## **3.2.0** (2014-02-21)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- dependencies' versions should be fixed to ease deployment ([#93](https://github.com/vatesfr/xo-web/issues/93))
|
||||
- badges added to the README to see whether dependencies are up to date ([#90](https://github.com/vatesfr/xo-web/issues/90))
|
||||
- an error notification has been added when the connection to XO-Server failed ([#89](https://github.com/vatesfr/xo-web/issues/89))
|
||||
- in host view, there is now a link to the host console ([#87](https://github.com/vatesfr/xo-web/issues/87))
|
||||
- in VM view, deleting a disk requires a confirmation ([#85](https://github.com/vatesfr/xo-web/issues/85))
|
||||
- the VM and console icons are now different ([#80](https://github.com/vatesfr/xo-web/issues/80))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- consoles now work in Google Chrome \o/ ([#46](https://github.com/vatesfr/xo-web/issues/46))
|
||||
- in host view, many buttons were not working ([#79](https://github.com/vatesfr/xo-web/issues/79))
|
||||
- in main view, incorrect icons were fixes ([#81](https://github.com/vatesfr/xo-web/issues/81))
|
||||
- MAC addresses should not be ignored during VM creation ([#94](https://github.com/vatesfr/xo-web/issues/94))
|
||||
|
||||
## **3.1.0** (2014-02-14)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- in VM view, interfaces' network should be displayed ([#64](https://github.com/vatesfr/xo-web/issues/64))
|
||||
- middle-click or `Ctrl`+click should open new windows (even on pseudo-links) ([#66](https://github.com/vatesfr/xo-web/issues/66))
|
||||
- lists should use natural sorting (e.g. *VM 2* before *VM 10*) ([#69](https://github.com/vatesfr/xo-web/issues/69))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- consoles are not implemented for hosts ([#57](https://github.com/vatesfr/xo-web/issues/57))
|
||||
- it makes no sense to remove a stand-alone host from a pool (58)
|
||||
- in VM view, the migrate button is not working ([#59](https://github.com/vatesfr/xo-web/issues/59))
|
||||
- pool and host names overflow their box in the main view ([#63](https://github.com/vatesfr/xo-web/issues/63))
|
||||
- in host view, interfaces incorrectly named *networks* and VLAN not shown ([#70](https://github.com/vatesfr/xo-web/issues/70))
|
||||
- VM suspended state is not properly handled ([#71](https://github.com/vatesfr/xo-web/issues/71))
|
||||
- unauthenticated users should not be able to access to consoles ([#73](https://github.com/vatesfr/xo-web/issues/73))
|
||||
- incorrect scroll (under the navbar) when the view changes ([#74](https://github.com/vatesfr/xo-web/issues/74))
|
||||
410
Gruntfile.js
@@ -1,410 +0,0 @@
|
||||
// Generated on 2013-11-12 using generator-angular 0.6.0-rc.1
|
||||
'use strict';
|
||||
|
||||
// # Globbing
|
||||
// for performance reasons we're only matching one level down:
|
||||
// 'test/spec/{,*/}*.js'
|
||||
// use this if you want to recursively match all subfolders:
|
||||
// 'test/spec/**/*.js'
|
||||
|
||||
module.exports = function (grunt) {
|
||||
require('load-grunt-tasks')(grunt);
|
||||
require('time-grunt')(grunt);
|
||||
|
||||
grunt.initConfig({
|
||||
|
||||
// Defines some variables.
|
||||
yeoman: {
|
||||
// configurable paths
|
||||
app: require('./bower.json').appPath || 'app',
|
||||
dist: 'dist'
|
||||
},
|
||||
|
||||
// Runs appropriate tasks whenever a file changes.
|
||||
watch: {
|
||||
coffee: {
|
||||
files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'],
|
||||
tasks: ['coffee:dist']
|
||||
},
|
||||
coffeeTest: {
|
||||
files: ['test/spec/{,*/}*.coffee'],
|
||||
tasks: ['coffee:test']
|
||||
},
|
||||
compass: {
|
||||
files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
|
||||
tasks: ['compass:server', 'autoprefixer']
|
||||
},
|
||||
styles: {
|
||||
files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
|
||||
tasks: ['copy:styles', 'autoprefixer']
|
||||
},
|
||||
livereload: {
|
||||
options: {
|
||||
livereload: '<%= connect.options.livereload %>'
|
||||
},
|
||||
files: [
|
||||
'<%= yeoman.app %>/{,*/}*.html',
|
||||
'.tmp/styles/{,*/}*.css',
|
||||
'{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
|
||||
'<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Automatically prefixes CSS rules.
|
||||
autoprefixer: {
|
||||
options: ['last 1 version'],
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '.tmp/styles/',
|
||||
src: '{,*/}*.css',
|
||||
dest: '.tmp/styles/'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// Starts an HTTP server.
|
||||
connect: {
|
||||
options: {
|
||||
port: 9000,
|
||||
// Change this to '0.0.0.0' to access the server from outside.
|
||||
hostname: 'localhost',
|
||||
livereload: 35729
|
||||
},
|
||||
livereload: {
|
||||
options: {
|
||||
base: [
|
||||
'.tmp',
|
||||
'<%= yeoman.app %>'
|
||||
]
|
||||
}
|
||||
},
|
||||
test: {
|
||||
options: {
|
||||
port: 9001,
|
||||
base: [
|
||||
'.tmp',
|
||||
'test',
|
||||
'<%= yeoman.app %>'
|
||||
]
|
||||
}
|
||||
},
|
||||
dist: {
|
||||
options: {
|
||||
base: '<%= yeoman.dist %>'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Deletes dirty files before build.
|
||||
clean: {
|
||||
dist: {
|
||||
files: [{
|
||||
dot: true,
|
||||
src: [
|
||||
'.tmp',
|
||||
'<%= yeoman.dist %>/*',
|
||||
'!<%= yeoman.dist %>/.git*'
|
||||
]
|
||||
}]
|
||||
},
|
||||
server: '.tmp'
|
||||
},
|
||||
|
||||
// Checks JavaScript files for errors and potential problems.
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc',
|
||||
reporter: require('jshint-stylish')
|
||||
},
|
||||
all: [
|
||||
'Gruntfile.js',
|
||||
'<%= yeoman.app %>/scripts/{,*/}*.js'
|
||||
]
|
||||
},
|
||||
|
||||
// Compiles CoffeeScript files to JavaScript.
|
||||
coffee: {
|
||||
options: {
|
||||
sourceMap: true,
|
||||
sourceRoot: ''
|
||||
},
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>/scripts',
|
||||
src: '{,*/}*.coffee',
|
||||
dest: '.tmp/scripts',
|
||||
ext: '.js'
|
||||
}]
|
||||
},
|
||||
test: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'test/spec',
|
||||
src: '{,*/}*.coffee',
|
||||
dest: '.tmp/spec',
|
||||
ext: '.js'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// Compiles SASS/SCSS files to CSS using Compass.
|
||||
compass: {
|
||||
options: {
|
||||
sassDir: '<%= yeoman.app %>/styles',
|
||||
cssDir: '.tmp/styles',
|
||||
generatedImagesDir: '.tmp/images/generated',
|
||||
imagesDir: '<%= yeoman.app %>/images',
|
||||
javascriptsDir: '<%= yeoman.app %>/scripts',
|
||||
fontsDir: '<%= yeoman.app %>/fonts',
|
||||
importPath: '<%= yeoman.app %>/bower_components',
|
||||
httpImagesPath: '/images',
|
||||
httpGeneratedImagesPath: '/images/generated',
|
||||
httpFontsPath: '/fonts',
|
||||
relativeAssets: false
|
||||
},
|
||||
dist: {},
|
||||
server: {
|
||||
options: {
|
||||
debugInfo: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// not used since Uglify task does concat,
|
||||
// but still available if needed
|
||||
/*concat: {
|
||||
dist: {}
|
||||
},*/
|
||||
|
||||
// Adds assets hash to their filename (useful for caching).
|
||||
rev: {
|
||||
dist: {
|
||||
files: {
|
||||
src: [
|
||||
'<%= yeoman.dist %>/scripts/{,*/}*.js',
|
||||
'<%= yeoman.dist %>/styles/{,*/}*.css',
|
||||
'<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
|
||||
'<%= yeoman.dist %>/styles/fonts/*'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
useminPrepare: {
|
||||
html: '<%= yeoman.app %>/index.html',
|
||||
options: {
|
||||
dest: '<%= yeoman.dist %>'
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
usemin: {
|
||||
html: ['<%= yeoman.dist %>/{,*/}*.html'],
|
||||
css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
|
||||
options: {
|
||||
assetsDirs: ['<%= yeoman.dist %>']
|
||||
}
|
||||
},
|
||||
|
||||
// Compresses PNG and JPG images.
|
||||
imagemin: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>/images',
|
||||
src: '{,*/}*.{png,jpg,jpeg}',
|
||||
dest: '<%= yeoman.dist %>/images'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// Compresses SVG images.
|
||||
svgmin: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>/images',
|
||||
src: '{,*/}*.svg',
|
||||
dest: '<%= yeoman.dist %>/images'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// Compresses CSS stylesheets.
|
||||
cssmin: {
|
||||
// By default, your `index.html` <!-- Usemin Block --> will take care of
|
||||
// minification. This option is pre-configured if you do not wish to use
|
||||
// Usemin blocks.
|
||||
// dist: {
|
||||
// files: {
|
||||
// '<%= yeoman.dist %>/styles/main.css': [
|
||||
// '.tmp/styles/{,*/}*.css',
|
||||
// '<%= yeoman.app %>/styles/{,*/}*.css'
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
},
|
||||
|
||||
// Compresses HTML pages.
|
||||
htmlmin: {
|
||||
dist: {
|
||||
options: {
|
||||
/*removeCommentsFromCDATA: true,
|
||||
// https://github.com/yeoman/grunt-usemin/issues/44
|
||||
//collapseWhitespace: true,
|
||||
collapseBooleanAttributes: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeOptionalTags: true*/
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
src: ['*.html', 'views/*.html'],
|
||||
dest: '<%= yeoman.dist %>'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// Put files not handled in other tasks here
|
||||
copy: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
dot: true,
|
||||
cwd: '<%= yeoman.app %>',
|
||||
dest: '<%= yeoman.dist %>',
|
||||
src: [
|
||||
'*.{ico,png,txt}',
|
||||
'.htaccess',
|
||||
'bower_components/**/*',
|
||||
'images/{,*/}*.{gif,webp}',
|
||||
'fonts/*'
|
||||
]
|
||||
}, {
|
||||
expand: true,
|
||||
cwd: '.tmp/images',
|
||||
dest: '<%= yeoman.dist %>/images',
|
||||
src: [
|
||||
'generated/*'
|
||||
]
|
||||
}]
|
||||
},
|
||||
styles: {
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.app %>/styles',
|
||||
dest: '.tmp/styles/',
|
||||
src: '{,*/}*.css'
|
||||
}
|
||||
},
|
||||
|
||||
// Runs some tasks concurrently.
|
||||
concurrent: {
|
||||
server: [
|
||||
'coffee:dist',
|
||||
'compass:server',
|
||||
'copy:styles'
|
||||
],
|
||||
test: [
|
||||
'coffee',
|
||||
'compass',
|
||||
'copy:styles'
|
||||
],
|
||||
dist: [
|
||||
'coffee',
|
||||
'compass:dist',
|
||||
'copy:styles',
|
||||
'imagemin',
|
||||
'svgmin',
|
||||
'htmlmin'
|
||||
]
|
||||
},
|
||||
|
||||
// Runs the test configuration.
|
||||
karma: {
|
||||
unit: {
|
||||
configFile: 'karma.conf.js',
|
||||
singleRun: true
|
||||
}
|
||||
},
|
||||
|
||||
// Updates HTML pages to use Google CDN for assets when possible.
|
||||
cdnify: {
|
||||
dist: {
|
||||
html: ['<%= yeoman.dist %>/*.html']
|
||||
}
|
||||
},
|
||||
|
||||
// Ensures the Angular code will still work after minification.
|
||||
ngmin: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '.tmp/concat/scripts',
|
||||
src: '*.js',
|
||||
dest: '.tmp/concat/scripts'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
// Minifies JavaScript files.
|
||||
uglify: {
|
||||
dist: {
|
||||
files: {
|
||||
'<%= yeoman.dist %>/scripts/scripts.js': [
|
||||
'<%= yeoman.dist %>/scripts/scripts.js'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('server', 'Runs a web server.', function (target) {
|
||||
if (target === 'dist') {
|
||||
return grunt.task.run(['build', 'connect:dist:keepalive']);
|
||||
}
|
||||
|
||||
grunt.task.run([
|
||||
'clean:server',
|
||||
'concurrent:server',
|
||||
'autoprefixer',
|
||||
'connect:livereload',
|
||||
'watch'
|
||||
]);
|
||||
});
|
||||
|
||||
grunt.registerTask('test', 'Runs the application\'s tests.', [
|
||||
'clean:server',
|
||||
'concurrent:test',
|
||||
'autoprefixer',
|
||||
'connect:test',
|
||||
'karma'
|
||||
]);
|
||||
|
||||
grunt.registerTask('build', 'Builds the application for distribution.', [
|
||||
'clean:dist',
|
||||
'useminPrepare',
|
||||
'concurrent:dist',
|
||||
'autoprefixer',
|
||||
'concat',
|
||||
'ngmin',
|
||||
'copy:dist',
|
||||
'cdnify',
|
||||
'cssmin',
|
||||
'uglify',
|
||||
'rev',
|
||||
'usemin'
|
||||
]);
|
||||
|
||||
grunt.registerTask('default', 'Tests and build the application.', [
|
||||
'jshint',
|
||||
'test',
|
||||
'build'
|
||||
]);
|
||||
};
|
||||
33
README.md
@@ -4,12 +4,43 @@ XO-Web is part of [Xen Orchestra](https://github.com/vatesfr/xo), a web interfac
|
||||
|
||||
It is a web client for [XO-Server](https://github.com/vatesfr/xo-server).
|
||||
|
||||
[](https://david-dm.org/vatesfr/xo-web)
|
||||
[](https://david-dm.org/vatesfr/xo-web#info=devDependencies)
|
||||
|
||||
___
|
||||
|
||||
## Installation
|
||||
|
||||
Ask us on *freenode/#vates* or wait for updates ;)
|
||||
Manual install procedure is [available here](https://github.com/vatesfr/xo/blob/master/installation.md)
|
||||
|
||||
## How to report a bug?
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
*.coffee
|
||||
543
app/.htaccess
@@ -1,543 +0,0 @@
|
||||
# Apache Configuration File
|
||||
|
||||
# (!) Using `.htaccess` files slows down Apache, therefore, if you have access
|
||||
# to the main server config file (usually called `httpd.conf`), you should add
|
||||
# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
|
||||
|
||||
# ##############################################################################
|
||||
# # CROSS-ORIGIN RESOURCE SHARING (CORS) #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Cross-domain AJAX requests |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Enable cross-origin AJAX requests.
|
||||
# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
|
||||
# http://enable-cors.org/
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Access-Control-Allow-Origin "*"
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | CORS-enabled images |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Send the CORS header for images when browsers request it.
|
||||
# https://developer.mozilla.org/en/CORS_Enabled_Image
|
||||
# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
|
||||
# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
|
||||
|
||||
<IfModule mod_setenvif.c>
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$">
|
||||
SetEnvIf Origin ":" IS_CORS
|
||||
Header set Access-Control-Allow-Origin "*" env=IS_CORS
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Web fonts access |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Allow access from all domains for web fonts
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$">
|
||||
Header set Access-Control-Allow-Origin "*"
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # ERRORS #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | 404 error prevention for non-existing redirected folders |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Prevent Apache from returning a 404 error for a rewrite if a directory
|
||||
# with the same name does not exist.
|
||||
# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
|
||||
# http://www.webmasterworld.com/apache/3808792.htm
|
||||
|
||||
Options -MultiViews
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Custom error messages / pages |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# You can customize what Apache returns to the client in case of an error (see
|
||||
# http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
|
||||
|
||||
ErrorDocument 404 /404.html
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # INTERNET EXPLORER #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Better website experience |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Force IE to render pages in the highest available mode in the various
|
||||
# cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-UA-Compatible "IE=edge"
|
||||
# `mod_headers` can't match based on the content-type, however, we only
|
||||
# want to send this header for HTML pages and not for the other resources
|
||||
<FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
|
||||
Header unset X-UA-Compatible
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Cookie setting from iframes |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Allow cookies to be set from iframes in IE.
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Screen flicker |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Stop screen flicker in IE on CSS rollovers (this only works in
|
||||
# combination with the `ExpiresByType` directives for images from below).
|
||||
|
||||
# BrowserMatch "MSIE" brokenvary=1
|
||||
# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
|
||||
# BrowserMatch "Opera" !brokenvary
|
||||
# SetEnvIf brokenvary 1 force-no-vary
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # MIME TYPES AND ENCODING #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Proper MIME types for all files |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
<IfModule mod_mime.c>
|
||||
|
||||
# Audio
|
||||
AddType audio/mp4 m4a f4a f4b
|
||||
AddType audio/ogg oga ogg
|
||||
|
||||
# JavaScript
|
||||
# Normalize to standard type (it's sniffed in IE anyways):
|
||||
# http://tools.ietf.org/html/rfc4329#section-7.2
|
||||
AddType application/javascript js jsonp
|
||||
AddType application/json json
|
||||
|
||||
# Video
|
||||
AddType video/mp4 mp4 m4v f4v f4p
|
||||
AddType video/ogg ogv
|
||||
AddType video/webm webm
|
||||
AddType video/x-flv flv
|
||||
|
||||
# Web fonts
|
||||
AddType application/font-woff woff
|
||||
AddType application/vnd.ms-fontobject eot
|
||||
|
||||
# Browsers usually ignore the font MIME types and sniff the content,
|
||||
# however, Chrome shows a warning if other MIME types are used for the
|
||||
# following fonts.
|
||||
AddType application/x-font-ttf ttc ttf
|
||||
AddType font/opentype otf
|
||||
|
||||
# Make SVGZ fonts work on iPad:
|
||||
# https://twitter.com/FontSquirrel/status/14855840545
|
||||
AddType image/svg+xml svg svgz
|
||||
AddEncoding gzip svgz
|
||||
|
||||
# Other
|
||||
AddType application/octet-stream safariextz
|
||||
AddType application/x-chrome-extension crx
|
||||
AddType application/x-opera-extension oex
|
||||
AddType application/x-shockwave-flash swf
|
||||
AddType application/x-web-app-manifest+json webapp
|
||||
AddType application/x-xpinstall xpi
|
||||
AddType application/xml atom rdf rss xml
|
||||
AddType image/webp webp
|
||||
AddType image/x-icon ico
|
||||
AddType text/cache-manifest appcache manifest
|
||||
AddType text/vtt vtt
|
||||
AddType text/x-component htc
|
||||
AddType text/x-vcard vcf
|
||||
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | UTF-8 encoding |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
|
||||
AddDefaultCharset utf-8
|
||||
|
||||
# Force UTF-8 for certain file formats.
|
||||
<IfModule mod_mime.c>
|
||||
AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # URL REWRITES #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Rewrite engine |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Turning on the rewrite engine and enabling the `FollowSymLinks` option is
|
||||
# necessary for the following directives to work.
|
||||
|
||||
# If your web host doesn't allow the `FollowSymlinks` option, you may need to
|
||||
# comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
|
||||
# performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
|
||||
|
||||
# Also, some cloud hosting services require `RewriteBase` to be set:
|
||||
# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
Options +FollowSymlinks
|
||||
# Options +SymLinksIfOwnerMatch
|
||||
RewriteEngine On
|
||||
# RewriteBase /
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Suppressing / Forcing the "www." at the beginning of URLs |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# The same content should never be available under two different URLs especially
|
||||
# not with and without "www." at the beginning. This can cause SEO problems
|
||||
# (duplicate content), therefore, you should choose one of the alternatives and
|
||||
# redirect the other one.
|
||||
|
||||
# By default option 1 (no "www.") is activated:
|
||||
# http://no-www.org/faq.php?q=class_b
|
||||
|
||||
# If you'd prefer to use option 2, just comment out all the lines from option 1
|
||||
# and uncomment the ones from option 2.
|
||||
|
||||
# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Option 1: rewrite www.example.com → example.com
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteCond %{HTTPS} !=on
|
||||
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
||||
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
|
||||
</IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Option 2: rewrite example.com → www.example.com
|
||||
|
||||
# Be aware that the following might not be a good idea if you use "real"
|
||||
# subdomains for certain parts of your website.
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteCond %{HTTPS} !=on
|
||||
# RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
|
||||
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # SECURITY #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Content Security Policy (CSP) |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# You can mitigate the risk of cross-site scripting and other content-injection
|
||||
# attacks by setting a Content Security Policy which whitelists trusted sources
|
||||
# of content for your site.
|
||||
|
||||
# The example header below allows ONLY scripts that are loaded from the current
|
||||
# site's origin (no inline scripts, no CDN, etc). This almost certainly won't
|
||||
# work as-is for your site!
|
||||
|
||||
# To get all the details you'll need to craft a reasonable policy for your site,
|
||||
# read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
|
||||
# see the specification: http://w3.org/TR/CSP).
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
|
||||
# <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
|
||||
# Header unset Content-Security-Policy
|
||||
# </FilesMatch>
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | File access |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Block access to directories without a default document.
|
||||
# Usually you should leave this uncommented because you shouldn't allow anyone
|
||||
# to surf through every directory on your server (which may includes rather
|
||||
# private places like the CMS's directories).
|
||||
|
||||
<IfModule mod_autoindex.c>
|
||||
Options -Indexes
|
||||
</IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Block access to hidden files and directories.
|
||||
# This includes directories used by version control systems such as Git and SVN.
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteCond %{SCRIPT_FILENAME} -d [OR]
|
||||
RewriteCond %{SCRIPT_FILENAME} -f
|
||||
RewriteRule "(^|/)\." - [F]
|
||||
</IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Block access to backup and source files.
|
||||
# These files may be left by some text editors and can pose a great security
|
||||
# danger when anyone has access to them.
|
||||
|
||||
<FilesMatch "(^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
Satisfy All
|
||||
</FilesMatch>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Secure Sockets Layer (SSL) |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
|
||||
# prevent `https://www.example.com` when your certificate only allows
|
||||
# `https://secure.example.com`.
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteCond %{SERVER_PORT} !^443
|
||||
# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
|
||||
# </IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Force client-side SSL redirection.
|
||||
|
||||
# If a user types "example.com" in his browser, the above rule will redirect him
|
||||
# to the secure version of the site. That still leaves a window of opportunity
|
||||
# (the initial HTTP connection) for an attacker to downgrade or redirect the
|
||||
# request. The following header ensures that browser will ONLY connect to your
|
||||
# server via HTTPS, regardless of what the users type in the address bar.
|
||||
# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Strict-Transport-Security max-age=16070400;
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Server software information |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Avoid displaying the exact Apache version number, the description of the
|
||||
# generic OS-type and the information about Apache's compiled-in modules.
|
||||
|
||||
# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
|
||||
|
||||
# ServerTokens Prod
|
||||
|
||||
|
||||
# ##############################################################################
|
||||
# # WEB PERFORMANCE #
|
||||
# ##############################################################################
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Compression |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
<IfModule mod_deflate.c>
|
||||
|
||||
# Force compression for mangled headers.
|
||||
# http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
|
||||
<IfModule mod_setenvif.c>
|
||||
<IfModule mod_headers.c>
|
||||
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
|
||||
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
# Compress all output labeled with one of the following MIME-types
|
||||
# (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
|
||||
# and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines
|
||||
# as `AddOutputFilterByType` is still in the core directives).
|
||||
<IfModule mod_filter.c>
|
||||
AddOutputFilterByType DEFLATE application/atom+xml \
|
||||
application/javascript \
|
||||
application/json \
|
||||
application/rss+xml \
|
||||
application/vnd.ms-fontobject \
|
||||
application/x-font-ttf \
|
||||
application/x-web-app-manifest+json \
|
||||
application/xhtml+xml \
|
||||
application/xml \
|
||||
font/opentype \
|
||||
image/svg+xml \
|
||||
image/x-icon \
|
||||
text/css \
|
||||
text/html \
|
||||
text/plain \
|
||||
text/x-component \
|
||||
text/xml
|
||||
</IfModule>
|
||||
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Content transformations |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Prevent some of the mobile network providers from modifying the content of
|
||||
# your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Cache-Control "no-transform"
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | ETag removal |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Since we're sending far-future expires headers (see below), ETags can
|
||||
# be removed: http://developer.yahoo.com/performance/rules.html#etags.
|
||||
|
||||
# `FileETag None` is not enough for every server.
|
||||
<IfModule mod_headers.c>
|
||||
Header unset ETag
|
||||
</IfModule>
|
||||
|
||||
FileETag None
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Expires headers (for better cache control) |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# The following expires headers are set pretty far in the future. If you don't
|
||||
# control versioning with filename-based cache busting, consider lowering the
|
||||
# cache time for resources like CSS and JS to something like 1 week.
|
||||
|
||||
<IfModule mod_expires.c>
|
||||
|
||||
ExpiresActive on
|
||||
ExpiresDefault "access plus 1 month"
|
||||
|
||||
# CSS
|
||||
ExpiresByType text/css "access plus 1 year"
|
||||
|
||||
# Data interchange
|
||||
ExpiresByType application/json "access plus 0 seconds"
|
||||
ExpiresByType application/xml "access plus 0 seconds"
|
||||
ExpiresByType text/xml "access plus 0 seconds"
|
||||
|
||||
# Favicon (cannot be renamed!)
|
||||
ExpiresByType image/x-icon "access plus 1 week"
|
||||
|
||||
# HTML components (HTCs)
|
||||
ExpiresByType text/x-component "access plus 1 month"
|
||||
|
||||
# HTML
|
||||
ExpiresByType text/html "access plus 0 seconds"
|
||||
|
||||
# JavaScript
|
||||
ExpiresByType application/javascript "access plus 1 year"
|
||||
|
||||
# Manifest files
|
||||
ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
|
||||
ExpiresByType text/cache-manifest "access plus 0 seconds"
|
||||
|
||||
# Media
|
||||
ExpiresByType audio/ogg "access plus 1 month"
|
||||
ExpiresByType image/gif "access plus 1 month"
|
||||
ExpiresByType image/jpeg "access plus 1 month"
|
||||
ExpiresByType image/png "access plus 1 month"
|
||||
ExpiresByType video/mp4 "access plus 1 month"
|
||||
ExpiresByType video/ogg "access plus 1 month"
|
||||
ExpiresByType video/webm "access plus 1 month"
|
||||
|
||||
# Web feeds
|
||||
ExpiresByType application/atom+xml "access plus 1 hour"
|
||||
ExpiresByType application/rss+xml "access plus 1 hour"
|
||||
|
||||
# Web fonts
|
||||
ExpiresByType application/font-woff "access plus 1 month"
|
||||
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
|
||||
ExpiresByType application/x-font-ttf "access plus 1 month"
|
||||
ExpiresByType font/opentype "access plus 1 month"
|
||||
ExpiresByType image/svg+xml "access plus 1 month"
|
||||
|
||||
</IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Filename-based cache busting |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# If you're not using a build process to manage your filename version revving,
|
||||
# you might want to consider enabling the following directives to route all
|
||||
# requests such as `/css/style.12345.css` to `/css/style.css`.
|
||||
|
||||
# To understand why this is important and a better idea than `*.css?v231`, read:
|
||||
# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteCond %{REQUEST_FILENAME} !-f
|
||||
# RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | File concatenation |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Allow concatenation from within specific CSS and JS files, e.g.:
|
||||
# Inside of `script.combined.js` you could have
|
||||
# <!--#include file="libs/jquery.js" -->
|
||||
# <!--#include file="plugins/jquery.idletimer.js" -->
|
||||
# and they would be included into this single file.
|
||||
|
||||
# <IfModule mod_include.c>
|
||||
# <FilesMatch "\.combined\.js$">
|
||||
# Options +Includes
|
||||
# AddOutputFilterByType INCLUDES application/javascript application/json
|
||||
# SetOutputFilter INCLUDES
|
||||
# </FilesMatch>
|
||||
# <FilesMatch "\.combined\.css$">
|
||||
# Options +Includes
|
||||
# AddOutputFilterByType INCLUDES text/css
|
||||
# SetOutputFilter INCLUDES
|
||||
# </FilesMatch>
|
||||
# </IfModule>
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# | Persistent connections |
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Allow multiple requests to be sent over the same TCP connection:
|
||||
# http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
|
||||
|
||||
# Enable if you serve a lot of static content but, be aware of the
|
||||
# possible disadvantages!
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Connection Keep-Alive
|
||||
# </IfModule>
|
||||
157
app/404.html
@@ -1,157 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Page Not Found :(</title>
|
||||
<style>
|
||||
::-moz-selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
html {
|
||||
padding: 30px 10px;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
color: #737373;
|
||||
background: #f0f0f0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
input {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 500px;
|
||||
_width: 500px;
|
||||
padding: 30px 20px 50px;
|
||||
border: 1px solid #b3b3b3;
|
||||
border-radius: 4px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff;
|
||||
background: #fcfcfc;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 10px;
|
||||
font-size: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 span {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 1.5em 0 0.5em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0 0 0 40px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 380px;
|
||||
_width: 380px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* google search */
|
||||
|
||||
#goog-fixurl ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#goog-fixurl form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#goog-wm-qt,
|
||||
#goog-wm-sb {
|
||||
border: 1px solid #bbb;
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
vertical-align: top;
|
||||
color: #444;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#goog-wm-qt {
|
||||
width: 220px;
|
||||
height: 20px;
|
||||
padding: 5px;
|
||||
margin: 5px 10px 0 0;
|
||||
box-shadow: inset 0 1px 1px #ccc;
|
||||
}
|
||||
|
||||
#goog-wm-sb {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
margin: 5px 0 0;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
background-color: #f5f5f5;
|
||||
background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1);
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
*overflow: visible;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
#goog-wm-sb:hover,
|
||||
#goog-wm-sb:focus {
|
||||
border-color: #aaa;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
#goog-wm-qt:hover,
|
||||
#goog-wm-qt:focus {
|
||||
border-color: #105cb6;
|
||||
outline: 0;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
input::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Not found <span>:(</span></h1>
|
||||
<p>Sorry, but the page you were trying to view does not exist.</p>
|
||||
<p>It looks like this was the result of either:</p>
|
||||
<ul>
|
||||
<li>a mistyped address</li>
|
||||
<li>an out-of-date link</li>
|
||||
</ul>
|
||||
<script>
|
||||
var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host;
|
||||
</script>
|
||||
<script src="//linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
85
app/app.coffee
Normal 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'
|
||||
115
app/directives.coffee
Normal file
@@ -0,0 +1,115 @@
|
||||
# TODO: split into multiple modules.
|
||||
module.exports = angular.module 'xoWebApp.directives', []
|
||||
|
||||
# This attribute stops the ascendant propagation of a given event.
|
||||
#
|
||||
# The value of this attribute should be the name of the event to
|
||||
# stop.
|
||||
.directive 'stopEvent', ->
|
||||
(_, $element, attrs) ->
|
||||
$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:
|
||||
# - is an `input`;
|
||||
# - has a `ng-click` attribute;
|
||||
# - has a `xo-click` attribute;
|
||||
# - has a `xo-sref` attribute;
|
||||
# - is a link (`a`) and has a `href` attribute.
|
||||
.directive 'xoClick', ($parse) ->
|
||||
($scope, $element, attrs) ->
|
||||
fn = $parse attrs.xoClick
|
||||
current = $element.get(0)
|
||||
current.addEventListener(
|
||||
'click'
|
||||
(event) ->
|
||||
|
||||
# Browse all parent elements of the element the event
|
||||
# happened to and abort if one of them should handle the
|
||||
# event itself.
|
||||
el = event.target
|
||||
while el isnt current
|
||||
{attributes: attrs, tagName: tag} = el
|
||||
|
||||
return if (
|
||||
tag is 'INPUT' or
|
||||
attrs['ng-click']? or
|
||||
attrs['xo-click']? or
|
||||
attrs['xo-sref']? or
|
||||
(tag is 'A') and attrs.href?
|
||||
)
|
||||
|
||||
el = el.parentNode
|
||||
|
||||
# Stop the propagation.
|
||||
event.stopPropagation()
|
||||
|
||||
# Apply the `xo-click` attribute.
|
||||
$scope.$apply ->
|
||||
fn $scope, {$event: event}
|
||||
true
|
||||
)
|
||||
|
||||
# TODO: create a directive which allows a link on any element.
|
||||
|
||||
# TODO: Mutualize code with `xoClick`.
|
||||
.directive 'xoSref', ($state, $window) ->
|
||||
($scope, $element, attrs) ->
|
||||
current = $element.get(0)
|
||||
current.addEventListener(
|
||||
'mouseup'
|
||||
(event) ->
|
||||
|
||||
{which: button} = event
|
||||
return unless button is 1 or button is 2
|
||||
|
||||
# Browse all parent elements of the element the event
|
||||
# happened to and abort if one of them should handle the
|
||||
# event itself.
|
||||
el = event.target
|
||||
while el isnt current
|
||||
{attributes: attrs_, tagName: tag} = el
|
||||
|
||||
return if (
|
||||
tag is 'INPUT' or
|
||||
attrs_['ng-click']? or
|
||||
attrs_['xo-click']? or
|
||||
attrs_['xo-sref']? or
|
||||
(tag is 'A') and attrs_.href?
|
||||
)
|
||||
|
||||
el = el.parentNode
|
||||
|
||||
# Stop the propagation.
|
||||
event.stopPropagation()
|
||||
|
||||
# Extracts the state and its parameters for the `xo-sref`
|
||||
# attribute.
|
||||
match = attrs.xoSref.match /^([^(]+)\s*(?:\((.*)\))?$/
|
||||
throw new Error 'invalid SREF' unless match
|
||||
state = match[1]
|
||||
params = if match[2] then $scope.$eval match[2] else {}
|
||||
|
||||
# Ctrl modifier or middle-button.
|
||||
if event.ctrlKey or button is 2
|
||||
url = $state.href state, params
|
||||
$window.open url
|
||||
else
|
||||
$state.go state, params
|
||||
true
|
||||
)
|
||||
|
||||
.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
|
||||
)
|
||||
@@ -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
|
After Width: | Height: | Size: 20 KiB |
BIN
app/images/logo_small.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
140
app/index.html
@@ -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 ui-view></div>
|
||||
|
||||
<script src="bower_components/jquery/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/modules/indeterminate/indeterminate.js"></script>
|
||||
<script src="bower_components/angular-ui-utils/modules/route/route.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>
|
||||
<!-- 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
@@ -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")
|
||||
20
app/modules/about/index.js
Normal 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'),
|
||||
});
|
||||
})
|
||||
;
|
||||
46
app/modules/about/view.jade
Normal 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
|
||||
175
app/modules/console/index.coffee
Normal 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
|
||||
}
|
||||
49
app/modules/console/view.jade
Normal file
@@ -0,0 +1,49 @@
|
||||
.container: .panel.panel-default
|
||||
|
||||
//- Title
|
||||
.panel-heading.text-center
|
||||
h2
|
||||
i.xo-icon-console(class = 'xo-color-{{VM.power_state | lowercase}}')
|
||||
|
|
||||
a(
|
||||
class = 'xo-color-{{VM.power_state | lowercase}}'
|
||||
ui-sref = 'VMs_view({id: VM.UUID})'
|
||||
) {{VM.name_label}}
|
||||
|
||||
.list-group
|
||||
|
||||
//- Toolbar
|
||||
.list-group-item: .row.text-center
|
||||
.col-sm-6: .input-group
|
||||
select.form-control(
|
||||
ng-model = 'mountedIso'
|
||||
ng-change = 'insert(mountedIso)'
|
||||
ng-options = 'VDI.UUID as VDI.name_label group by (VDI.$SR | resolve).name_label for VDI in VDIs | orderBy:natural("name_label")'
|
||||
)
|
||||
.input-group-btn
|
||||
button.btn.btn-default(
|
||||
ng-click = 'eject()'
|
||||
ng-disabled = '!mountedIso'
|
||||
)
|
||||
i.fa.fa-eject
|
||||
.col-sm-3: button.btn.btn-default(
|
||||
ng-click = 'vncRemote.sendCtrlAltDel()'
|
||||
)
|
||||
i.fa.fa-keyboard-o
|
||||
|
|
||||
| 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(
|
||||
url = '{{consoleUrl}}'
|
||||
remote-control = 'vncRemote'
|
||||
)
|
||||
35
app/modules/delete-vms/index.coffee
Normal 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
|
||||
24
app/modules/delete-vms/view.jade
Normal 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
|
||||
43
app/modules/generic-modal/index.js
Normal 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;
|
||||
}
|
||||
};
|
||||
})
|
||||
;
|
||||
11
app/modules/generic-modal/view.jade
Normal 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}}
|
||||
@@ -1,7 +1,19 @@
|
||||
'use strict'
|
||||
require 'angular'
|
||||
require 'angular-ui-router'
|
||||
|
||||
angular.module('xoWebApp')
|
||||
.controller 'MainCtrl', ($scope, $modal, xo) ->
|
||||
#=====================================================================
|
||||
|
||||
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
|
||||
@@ -19,10 +31,37 @@ angular.module('xoWebApp')
|
||||
)
|
||||
$scope.pool_disconnect = xo.pool.disconnect
|
||||
$scope.new_sr = xo.pool.new_sr
|
||||
$scope.pool_removeHost = xo.host.detach
|
||||
$scope.rebootHost = xo.host.restart
|
||||
$scope.restartToolStack = xo.host.restartToolStack
|
||||
$scope.shutdownHost = xo.host.stop
|
||||
|
||||
$scope.pool_addHost = (id) ->
|
||||
xo.host.attach id
|
||||
|
||||
$scope.pool_removeHost = (id) ->
|
||||
modal.confirm({
|
||||
title: 'Remove host from pool'
|
||||
message: 'Are you sure you want to detach this host from its pool? It will be automatically rebooted'
|
||||
}).then ->
|
||||
xo.host.detach id
|
||||
|
||||
$scope.rebootHost = (id) ->
|
||||
modal.confirm({
|
||||
title: 'Reboot host'
|
||||
message: 'Are you sure you want to reboot this host? It will be disabled then rebooted'
|
||||
}).then ->
|
||||
xo.host.restart id
|
||||
|
||||
$scope.restartToolStack = (id) ->
|
||||
modal.confirm({
|
||||
title: 'Restart XAPI'
|
||||
message: 'Are you sure you want to restart the XAPI toolstack?'
|
||||
}).then ->
|
||||
xo.host.restartToolStack id
|
||||
|
||||
$scope.shutdownHost = (id) ->
|
||||
modal.confirm({
|
||||
title: 'Shutdown host'
|
||||
message: 'Are you sure you want to shutdown this host?'
|
||||
}).then ->
|
||||
xo.host.stop id
|
||||
|
||||
$scope.startVM = xo.vm.start
|
||||
$scope.stopVM = xo.vm.stop
|
||||
@@ -30,7 +69,11 @@ angular.module('xoWebApp')
|
||||
$scope.rebootVM = xo.vm.restart
|
||||
$scope.force_rebootVM = (id) -> xo.vm.restart id, true
|
||||
$scope.migrateVM = xo.vm.migrate
|
||||
$scope.createVMSnapshot = xo.vm.createSnapshot
|
||||
$scope.snapshotVM = (id) ->
|
||||
vm = xo.get (id)
|
||||
date = dateFilter Date.now(), 'yyyy-MM-ddTHH:mmZ'
|
||||
snapshot_name = "#{vm.name_label}_#{date}"
|
||||
xo.vm.createSnapshot id, snapshot_name
|
||||
# check if there is any operation pending on a VM
|
||||
$scope.isVMWorking = (VM) ->
|
||||
return true for _ of VM.current_operations
|
||||
@@ -43,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 ->
|
||||
@@ -119,11 +160,3 @@ angular.module('xoWebApp')
|
||||
# Unselects all VMs.
|
||||
$scope.selectVMs false
|
||||
|
||||
$scope.osType = (osName) ->
|
||||
switch osName
|
||||
when 'debian','ubuntu','centos','suse','redhat'
|
||||
'linux'
|
||||
when 'windows'
|
||||
'windows'
|
||||
else
|
||||
'other'
|
||||
358
app/modules/home/view.jade
Normal 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
|
||||
|
|
||||
i.xo-icon-pool
|
||||
|
|
||||
|
|
||||
i(tooltip="{{hosts.length}} hosts")
|
||||
i.small {{hosts.length}}x
|
||||
|
|
||||
i.xo-icon-host
|
||||
|
|
||||
|
|
||||
i(tooltip="{{xo.$running_VMs.length}} of {{VMs.length}} VMs running")
|
||||
i.small {{xo.$running_VMs.length}}x
|
||||
|
|
||||
i.xo-icon-vm
|
||||
|
|
||||
|
|
||||
i(tooltip="{{xo.$vCPUs}} vCPUs used of {{xo.$CPUs}} CPUs")
|
||||
i.small {{xo.$vCPUs}}x
|
||||
|
|
||||
i.xo-icon-cpu
|
||||
|
|
||||
|
|
||||
i(tooltip="{{xo.$memory.usage | bytesToSize}} RAM allocated of {{xo.$memory.size | bytesToSize}}")
|
||||
i.small {{xo.$memory.usage | bytesToSize}}
|
||||
|
|
||||
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")
|
||||
|
|
||||
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")
|
||||
|
|
||||
.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
|
||||
|
|
||||
.btn-group.dropdown
|
||||
button.btn.navbar-btn.btn-default.dropdown-toggle.inversed(tooltip="Migrate VM", type="button")
|
||||
i.fa.fa-share
|
||||
|
|
||||
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}}
|
||||
|
|
||||
.btn-group.dropdown
|
||||
button.btn.navbar-btn.btn-default.dropdown-toggle.inversed(type="button")
|
||||
| More
|
||||
|
|
||||
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
|
||||
|
|
||||
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
|
||||
|
|
||||
i.xo-icon-host
|
||||
|
|
||||
|
|
||||
i(tooltip="{{pool.$running_VMs.length}} of {{pool.$VMs.length}} VMs running")
|
||||
i.small {{pool.$running_VMs.length}}x
|
||||
|
|
||||
i.xo-icon-vm
|
||||
li(ng-if="pool.master", ng-init="master = (pool.master | resolve)")
|
||||
| Master:
|
||||
|
|
||||
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
|
||||
|
|
||||
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}}")
|
||||
|
|
||||
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}}")
|
||||
|
|
||||
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.
|
||||
90
app/modules/host/index.coffee
Normal file
@@ -0,0 +1,90 @@
|
||||
require 'angular'
|
||||
|
||||
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
|
||||
->
|
||||
host = $scope.host = xo.get $stateParams.id
|
||||
return unless host?
|
||||
|
||||
$scope.pool = xo.get host.poolRef
|
||||
|
||||
SRsToPBDs = $scope.SRsToPBDs = Object.create null
|
||||
for PBD in host.$PBDs
|
||||
PBD = xo.get PBD
|
||||
|
||||
# If this PBD is unknown, just skips it.
|
||||
continue unless PBD
|
||||
|
||||
SRsToPBDs[PBD.SR] = PBD
|
||||
)
|
||||
|
||||
$scope.removeMessage = xo.message.delete
|
||||
|
||||
$scope.removeTask = xo.task.delete
|
||||
|
||||
$scope.disconnectPBD = xo.pbd.disconnect
|
||||
$scope.removePBD = xo.pbd.delete
|
||||
|
||||
$scope.new_sr = xo.pool.new_sr
|
||||
|
||||
$scope.pool_addHost = (id) ->
|
||||
xo.host.attach id
|
||||
|
||||
$scope.pool_removeHost = (id) ->
|
||||
modal.confirm({
|
||||
title: 'Remove host from pool'
|
||||
message: 'Are you sure you want to detach this host from its pool? It will be automatically rebooted'
|
||||
}).then ->
|
||||
xo.host.detach id
|
||||
$scope.rebootHost = (id) ->
|
||||
modal.confirm({
|
||||
title: 'Reboot host'
|
||||
message: 'Are you sure you want to reboot this host? It will be disabled then rebooted'
|
||||
}).then ->
|
||||
xo.host.restart id
|
||||
|
||||
$scope.restartToolStack = (id) ->
|
||||
modal.confirm({
|
||||
title: 'Restart XAPI'
|
||||
message: 'Are you sure you want to restart the XAPI toolstack?'
|
||||
}).then ->
|
||||
xo.host.restartToolStack id
|
||||
|
||||
$scope.shutdownHost = (id) ->
|
||||
modal.confirm({
|
||||
title: 'Shutdown host'
|
||||
message: 'Are you sure you want to shutdown this host?'
|
||||
}).then ->
|
||||
xo.host.stop id
|
||||
$scope.saveHost = ($data) ->
|
||||
{host} = $scope
|
||||
{name_label, name_description, enabled} = $data
|
||||
|
||||
$data = {
|
||||
id: host.UUID
|
||||
}
|
||||
if name_label isnt host.name_label
|
||||
$data.name_label = name_label
|
||||
if name_description isnt host.name_description
|
||||
$data.name_description = name_description
|
||||
if enabled isnt host.enabled
|
||||
$data.enabled = host.enabled
|
||||
|
||||
xoApi.call 'host.set', $data
|
||||
|
||||
$scope.deleteLog = (id) ->
|
||||
console.log "Remove log #{id}"
|
||||
xo.log.delete id
|
||||
267
app/modules/host/view.jade
Normal 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
|
||||
|
|
||||
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
|
||||
|
|
||||
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
|
||||
|
|
||||
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
@@ -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
@@ -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.
|
||||
33
app/modules/login/index.coffee
Normal 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
|
||||
50
app/modules/login/view.jade
Normal 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')
|
||||
33
app/modules/navbar/index.coffee
Normal 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
|
||||
}
|
||||
97
app/modules/navbar/view.jade
Normal 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.
|
||||
21
app/modules/new-sr/index.coffee
Normal 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
|
||||
)
|
||||
|
||||
44
app/modules/new-sr/view.jade
Normal 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
|
||||
@@ -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
|
||||
@@ -105,9 +116,15 @@ angular.module('xoWebApp')
|
||||
$scope.$watch 'template', (template) ->
|
||||
return unless template
|
||||
|
||||
{install_methods} = template.template_info
|
||||
availableMethods = $scope.availableMethods = Object.create null
|
||||
for method in template.template_info.install_methods
|
||||
for method in install_methods
|
||||
availableMethods[method] = true
|
||||
if install_methods.length is 1 # FIXME: does not work with network.
|
||||
$scope.installation_method = install_methods[0]
|
||||
else
|
||||
delete $scope.installation_method
|
||||
|
||||
|
||||
VDIs = $scope.VDIs = angular.copy template.template_info.disks
|
||||
for VDI in VDIs
|
||||
@@ -149,20 +166,25 @@ angular.module('xoWebApp')
|
||||
delete VIF.id
|
||||
|
||||
# Removes the MAC address if empty.
|
||||
delete VIF.MAC unless VIF.MAC
|
||||
if 'MAC' of VIF
|
||||
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
|
||||
197
app/modules/new-vm/view.jade
Normal 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  
|
||||
//- 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  
|
||||
//- 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
|
||||
@@ -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
|
||||
@@ -20,3 +31,7 @@ angular.module('xoWebApp')
|
||||
$data.name_description = name_description
|
||||
|
||||
xoApi.call 'pool.set', $data
|
||||
|
||||
$scope.deleteLog = (id) ->
|
||||
console.log "Remove log #{id}"
|
||||
xo.log.delete id
|
||||
137
app/modules/pool/view.jade
Normal 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
|
||||
|
|
||||
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
|
||||
@@ -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
|
||||
78
app/modules/settings/view.jade
Normal 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  
|
||||
p.text-center
|
||||
button.btn.btn-primary(type="submit")
|
||||
i.fa.fa-save
|
||||
| Save
|
||||
|
|
||||
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  
|
||||
p.text-center
|
||||
button.btn.btn-primary(type="submit")
|
||||
i.fa.fa-save
|
||||
| Save
|
||||
|
|
||||
button.btn.btn-success(type="button", ng-click="addUser()")
|
||||
i.fa.fa-plus
|
||||
@@ -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
@@ -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
|
||||
|
|
||||
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
|
||||
|
|
||||
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.
|
||||
172
app/modules/vm/index.coffee
Normal file
@@ -0,0 +1,172 @@
|
||||
require 'angular'
|
||||
|
||||
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
|
||||
dateFilter
|
||||
) ->
|
||||
{get} = xo
|
||||
$scope.$watch(
|
||||
-> xo.revision
|
||||
->
|
||||
VM = $scope.VM = get $stateParams.id
|
||||
|
||||
{byTypes} = xo
|
||||
$scope.hosts = byTypes.host
|
||||
|
||||
return unless VM?
|
||||
|
||||
# For the edition of this VM.
|
||||
$scope.memorySize = bytesToSizeFilter VM.memory.size
|
||||
|
||||
# build VDI list of this VM
|
||||
$scope.VDIs = []
|
||||
for VBD in VM.$VBDs
|
||||
VDI = get (get VBD)?.VDI
|
||||
$scope.VDIs.push VDI if VDI?
|
||||
)
|
||||
|
||||
# AngularUI select2 component settings
|
||||
$scope.select2Options =
|
||||
'multiple': true
|
||||
'simple_tags': true
|
||||
'tags': []
|
||||
|
||||
$scope.startVM = xo.vm.start
|
||||
$scope.stopVM = xo.vm.stop
|
||||
$scope.force_stopVM = (id) -> xo.vm.stop id, true
|
||||
$scope.rebootVM = xo.vm.restart
|
||||
$scope.force_rebootVM = (id) -> xo.vm.restart id, true
|
||||
$scope.migrateVM = xo.vm.migrate
|
||||
$scope.destroyVM = (id) ->
|
||||
modal.confirm
|
||||
title: 'VM deletion'
|
||||
message: 'Are you sure you want to delete this VM? (including its disks)'
|
||||
.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)
|
||||
|
||||
result = {
|
||||
id: snapshot.UUID
|
||||
name_label: $data
|
||||
}
|
||||
|
||||
if $data isnt snapshot.name_label
|
||||
console.log "new name recorded"
|
||||
result.name_label = $data
|
||||
|
||||
xoApi.call 'vm.set', result
|
||||
|
||||
$scope.saveVM = ($data) ->
|
||||
{VM} = $scope
|
||||
{CPUs, memory, name_label, name_description} = $data
|
||||
|
||||
$data = {
|
||||
id: VM.UUID
|
||||
}
|
||||
if memory isnt $scope.memorySize and (memory = sizeToBytesFilter memory)
|
||||
$data.memory = memory
|
||||
$scope.memorySize = bytesToSizeFilter memory
|
||||
if CPUs isnt VM.CPUs.number
|
||||
$data.CPUs = +CPUs
|
||||
if name_label isnt VM.name_label
|
||||
$data.name_label = name_label
|
||||
if name_description isnt VM.name_description
|
||||
$data.name_description = name_description
|
||||
|
||||
xoApi.call 'vm.set', $data
|
||||
|
||||
# VDI
|
||||
selected = $scope.selectedVDIs = {}
|
||||
|
||||
$scope.newVDIs = []
|
||||
|
||||
$scope.addVDI = ->
|
||||
$scope.newVDIs.push {
|
||||
# Fake (unique) identifier needed by Angular.JS
|
||||
id: Math.random()
|
||||
}
|
||||
## TODO: Use Angular XEditable Row
|
||||
|
||||
$scope.deleteVDI = (UUID) ->
|
||||
modal.confirm({
|
||||
title: 'Disk deletion'
|
||||
message: 'Are you sure you want to delete this disk? This operation is irreversible'
|
||||
}).then ->
|
||||
xoApi.call 'vdi.delete', {id: UUID}
|
||||
|
||||
$scope.disconnectVBD = (UUID) ->
|
||||
console.log "Disconnect VBD #{UUID}"
|
||||
|
||||
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({
|
||||
title: 'Snapshot deletion'
|
||||
message: 'Are you sure you want to delete this snapshot? (including its disks)'
|
||||
}).then ->
|
||||
# FIXME: provides a way to not delete its disks.
|
||||
xo.vm.delete id, true
|
||||
|
||||
$scope.deleteLog = (id) ->
|
||||
console.log "Remove log #{id}"
|
||||
xo.log.delete id
|
||||
|
||||
$scope.revertSnapshot = (id) ->
|
||||
console.log "Revert snapshot to #{id}"
|
||||
modal.confirm({
|
||||
title: 'Revert to snapshot'
|
||||
message: 'Are you sure you want to revert your VM to this snapshot? The VM will be halted and this operation is irreversible'
|
||||
}).then ->
|
||||
xo.vm.revert id
|
||||
|
||||
$scope.osType = (osName) ->
|
||||
switch osName
|
||||
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
@@ -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
|
||||
|
|
||||
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
|
||||
|
|
||||
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
|
||||
|
|
||||
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
|
||||
|
|
||||
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
|
||||
@@ -1,3 +0,0 @@
|
||||
# robotstxt.org
|
||||
|
||||
User-agent: *
|
||||
@@ -1,83 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
angular.module('xoWebApp', [
|
||||
'ngCookies'
|
||||
|
||||
'ui.bootstrap'
|
||||
'ui.indeterminate'
|
||||
'ui.route'
|
||||
'ui.router'
|
||||
'ui.select2'
|
||||
|
||||
'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'
|
||||
@@ -1,55 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
angular.module('xoWebApp')
|
||||
.controller 'ConsoleCtrl', ($scope, $stateParams, 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
|
||||
->
|
||||
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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -1,44 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
angular.module('xoWebApp')
|
||||
.controller 'HostCtrl', ($scope, $stateParams, xoApi, xo) ->
|
||||
$scope.$watch(
|
||||
-> xo.revision
|
||||
->
|
||||
host = $scope.host = xo.get $stateParams.id
|
||||
return unless host?
|
||||
|
||||
$scope.pool = xo.get host.poolRef
|
||||
|
||||
SRsToPBDs = $scope.SRsToPBDs = Object.create null
|
||||
for PBD in host.$PBDs
|
||||
PBD = xo.get PBD
|
||||
|
||||
# If this PBD is unknown, just skips it.
|
||||
continue unless PBD
|
||||
|
||||
SRsToPBDs[PBD.SR] = PBD
|
||||
)
|
||||
|
||||
$scope.removeMessage = xo.message.delete
|
||||
|
||||
$scope.removeTask = xo.task.delete
|
||||
|
||||
$scope.disconnectPBD = xo.pbd.disconnect
|
||||
$scope.removePBD = xo.pbd.delete
|
||||
|
||||
$scope.saveHost = ($data) ->
|
||||
{host} = $scope
|
||||
{name_label, name_description, enabled} = $data
|
||||
|
||||
$data = {
|
||||
id: host.UUID
|
||||
}
|
||||
if name_label isnt host.name_label
|
||||
$data.name_label = name_label
|
||||
if name_description isnt host.name_description
|
||||
$data.name_description = name_description
|
||||
if enabled isnt host.enabled
|
||||
$data.enabled = host.enabled
|
||||
|
||||
xoApi.call 'host.set', $data
|
||||
@@ -1,5 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
angular.module('xoWebApp')
|
||||
.controller 'ListCtrl', ($scope, xo) ->
|
||||
$scope.byTypes = xo.byTypes
|
||||
@@ -1,16 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
angular.module('xoWebApp')
|
||||
.controller 'NavBarCtrl', ($scope, $location, xoApi) ->
|
||||
$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'
|
||||
@@ -1,10 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
angular.module('xoWebApp')
|
||||
.controller 'NewSrCtrl', ($scope, $stateParams, xo) ->
|
||||
$scope.$watch(
|
||||
-> xo.revision
|
||||
->
|
||||
$scope.container = xo.get $stateParams.container
|
||||
)
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
angular.module('xoWebApp')
|
||||
.controller 'VmCtrl', (
|
||||
$scope, $stateParams
|
||||
xoApi, xo
|
||||
sizeToBytesFilter, bytesToSizeFilter
|
||||
modal
|
||||
) ->
|
||||
{get} = xo
|
||||
$scope.$watch(
|
||||
-> xo.revision
|
||||
->
|
||||
VM = $scope.VM = get $stateParams.id
|
||||
|
||||
return unless VM?
|
||||
|
||||
# For the edition of this VM.
|
||||
$scope.memorySize = bytesToSizeFilter VM.memory.size
|
||||
|
||||
# build VDI list of this VM
|
||||
$scope.VDIs = []
|
||||
for VBD in VM.$VBDs
|
||||
VDI = get (get VBD)?.VDI
|
||||
$scope.VDIs.push VDI if VDI?
|
||||
)
|
||||
|
||||
# AngularUI select2 component settings
|
||||
$scope.select2Options =
|
||||
'multiple': true
|
||||
'simple_tags': true
|
||||
'tags': []
|
||||
|
||||
$scope.startVM = xo.vm.start
|
||||
$scope.stopVM = xo.vm.stop
|
||||
$scope.force_stopVM = (id) -> xo.vm.stop id, true
|
||||
$scope.rebootVM = xo.vm.restart
|
||||
$scope.force_rebootVM = (id) -> xo.vm.restart id, true
|
||||
$scope.destroyVM = (id) ->
|
||||
modal.confirm({
|
||||
title: 'VM deletion'
|
||||
message: 'Are you sure you want to delete this VM? (including its disks)'
|
||||
}).then ->
|
||||
# FIXME: provides a way to not delete its disks.
|
||||
xo.vm.delete id, true
|
||||
|
||||
$scope.saveVM = ($data) ->
|
||||
{VM} = $scope
|
||||
{CPUs, memory, name_label, name_description} = $data
|
||||
|
||||
$data = {
|
||||
id: VM.UUID
|
||||
}
|
||||
if memory isnt $scope.memorySize and (memory = sizeToBytesFilter memory)
|
||||
$data.memory = memory
|
||||
$scope.memorySize = bytesToSizeFilter memory
|
||||
if CPUs isnt VM.CPUs.number
|
||||
$data.CPUs = +CPUs
|
||||
if name_label isnt VM.name_label
|
||||
$data.name_label = name_label
|
||||
if name_description isnt VM.name_description
|
||||
$data.name_description = name_description
|
||||
|
||||
xoApi.call 'vm.set', $data
|
||||
|
||||
# VDI
|
||||
selected = $scope.selectedVDIs = {}
|
||||
|
||||
$scope.newVDIs = []
|
||||
|
||||
$scope.addVDI = ->
|
||||
$scope.newVDIs.push {
|
||||
# Fake (unique) identifier needed by Angular.JS
|
||||
id: Math.random()
|
||||
}
|
||||
## TODO: Use Angular XEditable Row
|
||||
|
||||
$scope.deleteVDI = (UUID) ->
|
||||
console.log "Delete VDI #{UUID}"
|
||||
|
||||
xoApi.call 'vdi.delete', {id: UUID}
|
||||
|
||||
$scope.disconnectVBD = (UUID) ->
|
||||
console.log "Disconnect VBD #{UUID}"
|
||||
|
||||
xoApi.call 'vbd.disconnect', {id: UUID}
|
||||
|
||||
|
||||
$scope.osType = (osName) ->
|
||||
switch osName
|
||||
when 'debian','ubuntu','centos','suse','redhat'
|
||||
'linux'
|
||||
when 'windows'
|
||||
'windows'
|
||||
else
|
||||
'other'
|
||||
@@ -1,193 +0,0 @@
|
||||
angular.module('xoWebApp')
|
||||
|
||||
# This attribute stops the ascendant propagation of a given event.
|
||||
#
|
||||
# The value of this attribute should be the name of the event to
|
||||
# stop.
|
||||
.directive 'stopEvent', ->
|
||||
(_, $element, attrs) ->
|
||||
$element.on attrs.stopEvent, ($event) -> $event.stopPropagation()
|
||||
|
||||
# This attribute works similarly to `ng-click` but do not handle the
|
||||
# event if the clicked element:
|
||||
# - is an `input`;
|
||||
# - has a `ng-click` attribute;
|
||||
# - has a `xo-click` attribute;
|
||||
# - has a `xo-sref` attribute;
|
||||
# - is a link (`a`) and has a `href` attribute.
|
||||
.directive 'xoClick', ($parse) ->
|
||||
($scope, $element, attrs) ->
|
||||
fn = $parse attrs.xoClick
|
||||
current = $element.get(0)
|
||||
current.addEventListener(
|
||||
'click'
|
||||
(event) ->
|
||||
|
||||
# Browse all parent elements of the element the event
|
||||
# happened to and abort if one of them should handle the
|
||||
# event itself.
|
||||
el = event.target
|
||||
while el isnt current
|
||||
{attributes: attrs, tagName: tag} = el
|
||||
|
||||
return if (
|
||||
tag is 'INPUT' or
|
||||
attrs['ng-click']? or
|
||||
attrs['xo-click']? or
|
||||
attrs['xo-sref']? or
|
||||
(tag is 'A') and attrs.href?
|
||||
)
|
||||
|
||||
el = el.parentNode
|
||||
|
||||
# Stop the propagation.
|
||||
event.stopPropagation()
|
||||
|
||||
# Apply the `xo-click` attribute.
|
||||
$scope.$apply ->
|
||||
fn $scope, {$event: event}
|
||||
true
|
||||
)
|
||||
|
||||
# TODO: create a directive which allows a link on any element.
|
||||
|
||||
# TODO: Mutualize code with `xoClick`.
|
||||
.directive 'xoSref', ($state) ->
|
||||
($scope, $element, attrs) ->
|
||||
current = $element.get(0)
|
||||
current.addEventListener(
|
||||
'click'
|
||||
(event) ->
|
||||
|
||||
# Browse all parent elements of the element the event
|
||||
# happened to and abort if one of them should handle the
|
||||
# event itself.
|
||||
el = event.target
|
||||
while el isnt current
|
||||
{attributes: attrs_, tagName: tag} = el
|
||||
|
||||
return if (
|
||||
tag is 'INPUT' or
|
||||
attrs_['ng-click']? or
|
||||
attrs_['xo-click']? or
|
||||
attrs_['xo-sref']? or
|
||||
(tag is 'A') and attrs_.href?
|
||||
)
|
||||
|
||||
el = el.parentNode
|
||||
|
||||
# Stop the propagation.
|
||||
event.stopPropagation()
|
||||
|
||||
# Extracts the state and its parameters for the `xo-sref`
|
||||
# attribute.
|
||||
match = attrs.xoSref.match /^([^(]+)\s*(?:\((.*)\))?$/
|
||||
throw new Error 'invalid SREF' unless match
|
||||
state = match[1]
|
||||
params = if match[2] then $scope.$eval match[2] else {}
|
||||
|
||||
# Go to this state.
|
||||
$state.go state, params
|
||||
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]
|
||||
|
||||
# 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
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
angular.module('xoWebApp')
|
||||
|
||||
.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) ->
|
||||
if angular.isString options
|
||||
options = { message: options }
|
||||
else
|
||||
throw new Error 'missing message' unless options.message
|
||||
|
||||
toaster.pop(
|
||||
level
|
||||
options.title ? 'Xen-Orchestra'
|
||||
options.message
|
||||
)
|
||||
|
||||
{
|
||||
warning: notifier 'warning'
|
||||
error: notifier 'error'
|
||||
info: notifier 'info'
|
||||
|
||||
# TODO: It is probably a bad design to have notification for
|
||||
# successful operations.
|
||||
# success: notifier 'success'
|
||||
}
|
||||
|
||||
.service 'xoApi', ($cookieStore, $location, $q, $timeout, notify) ->
|
||||
url = do ->
|
||||
# Note: The path is ignored, the WebSocket must be relative to
|
||||
# root.
|
||||
protocol = if $location.protocol() is 'https' then 'wss:' else 'ws:'
|
||||
host = $location.host()
|
||||
port = $location.port()
|
||||
|
||||
"#{protocol}//#{host}:#{port}/api/"
|
||||
|
||||
# Identifier of the next request.
|
||||
nextId = 0
|
||||
|
||||
# Delay in seconds to the next reconnection attempt, `null` when
|
||||
# no reconnection are attempted (currently connected).
|
||||
delay = null
|
||||
|
||||
# Promises linked to the requests.
|
||||
deferreds = {}
|
||||
|
||||
# When the socket is closed, request are enqueued.
|
||||
queue = []
|
||||
|
||||
# Variable which will contains the webSocket to use.
|
||||
socket = null
|
||||
|
||||
# Currently logged in user.
|
||||
user = null
|
||||
|
||||
# Function used to send requests when the socket is opened.
|
||||
send = (method, params, deferred) ->
|
||||
id = nextId++
|
||||
socket.send JSON.stringify(
|
||||
jsonrpc: '2.0'
|
||||
id: id
|
||||
method: method
|
||||
params: params or []
|
||||
)
|
||||
deferreds[id] = deferred or $q.defer()
|
||||
deferreds[id].promise
|
||||
|
||||
# Function used to enqueue requests when the socket is closed.
|
||||
enqueue = (method, params) ->
|
||||
deferred = $q.defer()
|
||||
queue.push [method, params, deferred]
|
||||
deferred.promise
|
||||
|
||||
# This variable contains the function which be called (initially
|
||||
# it will points to `enqueue`).
|
||||
call = enqueue
|
||||
|
||||
connect = ->
|
||||
# Creation of the WebSocket.
|
||||
socket = new WebSocket url
|
||||
|
||||
# When the WebSocket opens, send any requests enqueued.
|
||||
socket.addEventListener 'open', ->
|
||||
notify.info 'Connected to XO-Server'
|
||||
|
||||
delay = null
|
||||
|
||||
# If there is a token tries to sign in.
|
||||
if (token = $cookieStore.get 'token')
|
||||
send(
|
||||
'session.signInWithToken'
|
||||
{token}
|
||||
).then (loggedInUser) ->
|
||||
user = loggedInUser
|
||||
.catch ->
|
||||
# The authentication failed, removes the token.
|
||||
$cookieStore.remove 'token'
|
||||
|
||||
# Sends queued requests.
|
||||
send entry... while (entry = queue.shift())?
|
||||
|
||||
# New requests are sent directly.
|
||||
call = send
|
||||
|
||||
socket.addEventListener 'close', ->
|
||||
call = enqueue
|
||||
user = null
|
||||
delay ?= 4 # Initial delay.
|
||||
|
||||
notify.error """
|
||||
The connection with XO-Server has been lost.
|
||||
|
||||
Attempt to reconnect in #{delay} seconds.
|
||||
"""
|
||||
|
||||
# Tries to reconnect after a small (increasing) delay.
|
||||
$timeout connect, delay * 1e3
|
||||
|
||||
# FIXME: Use Fibonacci progression instead of exponential.
|
||||
delay *= 2
|
||||
|
||||
# When a message is received, we call the corresponding
|
||||
# deferred (if any).
|
||||
socket.addEventListener 'message', (event) ->
|
||||
response = JSON.parse event.data
|
||||
id = response.id
|
||||
deferred = deferreds[id]
|
||||
delete deferreds[id]
|
||||
|
||||
error = response.error
|
||||
unless error is undefined
|
||||
deferred.reject error
|
||||
return
|
||||
|
||||
result = response.result
|
||||
if result is undefined
|
||||
console.error 'invalid message received', response
|
||||
deferred.reject {
|
||||
message: message
|
||||
object: response
|
||||
}
|
||||
return
|
||||
|
||||
deferred.resolve result
|
||||
|
||||
connect()
|
||||
|
||||
xoApi = {
|
||||
call: (method, params) ->
|
||||
call method, params
|
||||
|
||||
logIn: (email, password, persist) ->
|
||||
call(
|
||||
'session.signInWithPassword'
|
||||
{email, password}
|
||||
).then (loggedInUser) ->
|
||||
user = loggedInUser
|
||||
|
||||
if persist
|
||||
call('token.create').then (token) ->
|
||||
$cookieStore.put 'token', token
|
||||
.catch (error) ->
|
||||
notify.warning {
|
||||
title: 'Authentication failed'
|
||||
message: error.message
|
||||
}
|
||||
|
||||
logOut: ->
|
||||
send 'session.signOut' if socket
|
||||
user = null
|
||||
$cookieStore.remove 'token'
|
||||
}
|
||||
|
||||
Object.defineProperty xoApi, 'user', {
|
||||
get: -> user
|
||||
}
|
||||
|
||||
# This service provides access to XO objects.
|
||||
#
|
||||
# Deprecated: use the service `xo` instead.
|
||||
.service 'xoObjects', ($timeout, xoApi) ->
|
||||
byRefs = Object.create null
|
||||
byUUIDs = Object.create null
|
||||
{
|
||||
all
|
||||
byTypes
|
||||
} = xoObjects = {
|
||||
revision: 0
|
||||
all: []
|
||||
byTypes: Object.create null
|
||||
|
||||
get: (key) ->
|
||||
if angular.isArray key
|
||||
item for item in key when (item = byUUIDs[item] ? byRefs[item])
|
||||
else
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
.service 'xo', (xoObjects, xoApi, notify) ->
|
||||
action = (name, method, options) ->
|
||||
unless method
|
||||
return ->
|
||||
notify.info {
|
||||
title: name
|
||||
message: 'This feature has not been implemented yet.'
|
||||
}
|
||||
|
||||
# TODO: A (broken) promise should be returned for
|
||||
# consistency.
|
||||
|
||||
{argsMapper, notification} = options ? {}
|
||||
|
||||
argsMapper ?= (id) -> {id}
|
||||
|
||||
(args...) ->
|
||||
xoApi.call(
|
||||
method
|
||||
argsMapper args...
|
||||
).catch (error) ->
|
||||
unless notification is false
|
||||
code = error?.code
|
||||
message = if code is 2
|
||||
'You don\'t have the permission.'
|
||||
else
|
||||
'The action failed for unknown reason.'
|
||||
|
||||
notify.warning {
|
||||
title: name
|
||||
message
|
||||
}
|
||||
|
||||
console.error error
|
||||
|
||||
# Re-throws the error to make it available in the promise
|
||||
# chain.
|
||||
throw error
|
||||
|
||||
# The interface.
|
||||
xo = {
|
||||
objects: xoObjects.all
|
||||
byTypes: xoObjects.byTypes
|
||||
# revision: xoObjects.revision # Implemented below as a getter.
|
||||
|
||||
get: xoObjects.get
|
||||
|
||||
pool: {
|
||||
disconnect: action 'Disconnect pool'
|
||||
new_sr: action 'New SR' #temp fix before creating SR
|
||||
}
|
||||
|
||||
host: {
|
||||
detach: action 'Detach host'#, 'host.detach'
|
||||
restart: action 'Restart host'#, 'host.restart'
|
||||
restartToolStack: action 'Restart tool stack'#, 'host.restartToolStack'
|
||||
start: action 'Start host'#, 'host.start'
|
||||
stop: action 'Stop host'#, 'host.stop'
|
||||
new_sr: action 'New SR' #temp fix before creating SR
|
||||
# TODO: attach/set
|
||||
}
|
||||
|
||||
message: {
|
||||
delete: action, 'Delete message'
|
||||
}
|
||||
|
||||
pbd: {
|
||||
delete: action 'Delete PBD'
|
||||
disconnect: action 'Disconnect PBD'
|
||||
}
|
||||
|
||||
task: {
|
||||
delete: action, 'Delete task'
|
||||
}
|
||||
|
||||
vm: {
|
||||
createSnapshot: action 'Create VM snapshot'
|
||||
delete: action 'Delete VM', 'vm.delete', {
|
||||
argsMapper: (id, delete_disks) -> { id, delete_disks }
|
||||
}
|
||||
ejectCd: action 'Eject disc', 'vm.ejectCd'
|
||||
insertCd: action 'Insert disc', 'vm.insertCd', {
|
||||
argsMapper: (id, cd_id, force = false) -> { id, cd_id, force }
|
||||
}
|
||||
migrate: action 'Migrate VM', 'vm.migrate', {
|
||||
argsMapper: (id, host_id) -> { id, host_id }
|
||||
}
|
||||
restart: action 'Restart VM', 'vm.restart', {
|
||||
argsMapper: (id, force = false) -> { id, force }
|
||||
}
|
||||
start: action 'Start VM', 'vm.start'
|
||||
stop: action 'Stop VM', 'vm.stop', {
|
||||
argsMapper: (id, force = false) -> { id, force }
|
||||
}
|
||||
# TODO: create/set/pause/suspend
|
||||
}
|
||||
|
||||
vdi: {
|
||||
delete: action 'Delete VDI', 'vdi.delete'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Adds the revision property.
|
||||
Object.defineProperty xo, 'revision', {
|
||||
get: -> xoObjects.revision
|
||||
}
|
||||
|
||||
# Returns the interface.
|
||||
xo
|
||||
514
app/services.coffee
Normal file
@@ -0,0 +1,514 @@
|
||||
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.
|
||||
#
|
||||
# TODO: Publish in its own module (via npm/bower/UMD).
|
||||
# TODO: Implements randomization in the delay.
|
||||
# TODO: Should backOff() accept a reason?
|
||||
.service 'BackOff', ($timeout) ->
|
||||
strategies = {
|
||||
constant: (mseconds) ->
|
||||
@next = -> mseconds
|
||||
@reset = ->
|
||||
fibonacci: (init = 1)->
|
||||
prev = 0
|
||||
cur = init
|
||||
@next = ->
|
||||
[prev, cur] = [cur, prev + cur]
|
||||
cur
|
||||
@reset = ->
|
||||
prev = 0
|
||||
cur = init
|
||||
exponential: (base = 2, init) ->
|
||||
current = init ? base
|
||||
@next = ->
|
||||
value = current
|
||||
current *= base
|
||||
value
|
||||
@reset = ->
|
||||
current = init ? base
|
||||
}
|
||||
|
||||
# TODO: Implements a default strategy in a backOffConfig constant.
|
||||
BackOff = (strategy, args...) ->
|
||||
unless strategy of strategies
|
||||
throw new Error "invalid strategy: #{strategy}"
|
||||
|
||||
strategy = strategies[strategy]
|
||||
strategy.apply (@_strategy = Object.create strategy), args
|
||||
|
||||
@_delay = 0
|
||||
@_maxDelay = null
|
||||
@_tries = 0
|
||||
@_maxTries = null
|
||||
@_timer = null
|
||||
|
||||
# Interfaces to the service, MUST be overloaded.
|
||||
BackOff::onBackOff = BackOff::onFail = BackOff::onReady = ( -> )
|
||||
|
||||
# Sets a limit of tries the service will back off.
|
||||
BackOff::failAfter = (tries) ->
|
||||
@_maxTries = tries
|
||||
|
||||
# Sets a maximum delay to wait.
|
||||
BackOff::waitAtMost = (mseconds) ->
|
||||
@_maxDelay = mseconds
|
||||
|
||||
# Starts the back off.
|
||||
BackOff::backOff = ->
|
||||
throw new Error 'allready backed off' if @_timer
|
||||
|
||||
return @fail() if @_tries is @_maxTries
|
||||
|
||||
@_delay = @_strategy.next()
|
||||
@_delay = @_maxDelay if @_maxDelay < @_delay
|
||||
@_timer = $timeout (=> @_onReady()), @_delay
|
||||
|
||||
@onBackOff @_tries, @_delay
|
||||
|
||||
# Unconditionally fails.
|
||||
BackOff::fail = ->
|
||||
@reset()
|
||||
@onFail()
|
||||
|
||||
# Resets the back off.
|
||||
BackOff::reset = ->
|
||||
@_strategy.reset()
|
||||
@_tries = 0
|
||||
|
||||
if @_timer
|
||||
$timeout.cancel @_timer
|
||||
@_timer = null
|
||||
|
||||
BackOff::_onReady = ->
|
||||
@_timer = null
|
||||
tryNumber = @_tries++
|
||||
|
||||
@onReady tryNumber, @_delay
|
||||
|
||||
# Returns the service.
|
||||
BackOff
|
||||
|
||||
.service 'notify', (toaster) ->
|
||||
notifier = (level) ->
|
||||
(options) ->
|
||||
if angular.isString options
|
||||
options = { message: options }
|
||||
else
|
||||
throw new Error 'missing message' unless options.message
|
||||
|
||||
toaster.pop(
|
||||
level
|
||||
options.title ? 'Xen-Orchestra'
|
||||
options.message
|
||||
)
|
||||
|
||||
{
|
||||
warning: notifier 'warning'
|
||||
error: notifier 'error'
|
||||
info: notifier 'info'
|
||||
|
||||
# TODO: It is probably a bad design to have notification for
|
||||
# successful operations.
|
||||
# success: notifier 'success'
|
||||
}
|
||||
|
||||
.service 'xoApi', (
|
||||
$cookieStore
|
||||
$location
|
||||
$rootScope
|
||||
|
||||
$q
|
||||
|
||||
BackOff
|
||||
notify
|
||||
) ->
|
||||
url = do ->
|
||||
# Note: The path is ignored, the WebSocket must be relative to
|
||||
# root.
|
||||
protocol = if $location.protocol() is 'https' then 'wss:' else 'ws:'
|
||||
host = $location.host()
|
||||
port = $location.port()
|
||||
|
||||
"#{protocol}//#{host}:#{port}/api/"
|
||||
|
||||
# Identifier of the next request.
|
||||
nextId = 0
|
||||
|
||||
# Promises linked to the requests.
|
||||
deferreds = {}
|
||||
|
||||
# Listeners for notifications (grouped by method).
|
||||
listeners = {}
|
||||
|
||||
# When the socket is closed, request are enqueued.
|
||||
queue = []
|
||||
|
||||
# Variable which will contains the webSocket to use.
|
||||
socket = null
|
||||
|
||||
backOff = new BackOff 'fibonacci', 5e3
|
||||
backOff.waitAtMost 900e3 # 15 minutes.
|
||||
|
||||
xoApi = {
|
||||
# Current status which may be:
|
||||
# - disconnected
|
||||
# - connecting
|
||||
# - disconnected
|
||||
status: 'disconnected'
|
||||
|
||||
# Currently logged in user.
|
||||
user: null
|
||||
}
|
||||
|
||||
# Wraps a function to make sure scopes are updated after its
|
||||
# execution.
|
||||
wrap = (fn) ->
|
||||
(args...) ->
|
||||
result = fn.apply this, args
|
||||
$rootScope.$digest()
|
||||
result
|
||||
|
||||
# Function used to send requests when the socket is opened.
|
||||
send = (method, params, deferred) ->
|
||||
id = nextId++
|
||||
socket.send JSON.stringify(
|
||||
jsonrpc: '2.0'
|
||||
id: id
|
||||
method: method
|
||||
params: params or []
|
||||
)
|
||||
deferreds[id] = deferred or $q.defer()
|
||||
deferreds[id].promise
|
||||
|
||||
# Function used to enqueue requests when the socket is closed.
|
||||
enqueue = (method, params) ->
|
||||
deferred = $q.defer()
|
||||
queue.push [method, params, deferred]
|
||||
deferred.promise
|
||||
|
||||
# This variable contains the function which be called (initially
|
||||
# it will points to `enqueue`).
|
||||
call = enqueue
|
||||
|
||||
connect = ->
|
||||
xoApi.status = 'connecting'
|
||||
|
||||
# Creation of the WebSocket.
|
||||
socket = new WebSocket url
|
||||
|
||||
# When the WebSocket opens, send any requests enqueued.
|
||||
socket.addEventListener 'open', wrap ->
|
||||
xoApi.status = 'connected'
|
||||
backOff.reset()
|
||||
|
||||
# If there is a token tries to sign in.
|
||||
if (token = $cookieStore.get 'token')
|
||||
send(
|
||||
'session.signInWithToken'
|
||||
{token}
|
||||
).then (loggedInUser) ->
|
||||
xoApi.user = loggedInUser
|
||||
.catch ->
|
||||
# The authentication failed, removes the token.
|
||||
$cookieStore.remove 'token'
|
||||
|
||||
# Sends queued requests.
|
||||
send entry... while (entry = queue.shift())?
|
||||
|
||||
# New requests are sent directly.
|
||||
call = send
|
||||
|
||||
socket.addEventListener 'close', -> backOff.backOff()
|
||||
|
||||
# When a message is received, we call the corresponding
|
||||
# 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]
|
||||
|
||||
unless deferred
|
||||
# Response already handled.
|
||||
return
|
||||
|
||||
delete deferreds[id]
|
||||
|
||||
error = response.error
|
||||
unless error is undefined
|
||||
deferred.reject error
|
||||
return
|
||||
|
||||
result = response.result
|
||||
if result is undefined
|
||||
console.error 'invalid message received', response
|
||||
deferred.reject {
|
||||
message: message
|
||||
object: response
|
||||
}
|
||||
return
|
||||
|
||||
deferred.resolve result
|
||||
|
||||
backOff.onBackOff = wrap (tryNumber, delay) ->
|
||||
xoApi.status = 'disconnected'
|
||||
xoApi.user = null
|
||||
call = enqueue
|
||||
backOff.onReady = wrap connect
|
||||
|
||||
connect()
|
||||
|
||||
# Extends the singleton with various methods and returns it.
|
||||
angular.extend xoApi, {
|
||||
call: (method, params) ->
|
||||
call method, params
|
||||
|
||||
logIn: (email, password, persist) ->
|
||||
call(
|
||||
'session.signInWithPassword'
|
||||
{email, password}
|
||||
).then (loggedInUser) ->
|
||||
xoApi.user = loggedInUser
|
||||
|
||||
if persist
|
||||
call('token.create').then (token) ->
|
||||
$cookieStore.put 'token', token
|
||||
.catch (error) ->
|
||||
notify.warning {
|
||||
title: 'Authentication failed'
|
||||
message: error.message
|
||||
}
|
||||
|
||||
logOut: ->
|
||||
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, $rootScope) ->
|
||||
byRefs = Object.create null
|
||||
byUUIDs = Object.create null
|
||||
{
|
||||
all
|
||||
byTypes
|
||||
} = xoObjects = {
|
||||
revision: 0
|
||||
all: []
|
||||
byTypes: Object.create null
|
||||
|
||||
get: (key) ->
|
||||
if angular.isArray key
|
||||
item for item in key when (item = byUUIDs[item] ? byRefs[item])
|
||||
else
|
||||
byUUIDs[key] ? byRefs[key]
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
++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
|
||||
|
||||
.service 'xo', (xoObjects, xoApi, notify) ->
|
||||
action = (name, method, options) ->
|
||||
unless method
|
||||
return ->
|
||||
notify.info {
|
||||
title: name
|
||||
message: 'This feature has not been implemented yet.'
|
||||
}
|
||||
|
||||
# TODO: A (broken) promise should be returned for
|
||||
# consistency.
|
||||
|
||||
{argsMapper, notification} = options ? {}
|
||||
|
||||
# FIXME: default mapper should be identity.
|
||||
argsMapper ?= (id) -> {id}
|
||||
|
||||
(args...) ->
|
||||
xoApi.call(
|
||||
method
|
||||
argsMapper args...
|
||||
).catch (error) ->
|
||||
unless notification is false
|
||||
code = error?.code
|
||||
message = if code is 2
|
||||
'You don\'t have the permission.'
|
||||
else
|
||||
'The action failed for unknown reason.'
|
||||
|
||||
notify.warning {
|
||||
title: name
|
||||
message
|
||||
}
|
||||
|
||||
console.error error
|
||||
|
||||
# Re-throws the error to make it available in the promise
|
||||
# chain.
|
||||
throw error
|
||||
|
||||
# The interface.
|
||||
xo = {
|
||||
objects: xoObjects.all
|
||||
byTypes: xoObjects.byTypes
|
||||
# revision: xoObjects.revision # Implemented below as a getter.
|
||||
|
||||
get: xoObjects.get
|
||||
|
||||
pool:
|
||||
disconnect: action 'Disconnect pool'
|
||||
new_sr: action 'New SR' #temp fix before creating SR
|
||||
|
||||
host:
|
||||
attach: action 'Atach host'#, 'host.attach'
|
||||
detach: action 'Detach host', 'host.detach'
|
||||
restart: action 'Restart host', 'host.restart'
|
||||
restartToolStack: action 'Restart tool stack', 'host.restart_agent'
|
||||
start: action 'Start host'#, 'host.start'
|
||||
stop: action 'Stop host', 'host.stop'
|
||||
new_sr: action 'New SR' #temp fix before creating SR
|
||||
# TODO: attach/set
|
||||
|
||||
log:
|
||||
delete: action 'Delete Log', 'message.delete'
|
||||
|
||||
message:
|
||||
delete: action 'Delete message'
|
||||
|
||||
pbd:
|
||||
delete: action 'Delete PBD'
|
||||
disconnect: action 'Disconnect PBD'
|
||||
|
||||
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
|
||||
|
||||
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}
|
||||
}
|
||||
delete: action 'Delete VM', 'vm.delete', {
|
||||
argsMapper: (id, delete_disks) -> { id, delete_disks }
|
||||
}
|
||||
ejectCd: action 'Eject disc', 'vm.ejectCd'
|
||||
insertCd: action 'Insert disc', 'vm.insertCd', {
|
||||
argsMapper: (id, cd_id, force = false) -> { id, cd_id, force }
|
||||
}
|
||||
migrate: action 'Migrate VM', 'vm.migrate', {
|
||||
argsMapper: (id, host_id) -> { id, host_id }
|
||||
}
|
||||
restart: action 'Restart VM', 'vm.restart', {
|
||||
argsMapper: (id, force = false) -> { id, force }
|
||||
}
|
||||
start: action 'Start VM', 'vm.start'
|
||||
stop: action 'Stop VM', 'vm.stop', {
|
||||
argsMapper: (id, force = false) -> { id, force }
|
||||
}
|
||||
revert: action 'Revert snapshot', 'vm.revert'
|
||||
# TODO: create/set/pause/suspend
|
||||
|
||||
vdi:
|
||||
delete: action 'Delete VDI', 'vdi.delete'
|
||||
}
|
||||
|
||||
# Adds the revision property.
|
||||
Object.defineProperty xo, 'revision', {
|
||||
get: -> xoObjects.revision
|
||||
}
|
||||
|
||||
# Returns the interface.
|
||||
xo
|
||||
@@ -38,6 +38,10 @@ a, [ng-click], [xo-click], [xo-sref]
|
||||
#toast-container
|
||||
pointer-events: none
|
||||
|
||||
// Force our content to be under the fixed navbar.
|
||||
.view-main
|
||||
padding-top: 50px
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO: The styles in this file should be
|
||||
@@ -60,7 +64,7 @@ a, [ng-click], [xo-click], [xo-sref]
|
||||
.xo-color-halted
|
||||
@extend .text-danger
|
||||
|
||||
.xo-color-paused
|
||||
.xo-color-paused, .xo-color-suspended
|
||||
@extend .text-info
|
||||
|
||||
.xo-color-unknown
|
||||
@@ -80,20 +84,26 @@ a, [ng-click], [xo-click], [xo-sref]
|
||||
.xo-icon-warning
|
||||
@extend .fa, .fa-exclamation-circle, .text-warning
|
||||
|
||||
.xo-ison-error
|
||||
@extend .fa, .fa-exclamation-circle, .text-error
|
||||
.xo-icon-error
|
||||
@extend .fa, .fa-exclamation-circle, .text-danger
|
||||
|
||||
.xo-icon-success
|
||||
@extend .fa, .fa-check-circle, .text-success
|
||||
|
||||
////
|
||||
// Objects
|
||||
////
|
||||
|
||||
.xo-icon-console
|
||||
@extend .fa, .fa-terminal
|
||||
|
||||
.xo-icon-pool
|
||||
@extend .fa, .fa-cloud
|
||||
|
||||
.xo-icon-host
|
||||
@extend .fa, .fa-th-large
|
||||
|
||||
.xo-icon-vm, .xo-icon-console
|
||||
.xo-icon-vm
|
||||
@extend .fa, .fa-desktop
|
||||
|
||||
.xo-icon-memory
|
||||
@@ -121,6 +131,9 @@ a, [ng-click], [xo-click], [xo-sref]
|
||||
.xo-icon-halted
|
||||
@extend .fa, .fa-circle, .xo-color-halted
|
||||
|
||||
.xo-icon-suspended
|
||||
@extend .fa, .fa-circle, .xo-color-paused
|
||||
|
||||
.xo-icon-paused
|
||||
@extend .fa, .fa-circle, .xo-color-paused
|
||||
|
||||
@@ -198,6 +211,10 @@ a, [ng-click], [xo-click], [xo-sref]
|
||||
// Main view
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// FIXME: Class names are too generic and styles might be applied to
|
||||
// other views as well, all styles should be namespaced (e.g.
|
||||
// `.view-main`).
|
||||
|
||||
// Notice messages in place of the VMs list in the main page.
|
||||
.vms-notice
|
||||
@extend .text-center, .text-muted
|
||||
@@ -263,6 +280,16 @@ div.host-cell:hover .substats
|
||||
@extend .text-primary
|
||||
font-weight: bold
|
||||
|
||||
// Prevents a host name from overflowing outside its box.
|
||||
.host-cell, .pool-cell
|
||||
& div
|
||||
position: relative
|
||||
.host-name, .pool-name
|
||||
display: inline-block
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
max-width: 80%
|
||||
overflow: hidden
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Pool view
|
||||
|
||||
@@ -11,46 +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 {
|
||||
padding-top: 50px;
|
||||
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; }
|
||||
@@ -58,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;}
|
||||
@@ -100,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -167,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;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -180,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;}
|
||||
@@ -206,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: #eee;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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> Projet 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>
|
||||
@@ -1,47 +0,0 @@
|
||||
<!-- TODO: lots of stuff. -->
|
||||
<div class="row">
|
||||
<div class="grid-cell flat-panel">
|
||||
<p class="page-title"><i class="xo-icon-console xo-color-{{vm.power_state | lowercase}}"></i> {{VM.name_label}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ISO selector and CtrlAltSuppr button -->
|
||||
<div class="row flat-panel">
|
||||
<div class="col-md-5">
|
||||
<select
|
||||
class="form-control"
|
||||
ng-model="mountedIso"
|
||||
ng-change="insert(mountedIso)"
|
||||
ng-options="VDI.UUID as VDI.name_label group by (VDI.$SR | resolve).name_label for VDI in VDIs | orderBy:'name_label'"
|
||||
></select>
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<button
|
||||
class="btn btn-default"
|
||||
ng-click="eject()"
|
||||
ng-disabled="!mountedIso"
|
||||
>
|
||||
<i class="fa fa-eject"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<button
|
||||
class="btn btn-default"
|
||||
ng-click="vncRemote.sendCtrlAltDel()"
|
||||
>
|
||||
<i class="fa fa-keyboard-o"></i> Ctrl+Alt+Del
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NoVNC panel -->
|
||||
<div class="grid">
|
||||
<div class="grid-cell flat-panel">
|
||||
<xo-vnc
|
||||
url="{{consoleUrl}}"
|
||||
remote-control="vncRemote"
|
||||
>
|
||||
</xo-vnc>
|
||||
<br/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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:'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>
|
||||
@@ -1,23 +0,0 @@
|
||||
<div class="modal-header">
|
||||
<h3>{{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>
|
||||
@@ -1,364 +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">
|
||||
<div class="grid-cell text-center grid-button" tooltip="Add SR">
|
||||
<i class="xo-icon-sr fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Add VM">
|
||||
<i class="xo-icon-vm fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Reboot Host">
|
||||
<i class="fa fa-refresh fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Shutdown Host">
|
||||
<i class="fa fa-power-off fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Restart Toolstack">
|
||||
<i class="fa fa-retweet fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Remove from Pool">
|
||||
<i class="fa fa-cloud-upload fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Host Console">
|
||||
<i class="xo-icon-console fa-2x fa-fw"></i>
|
||||
</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:'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:'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:'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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Network 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> Network
|
||||
<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:'name_label' track by PIF.UUID">
|
||||
<td>{{PIF.device}} <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">
|
||||
<p class="flat-panel-title"><i class="fa fa-comments" style="color: #e25440;"></i> Logs</p>
|
||||
<p class="center" ng-if="!host.messages.length">No recent logs</p>
|
||||
<table class="table table-hover" ng-if="host.messages.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="message in host.messages | resolve | orderBy:'-time' track by message.UUID">
|
||||
<td>{{message.time*1e3 | date:'medium'}}</td>
|
||||
<td>{{message.name}}
|
||||
<a class="quick-remove" xo-click="removeMessage(message.UUID)">
|
||||
<i class="fa fa-trash-o fa-fw"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -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:'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>
|
||||
<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:'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>
|
||||
<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:'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>
|
||||
<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:'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>
|
||||
<span ng-repeat="tag in SR.tags">
|
||||
<span class="label label-primary">{{tag}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /Tags. -->
|
||||
|
||||
</div>
|
||||
<!-- /Properties & tags. -->
|
||||
|
||||
</div>
|
||||
<!-- /SR. -->
|
||||
@@ -1,504 +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>
|
||||
|
||||
<i tooltip="{{hosts.length}} hosts">
|
||||
<i class="small">{{hosts.length}}x</i>
|
||||
<i class="xo-icon-host"></i>
|
||||
</i>
|
||||
|
||||
<i tooltip="{{xo.$running_VMs.length}} of {{VMs.length}} VMs running">
|
||||
<i class="small">{{xo.$running_VMs.length}}x</i>
|
||||
<i class="xo-icon-console"></i>
|
||||
</i>
|
||||
|
||||
<i tooltip="{{xo.$vCPUs}} vCPUs used of {{xo.$CPUs}} CPUs">
|
||||
<i class="small">{{xo.$vCPUs}}x</i>
|
||||
<i class="xo-icon-cpu"></i>
|
||||
</i>
|
||||
|
||||
<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:'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:'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('createVMSnapshot')"><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:'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"> <i class="fa fa-caret-down big-caret"></i> </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>
|
||||
|
||||
<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-console"></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:'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:'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"> <i class="fa fa-caret-down"></i> </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 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><a xo-click="pool_removeHost(host.UUID)"><i class="fa fa-cloud-upload fa-fw"></i> Remove from 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 && !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:'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)}}"> </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:'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)}}"> </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>
|
||||
@@ -1,164 +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 && '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. -->
|
||||
|
||||
<!-- Right items of the navbar. -->
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<!-- Unregistered version: please register for support -->
|
||||
<li><a href="https://xen-orchestra.com/services?from=xoa" target="_blank"><i class="fa fa-exclamation-triangle"></i> Unregistered version</a></li>
|
||||
<!-- Main menu. -->
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle inverse">
|
||||
<i class="fa fa-th"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu inverse">
|
||||
<li ng-class="{active: $uiRoute}" ui-route="/">
|
||||
<a ui-sref="home">
|
||||
<i class="fa fa-indent"></i>
|
||||
Tree view
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{active: $uiRoute}" ui-route="/list">
|
||||
<a ui-sref="list">
|
||||
<i class="fa fa-align-justify"></i>
|
||||
Flat view
|
||||
</a>
|
||||
</li>
|
||||
<li class="disabled" ng-class="{active: $uiRoute}" 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 ng-class="{active: $uiRoute}" ui-route="/settings">
|
||||
<a ui-sref="settings">
|
||||
<i class="fa fa-cog"></i>
|
||||
Settings
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li ng-class="{active: $uiRoute}" 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 <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. -->
|
||||
@@ -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>
|
||||
@@ -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:'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">
|
||||
<input
|
||||
type="radio"
|
||||
value="cdrom"
|
||||
ng-model="installation_method"
|
||||
>
|
||||
<select
|
||||
ng-disabled="'cdrom' !== installation_method"
|
||||
class="form-control disabled"
|
||||
ng-model="installation_cdrom"
|
||||
>
|
||||
<optgroup
|
||||
ng-repeat="SR in ISO_SRs | orderBy:'name_label' track by SR.UUID"
|
||||
ng-if="SR.VDIs.length"
|
||||
label="{{SR.name_label}}"
|
||||
>
|
||||
<option
|
||||
ng-repeat="VDI in SR.VDIs | resolve | orderBy:'name_label' track by VDI.UUID"
|
||||
ng-value="VDI.UUID"
|
||||
>
|
||||
{{VDI.name_label}}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</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">
|
||||
<input
|
||||
type="radio"
|
||||
ng-model="installation_method"
|
||||
value="network"
|
||||
>
|
||||
<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 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"> </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="/^([0-9a-f]){2}(:([0-9a-f]){4})$/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:'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"> </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:'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>
|
||||
@@ -1,206 +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:'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:'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">
|
||||
<p class="flat-panel-title"><i class="fa fa-comments" style="color: #e25440;"></i> Logs</p>
|
||||
<p class="center" ng-if="!pool.messages.length">No recent logs</p>
|
||||
<table class="table table-hover" ng-if="pool.messages.length">
|
||||
<th class="col-md-1">Date</th>
|
||||
<th class="col-md-1">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}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</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:'name_label' track by template.UUID">
|
||||
{{template.name_description}}
|
||||
</accordion-group>
|
||||
</accordion>
|
||||
</div> -->
|
||||
|
||||
</div>
|
||||
@@ -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:'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> </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:'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> </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>
|
||||
@@ -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:'name_label'"
|
||||
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:'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. -->
|
||||
@@ -1,292 +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">
|
||||
<div class="grid-cell text-center grid-button" tooltip="Stop VM" xo-click="stopVM(VM.UUID)">
|
||||
<i class="fa fa-stop fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Start VM" xo-click="startVM(VM.UUID)">
|
||||
<i class="fa fa-play fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Reboot VM" xo-click="rebootVM(VM.UUID)">
|
||||
<i class="fa fa-refresh fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Migrate VM">
|
||||
<i class="fa fa-mail-forward fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Force Reboot" xo-click="force_rebootVM(VM.UUID)">
|
||||
<i class="fa fa-flash fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Force Shutdown" xo-click="force_stopVM(VM.UUID)">
|
||||
<i class="fa fa-power-off fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Delete VM" xo-click="destroyVM(VM.UUID)">
|
||||
<i class="fa fa-trash-o fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div class="grid-cell text-center grid-button" tooltip="Create a snapshot">
|
||||
<i class="xo-icon-snapshot fa-2x fa-fw"></i>
|
||||
</div>
|
||||
<div
|
||||
class="grid-cell text-center grid-button"
|
||||
tooltip="VM Console"
|
||||
xo-sref="consoles_view({id: VM.UUID})"
|
||||
>
|
||||
<i class="xo-icon-console fa-2x fa-fw"></i>
|
||||
</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:'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:'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>
|
||||
</div>
|
||||
</form>
|
||||
</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 class="col-md-1">Device</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="VIF in VM.VIFs | resolve | orderBy:'name_label' track by VIF.UUID">
|
||||
<td>VIF #{{VIF.device}}</td>
|
||||
<td>
|
||||
<span editable-text="VIF.MAC" e-name="mac" e-form="vmInterfaces" e-required>
|
||||
{{VIF.MAC}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span editable-text="VIF.MTU" e-name="mtu" e-form="vmInterfaces" e-required>
|
||||
{{VIF.MTU}}
|
||||
</span>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO: Snapshot and Logs panels -->
|
||||
<div class="grid">
|
||||
<!-- Snapshots panel -->
|
||||
<div class="grid-cell flat-panel">
|
||||
<p class="flat-panel-title"><i class="xo-icon-snapshot" style="color: #e25440;"></i> Snapshot</p>
|
||||
<p class="center" ng-if="!VM.snapshots.length">No snapshots</p>
|
||||
<table class="table table-hover" ng-if="VM.snapshots">
|
||||
<th class="col-md-1">Date</th>
|
||||
<th class="col-md-1">Name</th>
|
||||
<tr ng-repeat="snapshot in VM.snapshots | resolve | orderBy:'name_label' track by snapshot.UUID">
|
||||
<td>{{snapshot.snapshot_time*1e3 | date:'medium'}}</td>
|
||||
<td>{{snapshot.name_label}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Logs panel -->
|
||||
<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="!VM.messages.length">No recent logs</p>
|
||||
<table class="table table-hover" ng-if="VM.messages.length">
|
||||
<th class="col-md-1">Date</th>
|
||||
<th class="col-md-1">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}}
|
||||
<a class="quick-remove" tooltip="Remove log">
|
||||
<i class="fa fa-trash-o fa-fw"> </i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
31
bower.json
@@ -2,20 +2,21 @@
|
||||
"name": "xo-web",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"angular": "~1.2.10",
|
||||
"angular-cookies": "~1.2.10",
|
||||
"angular-notify-toaster": "~0.4.1",
|
||||
"angular-ui-bootstrap-bower": "~0.10.0",
|
||||
"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-router": "0.2.8-bowratic-tedium",
|
||||
"angular-ui-select2": "~0.0.5",
|
||||
"angular-ui-utils": "~0.0.4",
|
||||
"angular-xeditable": "~0.1.7",
|
||||
"es5-shim": "~2.2.0",
|
||||
"font-awesome": "~4.0.3",
|
||||
"jquery": "~2.0.3",
|
||||
"json3": "~3.2.6",
|
||||
"noVNC": "git://github.com/kanaka/noVNC",
|
||||
"sass-bootstrap": "~3.0.2"
|
||||
},
|
||||
"devDependencies": {}
|
||||
"angular-ui-select2": "0.0.5",
|
||||
"angular-ui-utils": "0.1.1",
|
||||
"angular-xeditable": "0.1.8",
|
||||
"angularjs-naturalsort": "https://bitbucket.org/OverZealous/angularjs-naturalsort/raw/0a5e2a6abc28c079f9b959bfaa29497a71d8a23a/dist/naturalSortVersionDates.min.js",
|
||||
"es5-shim": "2.3.0",
|
||||
"font-awesome": "4.0.3",
|
||||
"jquery": "2.1.0",
|
||||
"json3": "3.3.0",
|
||||
"noVNC": "https://github.com/kanaka/noVNC.git",
|
||||
"sass-bootstrap": "3.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
89
doc/README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Documentation
|
||||
|
||||

|
||||
|
||||
## 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!
|
||||
-->
|
||||
101
doc/administration/README.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
Note the green *check* icons: it indicates that you are correctly connected to XO-server. If you see this icon: , 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:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
From there, you can modify your current password, then Save:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
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).**
|
||||
BIN
doc/administration/assets/gosettings.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
doc/administration/assets/loginbad.png
Normal file
|
After Width: | Height: | Size: 776 B |
BIN
doc/administration/assets/loginok.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
doc/administration/assets/servers.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
doc/administration/assets/users.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
doc/administration/assets/welcome.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
60
doc/architecture/README.md
Normal 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:
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
You can see how we avoid a lost of resources and bandwidth waste with a central point:
|
||||
|
||||

|
||||
|
||||
### 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).**
|
||||
BIN
doc/architecture/assets/with-xo.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
doc/architecture/assets/without-xo.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
doc/architecture/assets/xo-arch.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
doc/assets/xo.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
166
doc/installation/README.md
Normal 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
@@ -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.
|
||||
|
||||

|
||||
|
||||
### Main menu
|
||||
|
||||
It's represented by the . It gives you access to the main zones of XO-web.
|
||||
|
||||

|
||||
|
||||
### 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":
|
||||
|
||||

|
||||
|
||||
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):
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
### Host and pools submenu
|
||||
|
||||
Don't forget to expand Pool and hosts submenus to explore what you can do:
|
||||
|
||||

|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
|
||||
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).**
|
||||
BIN
doc/layout/assets/filter.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
doc/layout/assets/iconmain.png
Normal file
|
After Width: | Height: | Size: 197 B |
BIN
doc/layout/assets/navbar.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
doc/layout/assets/navbarhome.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
doc/layout/assets/navbarselected.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
doc/layout/assets/quickactions.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
doc/layout/assets/submenus.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
5
doc/recipes/README.md
Normal 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)!)
|
||||