Compare commits

...

617 Commits

Author SHA1 Message Date
florent
93e7bdaeb0 add proprtype 2016-02-02 20:59:08 +01:00
florent
877e471b10 working on the create vm form 2016-02-01 22:29:48 +01:00
florent
32478e470b FB proposition for react (WIP)
after a few try, I think it can work this way :

 * everything is in one store: xoApi, session, even component states. try to use boolean when possible to simplify usage in components
 * components states should rarely be used : let components be stateless, a pure function of their props
 * use connect on high level component to give them access to part of the store and the actions. A component should not have access to the full store and action.
 * use event to get data back from child components ( if they can't dispatch action themselves)
 * redux-thunk allow to dispatch multiple action in cascade (even async one) , like want to save->saving ->saved (or errored)
 * use imutable object, this will allow faster performance in the long run
2016-01-31 22:06:12 +01:00
Julien Fontanet
ccbcaa94fe Various updates. 2016-01-28 09:45:40 +01:00
Julien Fontanet
0ffa9d4225 Various updates. 2016-01-22 16:03:33 +01:00
Julien Fontanet
8e5dee79e0 Use Babel 6. 2015-12-04 11:49:19 +01:00
Julien Fontanet
9abd9d20ec Initial work on React/Redux. 2015-12-03 18:22:13 +01:00
Julien Fontanet
0c173fde53 Explicit .scss for angular-notify-toaster (fix #488). 2015-11-13 11:53:32 +01:00
Julien Fontanet
77db2bbfec 4.9.0 2015-11-13 11:31:19 +01:00
Julien Fontanet
2987185a9d Fix coding style. 2015-11-13 11:30:47 +01:00
Olivier Lambert
7a7baf7175 change the release date 2015-11-12 16:54:30 +01:00
Olivier Lambert
5645cc0af2 removing an entry in the changelog 2015-11-12 12:35:46 +01:00
Olivier Lambert
63a6756fed add changelog 2015-11-12 11:37:31 +01:00
Olivier Lambert
9f408c98a6 Merge pull request #480 from vatesfr/marsaudf-vm-copy-sr
Copy VM
2015-11-12 11:16:25 +01:00
Olivier Lambert
26d6998d82 Merge pull request #485 from vatesfr/abhamonr-perf-12-cpus
for cpus graph: draw only one line which is an average of all CPUs (i…
2015-11-12 11:06:08 +01:00
wescoeur
6bee44acb7 for cpus graph: draw only one line which is an average of all CPUs (if more than 12 cores) 2015-11-12 10:38:37 +01:00
Fabrice Marsaud
441992cf37 Copy VM, first delivery 2015-11-12 10:33:46 +01:00
Fabrice Marsaud
490c224ac3 Merge pull request #469 from vatesfr/marsaudf-disaster-recovery
Disaster Recovery
2015-11-12 10:31:39 +01:00
Fabrice Marsaud
f5c55048de DR UI enhancemnts 2015-11-12 10:19:49 +01:00
Fabrice Marsaud
8139e124c2 DR features fixes 2015-11-12 10:19:49 +01:00
Fabrice Marsaud
cba73a5139 Disaster recovery feature 2015-11-12 10:19:48 +01:00
Fabrice Marsaud
de0c9367e5 Merge pull request #483 from vatesfr/abhamonr-backup-logs
Abhamonr backup logs
2015-11-12 10:11:44 +01:00
Fabrice Marsaud
630060860c Backup log UI fix 2015-11-12 10:09:09 +01:00
Fabrice Marsaud
dccd11fb7b Backup logs UI 2015-11-12 10:09:09 +01:00
Olivier Lambert
b8a4b2cf16 add duration filter 2015-11-12 10:09:09 +01:00
wescoeur
e52f55bfba backup view (logs) in progress 2015-11-12 10:09:09 +01:00
wescoeur
1cb99e02a9 Logs of jobs in progress in backup/management view 2015-11-12 10:09:09 +01:00
Fabrice Marsaud
c9c5c35e56 Merge pull request #476 from vatesfr/abhamonr-backup-logs
Abhamonr backup logs
2015-11-12 10:06:34 +01:00
Olivier Lambert
bc7c9f9c01 fix inversion in vCPUs and CPUs in dashboard view 2015-11-11 22:10:52 +01:00
Fabrice Marsaud
e4bfc4cb8d Better chosen objects display in ACL UI 2015-11-11 17:44:19 +01:00
Fabrice Marsaud
d64995c4a1 Backup log UI fix 2015-11-10 17:08:59 +01:00
Fabrice Marsaud
2952ea7404 Backup logs UI 2015-11-10 17:08:59 +01:00
Olivier Lambert
f34c807a2c add duration filter 2015-11-10 17:08:59 +01:00
wescoeur
b1f9704055 backup view (logs) in progress 2015-11-10 17:08:59 +01:00
wescoeur
9382829ba5 Logs of jobs in progress in backup/management view 2015-11-10 17:08:59 +01:00
Fabrice Marsaud
373a6ea912 Merge pull request #479 from vatesfr/marsaudf-backup-fixes
Backup UI enhancements
2015-11-10 16:53:00 +01:00
Fabrice Marsaud
72eb4e7b3b Backup UI enhancements 2015-11-10 15:21:24 +01:00
Julien Fontanet
315c0870ed Fix VM exports when not directly under /. 2015-11-09 16:13:17 +01:00
Olivier Lambert
200fa621bf use the new xenTools method 2015-11-06 13:54:13 +01:00
Fabrice Marsaud
80348c1980 Patched a display delay for some stats 2015-11-06 13:47:01 +01:00
Fabrice Marsaud
856dd8403c Patched stat display bug 2015-11-06 13:39:23 +01:00
Olivier Lambert
0bb9acd4c1 Merge pull request #470 from vatesfr/marsaudf-backup-no-compression
Compression can be disabled for backups
2015-11-06 11:40:06 +01:00
Fabrice Marsaud
047a80917f Compression can be disabled for backups 2015-11-06 11:37:31 +01:00
Fabrice Marsaud
e6e8fe4763 Fixed backup overview links 2015-11-06 11:36:17 +01:00
Olivier Lambert
6cd212398e Merge pull request #473 from vatesfr/marsaudf-fix-convertToTemplate
Back to index after VM converts to template
2015-11-06 11:10:34 +01:00
Fabrice Marsaud
44ad6d4247 Back to index after VM converts to template 2015-11-06 11:07:42 +01:00
Olivier Lambert
7302782853 Merge pull request #465 from vatesfr/marsaudf-restore-backup
Restore backups
2015-11-05 15:47:04 +01:00
Fabrice Marsaud
7fa1aba6b8 Backup restore and global backup UI rework 2015-11-05 15:18:01 +01:00
Julien Fontanet
2fed4e3e8b Update deps. 2015-11-03 12:08:05 +01:00
Julien Fontanet
bd343c51a3 4.8.0 2015-10-29 10:43:17 +01:00
Olivier Lambert
8a05f06efa changelog update 2015-10-28 17:56:51 +01:00
Julien Fontanet
27b049eada Fix permissions checking for XO admins. 2015-10-28 16:43:50 +01:00
Julien Fontanet
2d1afb5291 Fixes some permissions checking. 2015-10-28 16:24:13 +01:00
Olivier Lambert
63c17a3abf Merge pull request #456 from vatesfr/abhamonr-avoid-use-of-vms-offline-on-dashboard
Avoid the selection of vms or hosts which are not in the running state
2015-10-28 16:14:57 +01:00
wescoeur
94f9bc5fca Avoid the selection of vms or hosts which are not the running state 2015-10-28 15:46:04 +01:00
Julien Fontanet
ab273430d2 Merge pull request #455 from vatesfr/julien-f-acl-inheritance
Adapt to new ACLs API (and implement inheritance).
2015-10-28 15:30:05 +01:00
Julien Fontanet
0b3dc315ad Adapt to new ACLs API (and implement inheritance). 2015-10-28 15:09:08 +01:00
Olivier Lambert
f26a2d2f13 remove old parameter for granularity 2015-10-28 14:59:57 +01:00
Olivier Lambert
8edf9bf508 Merge pull request #454 from vatesfr/abhamonr-intelligent-stats
Remove default value of granularity in refreshStats
2015-10-28 14:44:20 +01:00
Olivier Lambert
415381cebd fix a missing char in the Jade template 2015-10-28 14:41:29 +01:00
wescoeur
59accec1c0 remove default value of granularity in refreshStats 2015-10-28 14:35:55 +01:00
Olivier Lambert
dd2699fcc1 Merge pull request #453 from vatesfr/abhamonr-intelligent-stats
host, vm and dashboard/health pages use now the new stats format prov…
2015-10-28 13:53:14 +01:00
wescoeur
0986a5f985 host, vm and dashboard/health pages use now the new stats format provided by the server 2015-10-28 12:51:13 +01:00
Olivier Lambert
fa77229b72 Merge pull request #452 from vatesfr/proxy-auth-updater
Proxy auth for update panel
2015-10-28 11:52:31 +01:00
Olivier Lambert
78b5080c9a Merge pull request #440 from vatesfr/reg-renewal#424
Xoa registration can be renewed
2015-10-28 10:49:15 +01:00
Fabrice Marsaud
0641da786c Proxy auth for update panel 2015-10-27 16:05:31 +01:00
Olivier Lambert
3291f3bb3c initial CHANGELOG for 4.8 2015-10-27 14:30:41 +01:00
Olivier Lambert
a0cfef8bda Merge pull request #444 from vatesfr/pierre-install-all-patches-button
Added button to install all the missing patches on host at one go
2015-10-26 15:23:50 +01:00
Julien Fontanet
4d033f4a03 Direct links work again (fix #371). 2015-10-26 12:41:45 +01:00
Pierre
562820180c Added button to install all the missing patches on host at one go 2015-10-23 15:48:03 +02:00
Olivier Lambert
a29832207e Merge pull request #443 from vatesfr/pierre-fix-patches-display
In host : fixed uploaded patches properties (name, description, uuid)
2015-10-23 15:28:29 +02:00
Pierre
2afd549826 In host : fixed uploaded patches properties (name, description, uuid) 2015-10-23 15:21:50 +02:00
Olivier Lambert
8a71b2c6dd Merge pull request #430 from vatesfr/pierre-2-status-patches
Patches installed on a host appear as 'Applied' or 'Not applied' and …
2015-10-23 15:11:08 +02:00
Pierre
d633d2691d Patches installed on a host do not appear as missing 2015-10-23 14:43:44 +02:00
Fabrice Marsaud
f9b1608fd2 Xoa registration can be renewed 2015-10-21 12:34:13 +02:00
Julien Fontanet
4d8ed3f00e Use bundler collapser for production build. 2015-10-21 11:28:46 +02:00
Julien Fontanet
359e7d0543 Merge pull request #437 from vatesfr/julien-f-hvm-network-install
Add PXE installation method for HVM templates. (fix #436)
2015-10-19 15:12:37 +02:00
Julien Fontanet
07bf93e022 Add PXE installation method for HVM templates. (fix #436) 2015-10-19 15:11:43 +02:00
Olivier Lambert
57e27da0c4 update style 2015-10-13 19:19:11 +02:00
Olivier Lambert
9ecbf62d25 fix #136 by adding FreeBSD icons from another icon pack 2015-10-13 18:56:23 +02:00
Olivier Lambert
48ffa591ca display the correct distro icons 2015-10-13 18:46:23 +02:00
Olivier Lambert
b7dd617bb1 update filter 2015-10-13 18:46:23 +02:00
Olivier Lambert
392f9d0775 install of new fonts Mfizz 2015-10-13 18:46:23 +02:00
Fabrice Marsaud
4361b11c68 4.7.0 2015-10-12 16:56:56 +02:00
Fabrice Marsaud
28bccad010 No plugin message 2015-10-12 16:44:47 +02:00
Fabrice Marsaud
29d31a0deb Cleaner plugin conf saving 2015-10-12 16:17:39 +02:00
Fabrice Marsaud
1d9960d349 Plugin configuration fixed and enhanced 2015-10-12 14:49:09 +02:00
Fabrice Marsaud
2747b241ab Notification for plugin conf saved 2015-10-12 12:13:32 +02:00
Fabrice Marsaud
6b8035b116 Plugin conf supports default values + fixes 2015-10-12 11:57:11 +02:00
Olivier Lambert
33ad5f4d45 release date 2015-10-12 10:56:06 +02:00
Fabrice Marsaud
af7ad9251a (not)required plugin conf values of object type are handled + fix 2015-10-12 10:54:23 +02:00
Fabrice Marsaud
4ec9975aa3 Merge pull request #416 from vatesfr/pluginConf#352
Plugin conf#352
2015-10-09 17:41:01 +02:00
Fabrice Marsaud
c6b0841583 Plugin configuration panel 2015-10-09 17:35:26 +02:00
Olivier Lambert
9312435076 Merge pull request #422 from vatesfr/pierre-cant-migrate-vm-to-current-host
Removed current host in migrate list. Disabled button when no other h…
2015-10-08 16:24:43 +02:00
Pierre
49427f1c54 Fix : condition for disabled button 2015-10-08 16:22:06 +02:00
Pierre
82e7e06dc4 Fix : button not diabled if host available on another pool 2015-10-08 16:03:08 +02:00
Pierre
76cf82bb19 Removed current host in migrate list. Disabled button when no other host available 2015-10-08 15:46:11 +02:00
Olivier Lambert
4f8ad2962e changelog updated 2015-10-08 15:22:27 +02:00
Olivier Lambert
fe4be48bff remove useless entries in the menu for hosts 2015-10-08 15:05:36 +02:00
Olivier Lambert
66fc0b421b fix tooltip as explained in #421 2015-10-08 15:03:50 +02:00
Olivier Lambert
c0b4867659 Merge pull request #420 from vatesfr/pierre-lone-host-cant-leave-pool
Added condition to remove 'remove host from pool'-button when host is…
2015-10-08 14:51:58 +02:00
Pierre
95253fbc76 Added condition to remove 'remove host from pool'-button when host is alone 2015-10-08 14:26:20 +02:00
Olivier Lambert
df519b3042 Merge pull request #418 from vatesfr/pierre-add-host-to-pool
Added button to merge host to another pool
2015-10-08 12:30:37 +02:00
Pierre
9ed963ef70 Modified icon for 'moving host to another pool' 2015-10-08 12:26:06 +02:00
Pierre
1dd7993e7a Added button to mode host to another pool. Doesn't work yet 2015-10-08 11:55:03 +02:00
Olivier Lambert
386b33b65d Merge pull request #411 from vatesfr/chartswitch
add chart selector for dataviz
2015-10-07 23:57:08 +02:00
florent
416deb8711 chart selector : less opacity and bigger font 2015-10-07 21:50:24 +02:00
florent
3c7fdac55e add navigation between dataviz
also : fix import in health, fix thumbnail size
2015-10-07 21:13:04 +02:00
Olivier Lambert
392a6af47f Merge pull request #415 from vatesfr/abhamonr-pass-generator-on-users
Password generation is implemented with password-generator module.
2015-10-07 17:30:16 +02:00
wescoeur
6b03e3f603 Password generation is implemented with password-generator module.
One button (with tooltip) is visible to the left of each password field.
One click on this button generate one visible password of 8 characters (upper/lower-case letters, underscore, numbers)
2015-10-07 17:24:09 +02:00
Olivier Lambert
9397f6beda minor changelog fix 2015-10-07 15:56:26 +02:00
Olivier Lambert
d17b386fd6 changelog updated 2015-10-07 15:44:50 +02:00
Olivier Lambert
f6d2e1a447 more explicit modal when host is removed from pool 2015-10-07 15:38:41 +02:00
Olivier Lambert
bd95ef5db6 Merge pull request #407 from vatesfr/desktop-notif
Use desktop notification when available and when the browser is minified
2015-10-07 15:13:13 +02:00
Olivier Lambert
6e76c621b8 Merge pull request #413 from vatesfr/pierre-unfocus-on-cd-list
Unfocusing CD list after selecting one
2015-10-07 15:08:11 +02:00
Olivier Lambert
3e58bee0eb Merge pull request #409 from vatesfr/abhamon-ronan-issue-396
Abhamon ronan issue 396
2015-10-07 14:46:25 +02:00
wescoeur
8c2ed1f581 Password viewer button added to right (with tooltip) and only displayed if input is not empty. 2015-10-07 14:41:31 +02:00
Pierre
500dd3bfaf Unfocusing CD list after selecting one 2015-10-07 12:04:35 +02:00
Olivier Lambert
bc7bacd654 remove Travis test on Node 0.10 2015-10-07 10:58:54 +02:00
florent
fa16b990b6 add chart selector for dataviz 2015-10-06 22:06:33 +02:00
Florent BEAUCHAMP
9d5e9dd9e5 Use desktop notification when avilable and when the browser is minified
without jquery, and using angular $window and $document
2015-10-06 20:37:56 +02:00
Olivier Lambert
4046f9dde1 restrict export button for admins only. Fix #410 2015-10-05 16:45:35 +02:00
Julien Fontanet
fcd82ada14 Fix coding styles and test on Travis. 2015-10-02 19:20:13 +02:00
Olivier Lambert
d616da7f67 Merge pull request #399 from vatesfr/circle
first integration of cricle chart
2015-10-01 21:45:43 +02:00
florent
0c81202bbb how circle chart and sunburst charts + bug fix
correct bug where wraptext was going wild with dimension less node
2015-10-01 20:53:43 +02:00
Julien Fontanet
6284bd3f17 Remove an unecessary console.log(). 2015-10-01 18:38:54 +02:00
Julien Fontanet
7adc9d94b4 Upgrade gulp-livereload to 3.8.1 to support npm 3. 2015-10-01 18:38:42 +02:00
florent
73e030d2f5 d3 circle : hide text if fully zoomed out 2015-09-30 22:07:19 +02:00
florent
3a3b45aa04 d3 charts : externalize a breadcrumbs directive
refactor sunburst and circle to use it
2015-09-30 22:00:39 +02:00
florent
81c19e9964 circle : hide virtual node ( like free ram or free disk) 2015-09-30 18:55:12 +02:00
florent
df856bc4a0 zoomable circle chart 2015-09-30 18:31:24 +02:00
florent
8558dc7ee4 first integration of cricle chart, no zoom
Drop in remplacement of sunburst or treemap
2015-09-30 15:14:42 +02:00
Fabrice Marsaud
087d5f6e58 4.6.1 2015-09-25 12:16:17 +02:00
Fabrice Marsaud
9540bc350a Fix a wrong deletion 2015-09-25 12:14:17 +02:00
Julien Fontanet
09153c6c30 Revert "Minor fix in release process."
This reverts commit 8dbab73d2b.

The release process was already correct.
2015-09-25 10:18:46 +02:00
Olivier Lambert
f66d81f147 4.6.0 2015-09-25 00:10:29 +02:00
Olivier Lambert
75925143b6 Merge pull request #395 from vatesfr/cubism
Multigraph, resolve issue #358
2015-09-24 21:37:28 +02:00
Florent BEAUCHAMP
e7dc00991e remove cubism from package.json 2015-09-24 21:26:45 +02:00
Florent BEAUCHAMP
dd9da82ed3 Merge branch 'cubism' of https://github.com/vatesfr/xo-web into cubism 2015-09-24 21:18:59 +02:00
Florent BEAUCHAMP
c995b8fa81 horizon: sort metrics name, rename cpu average to all cpus 2015-09-24 21:15:53 +02:00
Florent BEAUCHAMP
e7c2994ea3 horizon : delete last traces of cubism 2015-09-24 21:15:52 +02:00
Florent BEAUCHAMP
106997b26c horizon responsive 2015-09-24 21:15:51 +02:00
Florent BEAUCHAMP
fa842c1566 horizon : smaller label on date axis 2015-09-24 21:15:50 +02:00
Florent BEAUCHAMP
be03dd82f9 remove cubism 2015-09-24 21:15:49 +02:00
Florent BEAUCHAMP
39c46995e1 horizon : properly clean existing label 2015-09-24 21:15:48 +02:00
Florent BEAUCHAMP
97adc01e8d horizon : replace checkbox by button, remove bold 2015-09-24 21:15:47 +02:00
Florent BEAUCHAMP
36be881741 handle missing value 2015-09-24 21:15:45 +02:00
Florent BEAUCHAMP
955cc6dff5 horizon : add filter on hover 2015-09-24 21:15:44 +02:00
Florent BEAUCHAMP
2be1399eda horizon : add label, center label vertically, add a checkbox to
synchronize scales
2015-09-24 21:15:43 +02:00
Olivier Lambert
ef5d2a7654 change the panel title 2015-09-24 21:09:56 +02:00
Florent BEAUCHAMP
1cd00cab62 horizon : synchronize scales 2015-09-24 21:09:54 +02:00
Olivier Lambert
7652c231f6 case for foreach 2015-09-24 21:09:53 +02:00
Florent BEAUCHAMP
e17cdf0ca7 horizon : over text lighter 2015-09-24 21:09:51 +02:00
Florent BEAUCHAMP
3317791e68 rizons : two numbers 2015-09-24 21:09:50 +02:00
Florent BEAUCHAMP
c3871bc2ec Horizon : hide cpu x in metrics list 2015-09-24 21:09:49 +02:00
Florent BEAUCHAMP
ebba86f741 horizon : add label before chart
better handling of form when loading new metrics
2015-09-24 21:09:47 +02:00
Florent BEAUCHAMP
5ac84a6a02 replace cubism with a custom implementation 2015-09-24 21:09:46 +02:00
Florent BEAUCHAMP
cf3e9704e8 wip 2015-09-24 21:09:44 +02:00
Florent BEAUCHAMP
37eac8afcf formatting 2015-09-24 21:09:43 +02:00
Florent BEAUCHAMP
692a0535ff move cubism from dataviz to health 2015-09-24 21:09:42 +02:00
Olivier Lambert
0f0d804052 fix a typo in clonedeep import and save the depencency 2015-09-24 21:09:40 +02:00
florent
d189e6b53d first integration of cubism 2015-09-24 21:08:56 +02:00
Florent BEAUCHAMP
5da31691a9 horizon: sort metrics name, rename cpu average to all cpus 2015-09-24 21:02:34 +02:00
Florent BEAUCHAMP
4059a4fd9a horizon : delete last traces of cubism 2015-09-24 20:47:20 +02:00
Florent BEAUCHAMP
e56da71856 horizon responsive 2015-09-24 20:40:15 +02:00
Florent BEAUCHAMP
91e10f627f horizon : smaller label on date axis 2015-09-24 20:04:52 +02:00
Florent BEAUCHAMP
338c686e8d remove cubism 2015-09-24 19:53:31 +02:00
Florent BEAUCHAMP
0007e9ea2b horizon : properly clean existing label 2015-09-24 19:50:08 +02:00
Florent BEAUCHAMP
1e09e9b322 horizon : replace checkbox by button, remove bold 2015-09-24 19:13:48 +02:00
Florent BEAUCHAMP
43c358119a handle missing value 2015-09-24 18:48:03 +02:00
Florent BEAUCHAMP
7a0f251ebd horizon : add filter on hover 2015-09-24 18:36:17 +02:00
Florent BEAUCHAMP
e989321c5f Merge branch 'cubism' of https://github.com/vatesfr/xo-web into cubism 2015-09-24 18:12:42 +02:00
Florent BEAUCHAMP
56f27e6aaa horizon : add label, center label vertically, add a checkbox to
synchronize scales
2015-09-24 18:08:36 +02:00
Julien Fontanet
7c4e5aa667 Move lodash.debounce to dev deps. 2015-09-24 16:48:12 +02:00
Julien Fontanet
d253d826bb Update xo-lib to 0.7.3 to support Node 4. 2015-09-24 16:47:57 +02:00
Olivier Lambert
888fa20ca3 update the changelog 2015-09-24 12:13:31 +02:00
Julien Fontanet
598dbb2b7a Typos. 2015-09-24 11:37:02 +02:00
Julien Fontanet
71eb1eab14 Properly handles invalid auth tokens. 2015-09-24 11:09:22 +02:00
Olivier Lambert
62a6bd99e8 Merge pull request #393 from vatesfr/issue#237
Rely on unified server-side VM migrate
2015-09-24 10:27:29 +02:00
Olivier Lambert
174cdf2149 fix issue #394 2015-09-24 10:24:10 +02:00
Olivier Lambert
5267fbce7b change the panel title 2015-09-23 23:00:18 +02:00
Fabrice Marsaud
dd814e7e95 removed late migrate notification 2015-09-23 22:41:50 +02:00
Florent BEAUCHAMP
f806b45d3d Merge branch 'cubism' of https://github.com/vatesfr/xo-web into cubism 2015-09-23 22:35:30 +02:00
Florent BEAUCHAMP
8575e9eabe horizon : synchronize scales 2015-09-23 22:34:50 +02:00
Olivier Lambert
6ff9e22049 case for foreach 2015-09-23 22:18:42 +02:00
Fabrice Marsaud
caa86fdab7 Rely on unified server-side VM migrate 2015-09-23 22:15:19 +02:00
Florent BEAUCHAMP
48246716cc horizon : over text lighter 2015-09-23 22:04:29 +02:00
Florent BEAUCHAMP
6e07429e8a rizons : two numbers 2015-09-23 22:01:41 +02:00
Florent BEAUCHAMP
1a271c32b6 Horizon : hide cpu x in metrics list 2015-09-23 21:57:07 +02:00
Florent BEAUCHAMP
3fddec8f20 horizon : add label before chart
better handling of form when loading new metrics
2015-09-23 21:42:11 +02:00
Florent BEAUCHAMP
ac3944aece replace cubism with a custom implementation 2015-09-23 20:59:25 +02:00
Florent BEAUCHAMP
958cc2a50c wip 2015-09-22 23:17:32 +02:00
Florent BEAUCHAMP
058dfcfa9f Merge branch 'cubism' of https://github.com/vatesfr/xo-web into cubism 2015-09-22 19:09:35 +02:00
Florent BEAUCHAMP
9dbb1ca386 formatting 2015-09-22 19:06:01 +02:00
Florent BEAUCHAMP
4d1def6e9d move cubism from dataviz to health 2015-09-22 18:51:51 +02:00
Olivier Lambert
ff763b0278 fix a typo in clonedeep import and save the depencency 2015-09-22 13:28:04 +02:00
florent
74f611e0fd first integration of cubism 2015-09-22 09:23:28 +02:00
Olivier Lambert
61f8be1c60 Merge pull request #388 from vatesfr/issue#222
Filter for SR view VDIs #222
2015-09-17 18:01:41 +02:00
Olivier Lambert
96b18dab00 just add a placeholder in the filter for VDIs 2015-09-17 18:01:12 +02:00
Fabrice Marsaud
0a21b239bc Filter for SR view VDIs #222 2015-09-17 17:51:55 +02:00
Olivier Lambert
9c3589aea4 add PV args during VM creation. Fix #112 2015-09-17 16:36:14 +02:00
Olivier Lambert
2433485d13 Merge pull request #387 from vatesfr/issue#350
Issue#350
2015-09-17 14:56:21 +02:00
Fabrice Marsaud
6b5f254e0a Confirmations on tree and SR view 2015-09-17 14:51:33 +02:00
Fabrice Marsaud
e1b41b1e26 Additional confirmations for console view 2015-09-17 14:51:33 +02:00
Fabrice Marsaud
bd7a265df0 Additional confirmations for VM view 2015-09-17 14:51:33 +02:00
Olivier Lambert
039cca9529 Merge pull request #385 from vatesfr/issue#383
Html fixes so form required are no more ignored
2015-09-17 12:35:06 +02:00
Olivier Lambert
963347dbc2 Merge pull request #381 from vatesfr/issue#107
noVnc connection reset on disconnection
2015-09-17 12:19:19 +02:00
Fabrice Marsaud
697cc9f758 Html fixes so form required are no more ignored 2015-09-17 11:00:24 +02:00
Fabrice Marsaud
3892225584 Limited retries to reset consoles after reboot or halt or whatever disconnects... 2015-09-17 10:52:08 +02:00
Fabrice Marsaud
a7880a0ef5 noVnc connection reset on disconnection 2015-09-17 10:52:08 +02:00
Fabrice Marsaud
dd574830f5 Merge pull request #380 from vatesfr/issue#367
Tag management in xo-web
2015-09-17 10:30:10 +02:00
Fabrice Marsaud
71a0d15c35 Tag directive ensures an up to date tag display 2015-09-17 10:25:58 +02:00
Fabrice Marsaud
8a33c4f09a Various enhancements of the tag directive 2015-09-17 10:25:57 +02:00
Fabrice Marsaud
d223ce062a tags for VDIs. A refresh problem in VM view 2015-09-17 10:25:57 +02:00
Fabrice Marsaud
39c8f12963 Fixes for tag management 2015-09-17 10:25:57 +02:00
Fabrice Marsaud
bd4ba8c826 Autofocus, and smaller + button 2015-09-17 10:25:57 +02:00
Fabrice Marsaud
3d38c8e088 Add tag module 2015-09-17 10:25:57 +02:00
Fabrice Marsaud
ce58c80c6d Tag management UI for issue#367 2015-09-17 10:25:57 +02:00
Julien Fontanet
19b3a0781c Deps update. 2015-09-17 10:12:02 +02:00
Olivier Lambert
b42c1971b9 Merge pull request #382 from vatesfr/issue#323
Issue#323
2015-09-16 18:45:14 +02:00
Olivier Lambert
02440941e0 homogenize by using spaces vs nbsp 2015-09-16 17:58:27 +02:00
Olivier Lambert
cd2f986c50 fix a missing space in the list view 2015-09-16 17:56:22 +02:00
Olivier Lambert
e7cbd6b31f Update CHANGELOG.md
Add the fix pushed in the 4.5.1
2015-09-16 15:09:34 +02:00
Fabrice Marsaud
a7f6d5eebd Better order for acl management 2015-09-16 13:45:48 +02:00
Fabrice Marsaud
4f3b8c0906 Acls are editable 2015-09-16 13:45:48 +02:00
Fabrice Marsaud
7126c71943 Merge branch 'master' into next-release 2015-09-16 13:42:27 +02:00
Fabrice Marsaud
489cf16af8 4.5.1 2015-09-16 12:05:00 +02:00
Fabrice Marsaud
b012f44259 Merge pull request #379 from vatesfr/issue378
Fixes issue#378 and adds some cancel edit buttons on VM view. Ready for 4.5.1.
2015-09-16 12:03:00 +02:00
Fabrice Marsaud
5ce765bd27 Fixes issue#378 and adds some cancel edit buttons on VM view 2015-09-16 11:44:46 +02:00
Olivier Lambert
2450edd070 another minor UI fix 2015-09-15 20:01:54 +02:00
Olivier Lambert
fc2a61835c fix edge cases display of tools outdated but installed 2015-09-14 22:12:34 +02:00
Fabrice Marsaud
d06d73d5f7 Merge pull request #376 from vatesfr/issue#362
Any user can manage his password himself
2015-09-14 17:02:10 +02:00
Fabrice Marsaud
2c10996bb3 UI terms 2015-09-14 16:13:33 +02:00
Fabrice Marsaud
b9c85bb1bf Rework on password change 2015-09-14 15:36:22 +02:00
Fabrice Marsaud
f436afb9aa Any user can manage his password himself 2015-09-14 15:36:22 +02:00
Olivier Lambert
750efe4152 Merge pull request #377 from vatesfr/issue#342
Clearer tooltips for heatmap
2015-09-14 14:36:04 +02:00
Fabrice Marsaud
99cee95cd5 Clearer tooltips for heatmap 2015-09-14 14:24:10 +02:00
Olivier Lambert
ec10b84fa6 add new favicon. Fix #369 2015-09-11 22:16:35 +02:00
Olivier Lambert
f0442fe2ce spaces between tags in flat view 2015-09-11 22:16:06 +02:00
Olivier Lambert
7907969696 Merge pull request #372 from vatesfr/issue#368
Fix update panel for issue#368
2015-09-11 18:08:59 +02:00
Julien Fontanet
8dbab73d2b Minor fix in release process. 2015-09-11 18:04:18 +02:00
Fabrice Marsaud
ade8acb4e2 Fix update panel for issue#368 2015-09-11 15:54:08 +02:00
Fabrice Marsaud
9cb78e6954 Fix for Issue#357 2015-09-11 15:34:17 +02:00
Julien Fontanet
e9127bdbb3 4.5.0 2015-09-11 12:59:00 +02:00
Olivier Lambert
9b750bc756 add missing stuff in the changelog 2015-09-10 17:32:22 +02:00
Fabrice Marsaud
c3349e8cc7 Merge pull request #366 from vatesfr/issue#268
Issue#268
2015-09-10 17:24:58 +02:00
Olivier Lambert
f4d7c7f739 hide other buttons 2015-09-10 17:24:55 +02:00
Olivier Lambert
19d51cb1a4 remove a typo 2015-09-10 17:24:55 +02:00
Olivier Lambert
cc9983aa16 hide actions for a viewer and fix others 2015-09-10 17:24:55 +02:00
Fabrice Marsaud
81f8467f66 ng-if solution for unauthorized action button and links 2015-09-10 17:24:55 +02:00
Fabrice Marsaud
df6b23e3c7 disable solution. not very satisfying 2015-09-10 17:24:55 +02:00
Fabrice Marsaud
4dd81e7d59 Merge pull request #365 from vatesfr/issue#330
Issue#330
2015-09-10 17:22:27 +02:00
Fabrice Marsaud
54ce7067b4 Nan and data type bugs fixed 2015-09-10 17:22:32 +02:00
Fabrice Marsaud
2673f790e6 Issue #330 enhanced heatmap, first delivery 2015-09-10 17:22:32 +02:00
Fabrice Marsaud
69bea2ec9b Merge pull request #364 from vatesfr/issue#315
Issue#315
2015-09-10 16:18:58 +02:00
Fabrice Marsaud
37037cf797 Fixed "onlymetadata" backup bugs 2015-09-10 15:59:36 +02:00
Fabrice Marsaud
2a1586aab3 Onlymetadata scheduled backups 2015-09-09 12:12:06 +02:00
florent
915281d138 cleanup and enlarge brush 2015-09-08 23:05:36 +02:00
florent
b53a179ea0 correctly invoke parallel chart + readme + add drag filter 2015-09-08 22:56:32 +02:00
Olivier Lambert
7077e8b50e Merge pull request #359 from vatesfr/dynamic-charts
Dynamic charts
2015-09-08 13:16:57 +02:00
florent
d9181277d9 correct wrong throttle/debounce use * 2015-09-08 12:18:27 +02:00
florent
810c2d6a1a listen to xoApi.onUpdate event to refresh charts, throttle refresh to once every 300ms 2015-09-08 12:18:08 +02:00
Olivier Lambert
f31113fb90 change panel name 2015-09-08 12:08:01 +02:00
florent
1702b9dd37 force domain to start at zero 2015-09-08 12:06:13 +02:00
florent
f8e61c713c first implementation of parrallel chart directive 2015-09-08 12:05:16 +02:00
Julien Fontanet
643132754a xoApi.onUpdate(fn) 2015-09-07 14:10:51 +02:00
Olivier Lambert
4f0a131bd2 fix #341 2015-09-07 13:13:08 +02:00
Olivier Lambert
52aa0350cf update the changelog 2015-09-07 13:12:56 +02:00
Olivier Lambert
c9884f32fe fix #355 2015-09-07 12:36:35 +02:00
Olivier Lambert
aea3ae3d37 fix #356 2015-09-07 12:36:23 +02:00
Olivier Lambert
10f7c3045f fix #348 2015-09-03 15:15:51 +02:00
Olivier Lambert
bf4e158c30 fix #347 2015-09-03 14:48:08 +02:00
Olivier Lambert
4a3155ed22 Merge pull request #346 from vatesfr/performance
Performance improvment
2015-09-02 21:38:03 +02:00
florent
29f1c89fa5 fix sunburst layout on FF/IE 2015-09-02 21:08:54 +02:00
florent
4a92e8a99f improve performance
make suburst graph static and synchronize animation
2015-09-02 20:40:32 +02:00
Olivier Lambert
c6cffb1156 add metadata export in the VM view 2015-09-02 17:15:10 +02:00
Olivier Lambert
f6e4e59905 add outdate tools info in VM view 2015-09-02 15:55:20 +02:00
Olivier Lambert
3a0736c4bf fix #304 2015-09-02 15:43:05 +02:00
Julien Fontanet
47455b2029 Merge pull request #344 from vatesfr/coding-standard
Fix coding style of the dashboard.
2015-09-02 11:24:24 +02:00
Julien Fontanet
05eb7d765f Coding style fixes. 2015-09-02 11:12:37 +02:00
Julien Fontanet
5e786686d0 Remove unecessary dep. 2015-09-02 10:29:40 +02:00
florent
5cb8e3a7c3 comply to coding standards 2015-09-01 20:18:10 +02:00
Fabrice Marsaud
84bd077eac 4.4.0 2015-08-28 17:38:23 +02:00
Julien Fontanet
db39b27119 Fix home page on sign in. 2015-08-28 17:14:59 +02:00
Julien Fontanet
f2d2b35543 Merge pull request #335 from vatesfr/passport
Authentication is moved to xo-server.
2015-08-28 16:09:59 +02:00
Julien Fontanet
5dfd5766f2 Authentication is moved to xo-server. 2015-08-28 15:04:47 +02:00
Fabrice Marsaud
0e4c3e1e92 CSS tree view fixes 2015-08-28 10:53:13 +02:00
Fabrice Marsaud
221f42606c Fixes from Issue#329 2015-08-28 10:45:20 +02:00
Olivier Lambert
742f092ed3 add CSS flexbox issue in the changelog 2015-08-28 10:37:23 +02:00
Fabrice Marsaud
36bffa1475 gulpFilter 3.x 2015-08-28 09:22:35 +02:00
Fabrice Marsaud
936abc1b1a Fix for undefined VDIs (base copy) 2015-08-27 23:45:25 +02:00
Fabrice Marsaud
584bdd545f icon fix 2015-08-27 22:38:12 +02:00
Fabrice Marsaud
99debc18d7 Heatmap modifs 2015-08-27 21:41:34 +02:00
Olivier Lambert
56b896eda0 add better title 2015-08-27 18:27:44 +02:00
Fabrice Marsaud
cab102528d add omitted template 2015-08-27 18:24:29 +02:00
Fabrice Marsaud
1875cdcda2 Imporvements 2015-08-27 18:22:28 +02:00
Fabrice Marsaud
386dcc8d43 Host heatmap seems ok 2015-08-27 17:55:21 +02:00
Fabrice Marsaud
e6d59a47b1 Unstable progress 2015-08-27 17:01:25 +02:00
Fabrice Marsaud
2659393f33 First heatmap correct integration 2015-08-27 17:01:25 +02:00
Fabrice Marsaud
916b2363d9 Fixed storage dataviz (avoid hidden base copy vdis) 2015-08-27 17:01:25 +02:00
Olivier Lambert
bb513790b5 add changelog for 4.4 2015-08-27 17:01:25 +02:00
Olivier Lambert
e5ab15a727 remove permission for dashboard view if non admin 2015-08-27 17:01:25 +02:00
Fabrice Marsaud
bbaa750fda css touches
Conflicts:
	app/modules/dashboard/dataviz/view.jade
2015-08-27 17:01:25 +02:00
Fabrice Marsaud
a46e19210a css touch 2015-08-27 17:01:25 +02:00
Olivier Lambert
9d6772edd1 disable dashboard link menu 2015-08-27 17:01:25 +02:00
Olivier Lambert
3aeaa564a2 remove useless treemap 2015-08-27 17:01:25 +02:00
Olivier Lambert
8baad494e3 minor fixes 2015-08-27 17:01:25 +02:00
florent
fb04753d52 doc dor heatmap 2015-08-27 17:01:25 +02:00
florent
ea37c4ccd8 backport some correction to sunburst charts 2015-08-27 17:01:25 +02:00
florent
80f02b52e1 a littel doc for sunburst 2015-08-27 17:01:25 +02:00
florent
55464845d6 week heatmap 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
8aef4bb455 treemap directive 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
d8a2adbca2 dataviz refacto : split megacronlller in two bigcontroller 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
02e56da08a more transition, less console.log 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
9d9f857e73 sunburst : transition 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
b140c1e65f dataviz > overview : show shared SRS 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
81ff03462e dataviz breadcrumbs are full width 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
2ea7c09c84 dataviz : add cursor pointer if applicable 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
4163ed212c make dataviz clickable 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
c83722c2df dataviz : correct typo in storage sums 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
700db655e6 dataviz : storage is now pool > host > SRS > VDI
hide .iso srs
2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
226428f631 dataviz : replace cpu view by storage view pool>SRS>vdi 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
e2b293e49b no udev in overview list 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
1f6e9d4660 directive - less border 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
ecf2ee888f dataviz : remove logarithmic scale 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
1e8eeadb1d switch to a logarithm scale for ram 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
e7ceccdd83 dataviz : do not show "virtual pool" for poolless host 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
5390b4a4b3 oups, wrong CPU count 2015-08-27 17:01:25 +02:00
Florent BEAUCHAMP
f25ec34bc3 show free ram and cpu 2015-08-27 17:01:24 +02:00
Florent BEAUCHAMP
53ece86816 don't show iso in overview > SR. Order SR by usage ratio desc 2015-08-27 17:01:24 +02:00
Florent BEAUCHAMP
9b128cdfcc add comprehensible tooltip on overview > ram 2015-08-27 17:01:24 +02:00
Florent BEAUCHAMP
c047386755 sunburst : add text info in the center 2015-08-27 17:01:24 +02:00
Florent BEAUCHAMP
b0ffb272b3 sunburst : border width is coherent with graph width 2015-08-27 17:01:24 +02:00
Olivier Lambert
956e21c8db code style now using JS standard 2015-08-27 17:01:24 +02:00
Florent BEAUCHAMP
95057a2b09 use the right visualization 2015-08-27 17:01:24 +02:00
Florent BEAUCHAMP
788aa24a80 overview with real data 2015-08-27 17:01:24 +02:00
Florent BEAUCHAMP
0a72ef91cc less opacity to non hovered sunburst node. text ellipsis in breadcrumbs 2015-08-27 17:01:24 +02:00
Florent BEAUCHAMP
f0f4e0985a sunburst directive : externalized in a module, react to vm changes 2015-08-27 17:01:24 +02:00
Florent BEAUCHAMP
a6bedea4b6 sunburst are responsive 2015-08-27 17:01:24 +02:00
Florent BEAUCHAMP
055316e1ca multiple synchronizated sunburst + breadcrumbs 2015-08-27 17:01:24 +02:00
Olivier Lambert
25fece5947 add initial d3 work of FBP 2015-08-27 17:01:24 +02:00
Olivier Lambert
5ec3cdbcc5 health page mockup 2015-08-27 17:01:24 +02:00
Olivier Lambert
b530ab2ef6 clean copy/pasted code 2015-08-27 17:01:24 +02:00
Olivier Lambert
5164f60c98 Ui fixes for dashboard 2015-08-27 17:01:24 +02:00
Olivier Lambert
19ba3015f7 not responsive graph to avoid a display bug 2015-08-27 17:01:24 +02:00
Olivier Lambert
fb00b2672c start dashboard mockup 2015-08-27 17:01:24 +02:00
Fabrice Marsaud
3e0f936d2a host refreshStats fix 2015-08-27 16:47:58 +02:00
Fabrice Marsaud
f5be146dbb refreshStats fix 2015-08-27 10:01:44 +02:00
Olivier Lambert
95431a0874 disable the scheduler for non admin users 2015-08-26 16:53:15 +02:00
Julien Fontanet
f76b130ca4 Coding style fixes. 2015-08-26 15:03:48 +02:00
Julien Fontanet
d19f8259d0 Upgrade deps. 2015-08-26 15:03:35 +02:00
Julien Fontanet
68cd62d756 Scheduler view is only accessible to admins. 2015-08-26 11:08:58 +02:00
Fabrice Marsaud
ea4a55d3dd grid css enhanced 2015-08-25 19:11:28 +02:00
Fabrice Marsaud
788bdcd35b menu-entry class is back 2015-08-24 18:20:38 +02:00
Fabrice Marsaud
ebd7e24830 group icon fix 2015-08-24 17:21:22 +02:00
Fabrice Marsaud
0b7fbffa0a Icons and styles enhanced a bit 2015-08-24 17:02:14 +02:00
Fabrice Marsaud
2afda9a055 form template touch 2015-08-24 17:02:14 +02:00
Fabrice Marsaud
28dd275bd8 Honest responsive grid 2015-08-24 17:02:14 +02:00
Fabrice Marsaud
9a3cf182ac side menu and other enhancements 2015-08-24 17:02:14 +02:00
Fabrice Marsaud
dd278c28be first steps 2015-08-24 17:02:14 +02:00
Fabrice Marsaud
37572122b0 Reload prompt after upgrade 2015-08-24 16:55:04 +02:00
Fabrice Marsaud
5348f75b5e ACL enhanced object selector 2015-08-10 18:43:44 +02:00
Fabrice Marsaud
6b8873d385 Updater flow and display fixed 2015-07-31 09:27:06 +02:00
Fabrice Marsaud
8db18d87e5 Minor vocabulary fix 2015-07-29 15:18:26 +02:00
Fabrice Marsaud
444920d15c 4.3.0 2015-07-22 15:48:52 +02:00
Fabrice Marsaud
8aa2fab603 Merge branch 'next-release' 2015-07-22 15:39:36 +02:00
Olivier Lambert
9ded4386cc Update CHANGELOG.md 2015-07-22 15:16:13 +02:00
Fabrice Marsaud
cc6b1b5aa1 Fixed delete all host logs 2015-07-22 15:05:54 +02:00
Olivier Lambert
74f20da82f Merge pull request #305 from vatesfr/issue#301
GUI fix for vm delete
2015-07-21 14:34:59 +02:00
Olivier Lambert
4c9c838b70 Merge pull request #300 from vatesfr/marsaud-backup
Scheduled VM Backup feature
2015-07-21 14:34:51 +02:00
Fabrice Marsaud
9a9d27d37a GUI fix for vm delete 2015-07-21 14:26:07 +02:00
Fabrice Marsaud
0347d4cec4 Rolling backup feature 2015-07-21 14:14:15 +02:00
Fabrice Marsaud
b1b189288e Fix issue #302 2015-07-21 14:11:24 +02:00
Fabrice Marsaud
b3220f981b GUI fix for vm delete 2015-07-20 19:29:53 +02:00
Julien Fontanet
a5573e62c6 Update deps. 2015-07-15 09:47:01 +02:00
Fabrice Marsaud
c4ccee8df6 4.2.0 2015-06-29 09:18:43 +02:00
Fabrice Marsaud
fbcf803d06 Merge branch 'next-release' 2015-06-29 09:16:31 +02:00
Olivier Lambert
5247b7a9af Update CHANGELOG.md 2015-06-27 22:42:39 +02:00
Fabrice Marsaud
dc218cc992 rollingSnap view clean 2015-06-26 14:39:42 +02:00
Fabrice Marsaud
c21761d9d4 Merge pull request #289 from vatesfr/scheduler#176
Scheduler#176
2015-06-26 14:24:38 +02:00
Fabrice Marsaud
36c0bf06d7 Rolling VM Snapshot scheduling feature 2015-06-26 12:35:02 +02:00
Fabrice Marsaud
ccdab2b083 Fixed VM snapshots pagination 2015-06-26 12:34:35 +02:00
Fabrice Marsaud
15a8a56807 Fixes service link 2015-06-26 09:47:35 +02:00
Fabrice Marsaud
385d42281b Revert "This code should now be unnecessary."
This reverts commit 5f9cf47003.
2015-06-25 17:29:20 +02:00
Julien Fontanet
b0dc933021 Do not use babel-plugin-closure-elimination. 2015-06-23 15:01:49 +02:00
Olivier Lambert
b73ee1f638 Merge pull request #293 from vatesfr/SRViewFix
Fixed VM display on SR view
2015-06-22 10:40:23 +02:00
Fabrice Marsaud
51c2a54179 Fixed VM display on SR view 2015-06-22 10:25:44 +02:00
Julien Fontanet
2d71a916a2 Update vinyl to 0.5. 2015-06-15 16:47:56 +02:00
Julien Fontanet
5f9cf47003 This code should now be unnecessary. 2015-06-15 15:15:12 +02:00
Julien Fontanet
16b39185dc Fix coding style in gulpfile.js 2015-06-15 14:29:45 +02:00
Julien Fontanet
6f0410f26e Fix Angular to 1.4.x.
Angular's devs tend to break API on minor versions.
2015-06-15 10:58:18 +02:00
Julien Fontanet
0b86845852 Add Babel plugin for closure elimination (disabled for now). 2015-06-11 08:54:58 +02:00
Julien Fontanet
d5f914bd2f Do not distribute examples & tests. 2015-06-11 08:54:58 +02:00
Julien Fontanet
663c65e42e Babel configuration. 2015-06-11 08:54:58 +02:00
Julien Fontanet
b9de86f96c Minor fix in EditorConfig. 2015-06-11 08:54:58 +02:00
Olivier Lambert
bd9c0ffb25 4.1.0 2015-06-10 16:59:17 +02:00
Olivier Lambert
9d763773cf 4.1.0 2015-06-10 16:58:48 +02:00
Olivier Lambert
540f977146 Update CHANGELOG.md 2015-06-10 16:24:33 +02:00
Olivier Lambert
d16b09d3fc fix issue #287 2015-06-10 16:22:35 +02:00
Olivier Lambert
6f8a8d3b90 Update CHANGELOG.md 2015-06-10 13:19:02 +02:00
Olivier Lambert
00ef4166c7 fix #286 2015-06-10 10:35:56 +02:00
Olivier Lambert
b88414735e minor style modification for drag'n drop 2015-06-09 13:31:53 +02:00
Fabrice Marsaud
af092fae9b Fixed missing anguler injection 2015-06-09 12:58:11 +02:00
Fabrice Marsaud
b889efc913 migratePool fallback for dragNdrop migration 2015-06-09 12:58:11 +02:00
Fabrice Marsaud
877dd68a6b Final drag style 2015-06-09 12:58:11 +02:00
Fabrice Marsaud
2805a1c7bc A bit of style 2015-06-09 12:58:11 +02:00
Fabrice Marsaud
c5c000ea6f Work in progress 2015-06-09 12:58:11 +02:00
Fabrice Marsaud
673f1072bf Minor fix 2015-06-09 12:58:11 +02:00
Fabrice Marsaud
d0e93b9b9f Drag & drop VM migration in progress 2015-06-09 12:58:11 +02:00
Olivier Lambert
f239088bcb fix #270 2015-06-08 20:49:10 +02:00
Fabrice Marsaud
32642f105c Groups UI enhancement 2015-06-08 16:50:04 +02:00
Fabrice Marsaud
4adaf6d355 Group panel allows ACL suppression, and user search for add 2015-06-08 14:21:09 +02:00
Fabrice Marsaud
291e2a5e40 group: direct user add, without button 2015-06-08 13:34:26 +02:00
Fabrice Marsaud
05bdb56203 Minor code fix 2015-06-08 11:59:29 +02:00
Fabrice Marsaud
cb71df8345 Group view improvements 2015-06-08 11:54:03 +02:00
Fabrice Marsaud
c6c5f5188b Group dedicated edit page with acl recap 2015-06-05 15:29:34 +02:00
Fabrice Marsaud
a7b6ca0914 Fix for Issue #272 hide non auth objects 2015-06-04 17:16:49 +02:00
Fabrice Marsaud
30ba062695 Fixes for issue #271 newvm 2015-06-04 09:17:10 +02:00
Fabrice Marsaud
a595af7b3f vm controller clean up 2015-06-03 17:51:57 +02:00
Fabrice Marsaud
b2ee3172d8 console view data behaves ok on browser refresh 2015-06-03 17:45:23 +02:00
Fabrice Marsaud
73992ee8e9 console iso-device integration 2015-06-03 17:28:24 +02:00
Fabrice Marsaud
78885fd00a Fixed iso-device bug 2015-06-03 17:27:53 +02:00
Fabrice Marsaud
ce55ac6ccb Reworked iso-device module and integration on vm view 2015-06-03 17:01:30 +02:00
Fabrice Marsaud
8ce0951e5f Fix Issue #271 2015-06-03 14:53:42 +02:00
Julien Fontanet
7788fa9d3e Use standard with babel-eslint. 2015-06-03 09:40:38 +02:00
Julien Fontanet
7f36552c71 Release process. 2015-06-03 09:40:38 +02:00
Fabrice Marsaud
16f9437b29 Solving Issue #269 2015-06-02 15:51:40 +02:00
Julien Fontanet
0beaff718e Update release process. 2015-06-01 16:18:31 +02:00
Olivier Lambert
9b6f37b5d0 add changelog for patch releases 2015-06-01 16:16:08 +02:00
Olivier Lambert
3d6d4aea6a 4.0.2 2015-06-01 10:26:34 +02:00
Fabrice Marsaud
2356a21e54 Fix for Issue #264 field resets whil editing users 2015-06-01 09:32:20 +02:00
Olivier Lambert
a55e7ed34f 4.0.1 2015-05-30 18:35:34 +02:00
Fabrice Marsaud
e355e4d35d Fixed xoa-updater-service url 2015-05-30 14:24:57 +02:00
Julien Fontanet
6dcaf80f3f 4.0.0 2015-05-29 16:14:34 +02:00
Julien Fontanet
a465114d36 Remove unused imports & coding style. 2015-05-29 16:11:59 +02:00
Julien Fontanet
07fbcb3488 Various changes. 2015-05-29 16:02:54 +02:00
Julien Fontanet
534fbe1b6e Force Angular 1.4. 2015-05-29 16:02:45 +02:00
Julien Fontanet
f5c9c1ba0e Fix networks on new VM page on first load. 2015-05-29 16:02:15 +02:00
Julien Fontanet
5d5485f569 Fix host patches pagination. 2015-05-29 15:25:09 +02:00
Julien Fontanet
3d3fa5d18a Work around an Angular issue. 2015-05-29 14:20:05 +02:00
Julien Fontanet
312c41f229 Fix CD handling on VM page. 2015-05-29 14:04:10 +02:00
Fabrice Marsaud
2df1dc9028 Fix disks reordering on VM page. 2015-05-29 13:30:21 +02:00
Olivier Lambert
222f245e63 workaround with ng if 2015-05-29 13:29:52 +02:00
Olivier Lambert
2aa7702aed ng-if breaks the scope, replaced by ng-show 2015-05-29 13:15:16 +02:00
Olivier Lambert
0b185c35c2 restore UUID display 2015-05-29 12:19:41 +02:00
Fabrice Marsaud
48dcec3cc3 Separated user and group managment 2015-05-29 12:11:26 +02:00
Fabrice Marsaud
8567179fa3 reset ACL form after creation 2015-05-29 11:44:29 +02:00
Fabrice Marsaud
79d15ecd7e Sort user/group edit by id to avoid jumps while editing 2015-05-29 11:37:27 +02:00
Fabrice Marsaud
837c7e4bc7 Fix a missing loading anim when creating iSCSI SR 2015-05-29 11:30:51 +02:00
Fabrice Marsaud
2ae7e9920d Fix NFS_ISO missing settings for SR creation 2015-05-29 11:19:41 +02:00
Julien Fontanet
8cf955b674 Minor code simplification. 2015-05-29 10:50:35 +02:00
Julien Fontanet
33f897d43e Fix _gatherConnectedUuids(). 2015-05-29 10:45:32 +02:00
Fabrice Marsaud
ddb0946a0d Fixed UI bug on group user add 2015-05-29 08:30:34 +02:00
Fabrice Marsaud
0f5beac4a8 Fix group managment 2015-05-29 00:47:02 +02:00
Fabrice Marsaud
974e2f71f9 Roles added to ACLs 2015-05-28 23:58:36 +02:00
Fabrice Marsaud
3c427d7e28 acl work in progress 2015-05-28 23:26:03 +02:00
Fabrice Marsaud
0f10c8f5df bugfix on empty groups for settings/user 2015-05-28 21:50:49 +02:00
Olivier Lambert
7840b51f5c Update CHANGELOG.md 2015-05-28 19:27:18 +02:00
Olivier Lambert
6578855182 fix a bug with Other media install 2015-05-28 18:39:52 +02:00
Julien Fontanet
58d68497a4 Fixed width icons on ACLs subject selection. 2015-05-28 16:51:39 +02:00
Julien Fontanet
bddcf42a54 Fix access control to different pages. 2015-05-28 16:50:56 +02:00
Julien Fontanet
6318f4e7ac Correctly escape special chars in RegExp. 2015-05-28 16:20:37 +02:00
Julien Fontanet
0c6cced7ee Update xo-lib to 0.7.2. 2015-05-28 15:51:20 +02:00
Julien Fontanet
925bf47c9e Fix patches on host view. 2015-05-28 12:14:26 +02:00
Julien Fontanet
8472b991ff Only display running tasks on host view. 2015-05-28 12:06:56 +02:00
Julien Fontanet
ed59c32d96 Revert "Use lodash.slice instead of builtin method."
This reverts commit 1aaaee128f.

lodash.slice() incorrectly used on strings.
2015-05-28 11:54:27 +02:00
Julien Fontanet
b1981d7499 Various fixes. 2015-05-28 11:54:23 +02:00
Julien Fontanet
8983dfea57 Various fixes. 2015-05-28 10:58:24 +02:00
Julien Fontanet
5231b9b22b Again: UUID → id. 2015-05-28 10:25:04 +02:00
Julien Fontanet
55846a2314 Fix task.created date formatting. 2015-05-28 10:25:03 +02:00
Julien Fontanet
1c94f5749d Fix logs. 2015-05-28 10:25:03 +02:00
Fabrice Marsaud
90bacd9d31 VM Create Disk panal closes on success 2015-05-28 09:46:45 +02:00
Julien Fontanet
0053cbf782 No more UUID (→ id). 2015-05-27 19:44:28 +02:00
Julien Fontanet
5d120a79e8 Various fixes. 2015-05-27 19:42:31 +02:00
Julien Fontanet
3389569ea0 Various fixes. 2015-05-27 19:35:25 +02:00
Julien Fontanet
f546606de0 Various fixes. 2015-05-27 19:28:21 +02:00
Julien Fontanet
fef95b3aae Various fixes. 2015-05-27 19:21:49 +02:00
Julien Fontanet
5ba2b72439 Various fixes. 2015-05-27 18:59:15 +02:00
Julien Fontanet
4bb849f7c9 Updates xo-lib to v0.7.1. 2015-05-27 18:04:11 +02:00
Fabrice Marsaud
21b5e7e701 ACL Panel exposes groups 2015-05-27 17:20:42 +02:00
Julien Fontanet
34a1965497 Fix CoffeeScript indentation. 2015-05-27 17:19:31 +02:00
Julien Fontanet
1701682636 Rename filter: % → percentage. 2015-05-27 17:19:31 +02:00
Julien Fontanet
5d826972f1 Remove unused gulp-coffee. 2015-05-27 17:19:31 +02:00
Fabrice Marsaud
2467b336e5 Group managment in settings 2015-05-27 17:03:28 +02:00
Julien Fontanet
4f78414c7f Fixing host view… 2015-05-27 16:58:32 +02:00
Julien Fontanet
4532714bae Fix the tree view. 2015-05-27 16:36:01 +02:00
Olivier Lambert
352c23b0ba Update CHANGELOG.md 2015-05-27 15:25:38 +02:00
Julien Fontanet
8e432ee818 Global stats. 2015-05-27 15:18:49 +02:00
Julien Fontanet
47bb2d24f5 Avoid polluting the local scope in xo-api. 2015-05-27 14:24:55 +02:00
Julien Fontanet
f3fd4c607d Fix race condition in host view. 2015-05-27 14:24:32 +02:00
Julien Fontanet
0610ceafdf Remove unused $scope.removeMessage(). 2015-05-27 12:52:01 +02:00
Julien Fontanet
032fcdce40 Fix VDI handling in VM view. 2015-05-26 18:31:47 +02:00
Olivier Lambert
636bacd637 using standard style with a linter show that missing semicolon 2015-05-26 18:09:26 +02:00
Olivier Lambert
3f3fbd8bbc Update CHANGELOG.md 2015-05-26 10:48:34 +02:00
Fabrice Marsaud
955e88b4fb Fixed minor jade template error 2015-05-26 09:55:10 +02:00
Olivier Lambert
5954b552c9 fix patch issue 2015-05-25 15:31:58 +02:00
Julien Fontanet
aaad4c5d20 Fix production build (fix #261). 2015-05-25 10:21:45 +02:00
Julien Fontanet
a24c8526ea CSS sourcemaps only in dev mode! 2015-05-24 19:03:30 +02:00
Julien Fontanet
a533535520 Do not include CSS source maps in prod. 2015-05-24 18:47:38 +02:00
Julien Fontanet
badded3aa4 Inline external CSS. 2015-05-24 18:32:50 +02:00
Julien Fontanet
3055e612d4 Source maps for CSS. 2015-05-24 18:18:54 +02:00
Julien Fontanet
525cb1a2b6 Update deps. 2015-05-24 18:10:31 +02:00
Julien Fontanet
4dd70abc3b Remove a console.log. 2015-05-24 15:50:28 +02:00
Julien Fontanet
2ea4c214df Use more objects views and less the byType index. 2015-05-24 15:49:50 +02:00
Julien Fontanet
0a0174a79d New objects view: runningVms. 2015-05-24 15:21:14 +02:00
Julien Fontanet
3db031be1b Updates xo-collection to 0.3.2.. 2015-05-24 14:42:56 +02:00
Julien Fontanet
6d3a87fe7d New objects view: runningTasks. 2015-05-24 14:38:53 +02:00
Olivier Lambert
8cfd2cdd79 add host stats fix 2015-05-22 22:11:26 +02:00
Fabrice Marsaud
9e874e076f Stats stop if response exceeds 10 sec 2015-05-22 17:45:38 +02:00
Fabrice Marsaud
28192bf184 Simple x reattempt mode for stats 2015-05-22 17:35:53 +02:00
Julien Fontanet
a54957b4de Use $applyAsync() instead of $apply() for perf. 2015-05-22 15:16:02 +02:00
Julien Fontanet
f4b1a076b7 Better sync with xo-lib. 2015-05-22 15:15:39 +02:00
Julien Fontanet
27a3296d6e No need to use the UUID index. 2015-05-22 13:10:03 +02:00
Julien Fontanet
1aaaee128f Use lodash.slice instead of builtin method. 2015-05-22 12:12:39 +02:00
Julien Fontanet
15a16a2c35 Remove unnecessary modules. 2015-05-22 12:10:54 +02:00
Julien Fontanet
db23fe5a58 Fix SRs in list view. 2015-05-22 12:06:04 +02:00
Julien Fontanet
620c88b615 Move slice to xo-filters and use lodash.slice. 2015-05-22 12:05:49 +02:00
Julien Fontanet
99f2fb9764 Use views in list page. 2015-05-21 18:27:24 +02:00
Julien Fontanet
d5a3e67dbd Use more xo-collection/View in the tree page. 2015-05-21 17:57:27 +02:00
Julien Fontanet
55ef81f3e7 master-select module. 2015-05-21 17:48:48 +02:00
Julien Fontanet
41699fab1e Use lodash.clonedeep instead of angular.copy. 2015-05-21 17:48:48 +02:00
Julien Fontanet
32a1195157 Always use lodash.foreach. 2015-05-21 17:48:48 +02:00
Olivier Lambert
f53db2ddfa fix a display issue in general panel for VM view 2015-05-21 15:16:26 +02:00
Fabrice Marsaud
e060f9172b Trial handling removal 2015-05-21 11:16:46 +02:00
Fabrice Marsaud
4adef88e61 Minor update message fix 2015-05-21 11:14:26 +02:00
Fabrice Marsaud
d734f2cf89 Fixed host/patches panel 2015-05-21 10:41:35 +02:00
Fabrice Marsaud
3e81d14bd8 First delivery for pagination Issue #221 2015-05-21 09:09:14 +02:00
Olivier Lambert
e88a94d9e0 better UI for registration 2015-05-20 19:40:45 +02:00
Olivier Lambert
f4f16e4e87 updater UI fixes 2015-05-20 18:57:20 +02:00
Olivier Lambert
6268f3a3d9 small improvement for UI in vm creation 2015-05-20 18:00:50 +02:00
Fabrice Marsaud
06e7c8d19a VM logs are paginated 2015-05-20 17:50:46 +02:00
Olivier Lambert
32395232ea nicer vm creation view 2015-05-20 17:49:55 +02:00
Julien Fontanet
65d6ef91ff Fix defaultArgsMapper. 2015-05-20 17:19:41 +02:00
Julien Fontanet
4aecc875d1 Reorganize service. 2015-05-20 17:13:19 +02:00
Fabrice Marsaud
0e649a626c Handle unexisting boot order 2015-05-20 15:04:39 +02:00
Fabrice Marsaud
5fa249b0f3 Minor touch on boot reordering 2015-05-20 14:31:47 +02:00
Julien Fontanet
24ca86aad3 osFamily Angular filter. 2015-05-20 14:26:38 +02:00
Julien Fontanet
8a4f413289 TODO: deleteVms should be integrated in xo.deleteVms(). 2015-05-20 14:26:38 +02:00
Julien Fontanet
6dbad4501d Use ES6 export syntax. 2015-05-20 14:26:38 +02:00
Julien Fontanet
9ab6490fee Typo. 2015-05-20 14:26:38 +02:00
Julien Fontanet
a413efa550 Remove unnecessary values(). 2015-05-20 14:26:38 +02:00
Julien Fontanet
cd337d444c Minor improvement of xoApi.getObject(). 2015-05-20 14:26:38 +02:00
Julien Fontanet
45e1ce0a42 Fix global stats on the tree view. 2015-05-20 14:26:38 +02:00
Fabrice Marsaud
e5ef1e6efe First delivery for Issue #251 2015-05-20 14:05:06 +02:00
Julien Fontanet
b1ce3be3d2 Move consoles in /api/consoles/. 2015-05-20 10:28:52 +02:00
Olivier Lambert
e13ab73a29 stat changes 2015-05-19 21:19:18 +02:00
Fabrice Marsaud
aede952b12 Issue #253 2015-05-19 18:38:14 +02:00
Fabrice Marsaud
acc1476b29 Fixing Issue #250 2015-05-19 18:38:14 +02:00
Fabrice Marsaud
138bf56624 A fix on xoa-updater handling 2015-05-19 18:38:14 +02:00
Julien Fontanet
c608de4183 SPDX valid license. 2015-05-19 17:44:07 +02:00
Julien Fontanet
ccb6c02c31 Use more const. 2015-05-19 17:35:14 +02:00
Julien Fontanet
5cc457b28c Use xo-lib 0.7. 2015-05-19 16:55:54 +02:00
Julien Fontanet
a353b3d40d map Angular filter now based on lodash. 2015-05-19 16:54:10 +02:00
Julien Fontanet
6f7aca8e5b isEmpty Angular filter. 2015-05-19 16:53:50 +02:00
Julien Fontanet
92b0d4561e xo-web should only have dev deps. 2015-05-19 15:54:27 +02:00
Olivier Lambert
ef8b8346dc better UI for host 2015-05-19 14:55:10 +02:00
Fabrice Marsaud
058058a015 Updater rewritten with ws 2015-05-19 14:52:49 +02:00
Olivier Lambert
fddba7315a better recap for templates with disks 2015-05-18 20:57:18 +02:00
Olivier Lambert
a5e964ea19 start VM creation from template with disks 2015-05-18 20:39:48 +02:00
Julien Fontanet
3d2152e559 Fix relative URLs (fix #254). 2015-05-18 20:34:46 +02:00
Olivier Lambert
50f9c68c26 add spinner while fetching host stats 2015-05-18 17:13:04 +02:00
Olivier Lambert
b40207b367 start to add stats to host view 2015-05-18 17:13:04 +02:00
Julien Fontanet
6c9305d2b1 Fix unnecessary recompilation of app.js 2015-05-18 16:20:06 +02:00
Julien Fontanet
9fda3c911d Standard code style. 2015-05-18 16:13:36 +02:00
Olivier Lambert
473c3601ef better responsive tree menu for extra small devices 2015-05-18 12:38:38 +02:00
Olivier Lambert
fde8a3720d more responsive settings menu 2015-05-18 12:04:16 +02:00
Fabrice Marsaud
13a6d6b458 xoa-updater proxy settings when xoa-updater available 2015-05-18 09:26:39 +02:00
Olivier Lambert
29d9ba0446 some UI fixes for VM view 2015-05-15 10:46:57 +02:00
Fabrice Marsaud
71e271774e Tolerance to xoa-updater missing 2015-05-14 15:45:21 +02:00
Olivier Lambert
c9db49e255 tree view is now compatible with small devices 2015-05-14 15:10:09 +02:00
Olivier Lambert
22f35f0e86 pool view better UI 2015-05-14 15:10:09 +02:00
Fabrice Marsaud
375f3ac3ac updater loss and reconnection properly handled 2015-05-14 15:03:34 +02:00
Olivier Lambert
b60a02bc34 use ellipsis to compact lines in tables 2015-05-14 14:56:25 +02:00
Olivier Lambert
5a4d821c98 fix display issue for small devices in vm view 2015-05-14 14:24:14 +02:00
Olivier Lambert
cd0305c71d better UI display for small devices 2015-05-14 14:22:17 +02:00
Olivier Lambert
371459ff5e far better fluid display for host view 2015-05-14 14:15:09 +02:00
Fabrice Marsaud
5a8a7c6a0f A first delivery for trial handling 2015-05-14 13:01:27 +02:00
Fabrice Marsaud
69db541300 Starting to handle trial 2015-05-14 13:01:27 +02:00
Olivier Lambert
94949866ee reduce the warning lenght and add a tooltip 2015-05-13 18:41:51 +02:00
Fabrice Marsaud
c22b3e7449 Fix : removed a no mùore existing service provider 2015-05-12 10:22:57 +02:00
Fabrice Marsaud
096dde922b Minor fixes 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
00f26d854f updater and register services merged 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
6c8ff1717e bug fix 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
c7288c1d8a No https for update/register 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
2e52fe369d bugfix 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
df3430add5 updating url fix 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
7af848c94b Minor fix 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
f57c462b5f Fix + enhancement 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
6018035908 Update feature fully adapted to xoa-updater and xoa-register 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
c8b0351786 Nice update/register panels 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
26cc812f82 a bit of UI tuning 2015-05-12 10:08:30 +02:00
Olivier Lambert
67f98950e6 ui fixes 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
8ba8537b9f First proto delivery for updating from xo-web 2015-05-12 10:08:30 +02:00
Fabrice Marsaud
a7f05a68e0 UI sketch for updating 2015-05-12 10:08:30 +02:00
Olivier Lambert
a0db228154 better docker UI and border for stats 2015-05-08 19:10:19 +02:00
Olivier Lambert
eec6fabe58 add UI info for Docker enabled VMs 2015-05-08 16:55:02 +02:00
Olivier Lambert
501c038f97 reflect changes done in server 2015-05-08 16:12:42 +02:00
Olivier Lambert
e0a0f717fd add container stuff 2015-05-08 15:37:52 +02:00
Olivier Lambert
4dd3f7487c Update README.md 2015-05-07 19:41:53 +02:00
Olivier Lambert
99d1cddaa5 fix #245 2015-05-07 19:17:17 +02:00
Olivier Lambert
2158e1a47e Merge branch 'patch' into next-release 2015-05-06 18:44:22 +02:00
Olivier Lambert
059238759a better patch view 2015-05-06 18:21:45 +02:00
Olivier Lambert
8d3ea7548a add a TODO 2015-05-06 16:05:46 +02:00
Olivier Lambert
221b411b63 patch management merged 2015-05-06 16:03:26 +02:00
Olivier Lambert
e2c173990f work in progress with pool patches 2015-05-05 19:41:09 +02:00
Olivier Lambert
a609a8d5d6 patch system in the host view is OK 2015-05-05 18:32:08 +02:00
Olivier Lambert
c5c2afddc2 UI fixes 2015-05-02 19:22:13 +02:00
Olivier Lambert
409d87f210 start to work on update and patching 2015-05-02 18:15:44 +02:00
Julien Fontanet
78baa4b01e Better/simpler styling of the Settings using flexbox. 2015-04-21 12:12:04 +02:00
Julien Fontanet
b41c66c4af 3.9.1 2015-04-21 11:09:27 +02:00
Julien Fontanet
d6b7388c2e Merge branch 'next-release' 2015-04-21 11:09:20 +02:00
Julien Fontanet
b0dc681d48 Hide link to not implemented Groups page. 2015-04-21 11:04:43 +02:00
Julien Fontanet
c20b460f2d Fix link in ChangeLog. 2015-04-21 11:04:19 +02:00
Julien Fontanet
cd0a46fd7f ChangeLog 2015-04-21 10:53:56 +02:00
Julien Fontanet
0a9c868678 ngAnnotate is broken due to Babel. 2015-04-20 20:17:33 +02:00
Olivier Lambert
027d1e8cb1 notify when connect/disconnect a server 2015-04-20 19:59:11 +02:00
Olivier Lambert
a5c9880318 do not fetch stat if the VM has current operations 2015-04-20 19:59:11 +02:00
96 changed files with 1339 additions and 8237 deletions

12
.babelrc Normal file
View File

@@ -0,0 +1,12 @@
{
"comments": false,
"compact": true,
"plugins": [
"transform-runtime"
],
"presets": [
"es2015",
"stage-0",
"react"
]
}

View File

@@ -46,7 +46,7 @@ indent_size = 2
indent_style = space
# Less
[*.js]
[*.less]
indent_size = 2
indent_style = space

9
.gitignore vendored
View File

@@ -1,6 +1,9 @@
/.nyc_output/
/bower_components/
/dist/
/node_modules/*
!/node_modules/*.js
/node_modules/*.js/
npm-debug.log
npm-debug.log.*
!node_modules/*
node_modules/*/

View File

@@ -1,94 +0,0 @@
{
// Julien Fontanet JSHint configuration
// https://gist.github.com/julien-f/8095615
//
// Changes from defaults:
// - all enforcing options (except `++` & `--`) enabled
// - single quotes
// - indentation set to 2 instead of 4
// - almost all relaxing options disabled
// - allow expression statements (necessary for chai.expect())
// - environments are set to Browserify, mocha & Node.js
//
// See http://jshint.com/docs/ for more details
"maxerr" : 50, // {int} Maximum error before stopping
// Enforcing
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : true, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
"freeze" : true, // true: Prohibit overwriting prototypes of native objects (Array, Date, ...)
"immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : 2, // {int} Number of spaces to use for indentation
"latedef" : true, // true: Require variables/functions to be defined before being used
"newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonbsp" : true, // true: Prohibit use of non breakable spaces
"nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : "single", // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : true, // true: Require all defined variables be used
"strict" : false, // true: Requires all functions run in ES5 Strict Mode
"maxcomplexity" : 7, // {int} Max cyclomatic complexity per function
"maxdepth" : 3, // {int} Max depth of nested blocks (within functions)
"maxlen" : 80, // {int} Max number of characters per line
"maxparams" : 4, // {int} Max number of formal params allowed per function
"maxstatements" : 20, // {int} Max number statements per function
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"multistr" : false, // true: Tolerate multi-line strings
"notypeof" : false, // true: Tolerate typeof comparison with unknown values.
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
"noyield" : false, // true: Tolerate generators without yields
// Environments
"browser" : false, // Web Browser (window, document, etc)
"browserify" : true, // Browserify (node.js code in the browser)
"couch" : false, // CouchDB
"devel" : false, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jquery" : false, // jQuery
"mocha" : false, // mocha
"mootools" : false, // MooTools
"node" : false, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"phantom" : false, // PhantomJS
"prototypejs" : false, // Prototype and Scriptaculous
"rhino" : false, // Rhino
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
// Custom Globals
"globals" : {} // additional predefined global variables
}

5
.mocha.js Normal file
View File

@@ -0,0 +1,5 @@
Error.stackTraceLimit = 100
try { require('trace') } catch (_) {}
try { require('clarify') } catch (_) {}
try { require('source-map-support/register') } catch (_) {}

1
.mocha.opts Normal file
View File

@@ -0,0 +1 @@
--require ./.mocha.js

10
.npmignore Normal file
View File

@@ -0,0 +1,10 @@
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/test/
/tests/
*.spec.js
*.spec.js.map

10
.travis.yml Normal file
View File

@@ -0,0 +1,10 @@
language: node_js
node_js:
- 'stable'
- '4'
- '0.12'
- '0.10'
# Use containers.
# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
sudo: false

View File

@@ -1,15 +1,284 @@
# ChangeLog
## **4.9.0** (2015-11-13)
Automated DR, restore backup, VM copy
### Enhancements
- DR: schedule VM export on other host ([xo-web#447](https://github.com/vatesfr/xo-web/issues/447))
- Scheduler logs ([xo-web#390](https://github.com/vatesfr/xo-web/issues/390) and [xo-web#477](https://github.com/vatesfr/xo-web/issues/477))
- Restore backups ([xo-web#450](https://github.com/vatesfr/xo-web/issues/350))
- Disable backup compression ([xo-web#467](https://github.com/vatesfr/xo-web/issues/467))
- Copy VM to another SR (even remote) ([xo-web#475](https://github.com/vatesfr/xo-web/issues/475))
- VM stats without time sync ([xo-web#460](https://github.com/vatesfr/xo-web/issues/460))
- Stats perfs for high CPU numbers ([xo-web#461](https://github.com/vatesfr/xo-web/issues/461))
### Bug fixes
- Rolling backup bug ([xo-web#484](https://github.com/vatesfr/xo-web/issues/484))
- vCPUs/CPUs inversion in dashboard ([xo-web#481](https://github.com/vatesfr/xo-web/issues/481))
- Machine to template ([xo-web#459](https://github.com/vatesfr/xo-web/issues/459))
### Misc
- Console fix in XenServer ([xo-web#406](https://github.com/vatesfr/xo-web/issues/406))
## **4.8.0** (2015-10-29)
Fully automated patch system, ACLs inheritance, stats performance improved.
### Enhancements
- ACLs inheritance ([xo-web#279](https://github.com/vatesfr/xo-web/issues/279))
- Patch automatically all missing updates ([xo-web#281](https://github.com/vatesfr/xo-web/issues/281))
- Intelligent stats polling ([xo-web#432](https://github.com/vatesfr/xo-web/issues/432))
- Cache latest result of stats request ([xo-web#431](https://github.com/vatesfr/xo-web/issues/431))
- Improve stats polling on multiple objects ([xo-web#433](https://github.com/vatesfr/xo-web/issues/433))
- Patch upload task should display the patch name ([xo-web#449](https://github.com/vatesfr/xo-web/issues/449))
- Backup filename for Windows ([xo-web#448](https://github.com/vatesfr/xo-web/issues/448))
- Specific distro icons ([xo-web#446](https://github.com/vatesfr/xo-web/issues/446))
- PXE boot for HVM ([xo-web#436](https://github.com/vatesfr/xo-web/issues/436))
- Favicon display before sign in ([xo-web#428](https://github.com/vatesfr/xo-web/issues/428))
- Registration renewal ([xo-web#424](https://github.com/vatesfr/xo-web/issues/424))
- Reconnect to the host if pool merge fails ([xo-web#403](https://github.com/vatesfr/xo-web/issues/403))
- Avoid brute force login ([xo-web#339](https://github.com/vatesfr/xo-web/issues/339))
- Missing FreeBSD icon ([xo-web#136](https://github.com/vatesfr/xo-web/issues/136))
- Hide halted objects in the Health view ([xo-web#457](https://github.com/vatesfr/xo-web/issues/457))
- Click on "Remember me" label ([xo-web#438](https://github.com/vatesfr/xo-web/issues/438))
### Bug fixes
- Pool patches in multiple pools not displayed ([xo-web#442](https://github.com/vatesfr/xo-web/issues/442))
- VM Import crashes with Chrome ([xo-web#427](https://github.com/vatesfr/xo-web/issues/427))
- Cannot open a direct link ([xo-web#371](https://github.com/vatesfr/xo-web/issues/371))
- Patch display edge case ([xo-web#309](https://github.com/vatesfr/xo-web/issues/309))
- VM snapshot should require user permission on SR ([xo-web#429](https://github.com/vatesfr/xo-web/issues/429))
## **4.7.0** (2015-10-12)
Plugin config management and browser notifications.
### Enhancements
- Plugin management in the web interface ([xo-web#352](https://github.com/vatesfr/xo-web/issues/352))
- Browser notifications ([xo-web#402](https://github.com/vatesfr/xo-web/issues/402))
- Graph selector ([xo-web#400](https://github.com/vatesfr/xo-web/issues/400))
- Circle packing visualization ([xo-web#374](https://github.com/vatesfr/xo-web/issues/374))
- Password generation ([xo-web#397](https://github.com/vatesfr/xo-web/issues/397))
- Password reveal during user creation ([xo-web#396](https://github.com/vatesfr/xo-web/issues/396))
- Add host to a pool ([xo-web#62](https://github.com/vatesfr/xo-web/issues/62))
- Better modal when removing a host from a pool ([xo-web#405](https://github.com/vatesfr/xo-web/issues/405))
- Drop focus on CD/ISO selector ([xo-web#290](https://github.com/vatesfr/xo-web/issues/290))
- Allow non persistent session ([xo-web#243](https://github.com/vatesfr/xo-web/issues/243))
### Bug fixes
- VM export permission corrected ([xo-web#410](https://github.com/vatesfr/xo-web/issues/410))
- Proper host removal in a pool ([xo-web#402](https://github.com/vatesfr/xo-web/issues/402))
- Sub-optimal tooltip placement ([xo-web#421](https://github.com/vatesfr/xo-web/issues/421))
- VM migrate host incorrect target ([xo-web#419](https://github.com/vatesfr/xo-web/issues/419))
- Alone host can't leave its pool ([xo-web#414](https://github.com/vatesfr/xo-web/issues/414))
## **4.6.0** (2015-09-25)
Tags management and new visualization.
### Enhancements
- Multigraph for correlation ([xo-web#358](https://github.com/vatesfr/xo-web/issues/358))
- Tags management ([xo-web#367](https://github.com/vatesfr/xo-web/issues/367))
- Google Provider for authentication ([xo-web#363](https://github.com/vatesfr/xo-web/issues/363))
- Password change for users ([xo-web#362](https://github.com/vatesfr/xo-web/issues/362))
- Better live migration process ([xo-web#237](https://github.com/vatesfr/xo-web/issues/237))
- VDI search filter in SR view ([xo-web#222](https://github.com/vatesfr/xo-web/issues/222))
- PV args during VM creation ([xo-web#112](https://github.com/vatesfr/xo-web/issues/330))
- PV args management ([xo-web#394](https://github.com/vatesfr/xo-web/issues/394))
- Confirmation dialog on important actions ([xo-web#350](https://github.com/vatesfr/xo-web/issues/350))
- New favicon ([xo-web#369](https://github.com/vatesfr/xo-web/issues/369))
- Filename of VM for exports ([xo-web#370](https://github.com/vatesfr/xo-web/issues/370))
- ACLs rights edited on the fly ([xo-web#323](https://github.com/vatesfr/xo-web/issues/323))
- Heatmap values now human readable ([xo-web#342](https://github.com/vatesfr/xo-web/issues/342))
### Bug fixes
- Export backup fails if no tags specified ([xo-web#383](https://github.com/vatesfr/xo-web/issues/383))
- Wrong login give an obscure error message ([xo-web#373](https://github.com/vatesfr/xo-web/issues/373))
- Update view is broken during updates ([xo-web#356](https://github.com/vatesfr/xo-web/issues/356))
- Settings/dashboard menu incorrect display ([xo-web#357](https://github.com/vatesfr/xo-web/issues/357))
- Console View Not refreshing if the VM restart ([xo-web#107](https://github.com/vatesfr/xo-web/issues/107))
## **4.5.1** (2015-09-16)
An issue in `xo-web` with the VM view.
### Bug fix
- Attach disk/new disk/create interface is broken ([xo-web#378](https://github.com/vatesfr/xo-web/issues/378))
## **4.5.0** (2015-09-11)
A new dataviz (parallel coord), a new provider (GitHub) and faster consoles.
### Enhancements
- Parallel coordinates view ([xo-web#333](https://github.com/vatesfr/xo-web/issues/333))
- Faster consoles ([xo-web#337](https://github.com/vatesfr/xo-web/issues/337))
- Disable/hide button ([xo-web#268](https://github.com/vatesfr/xo-web/issues/268))
- More details on missing-guest-tools ([xo-web#304](https://github.com/vatesfr/xo-web/issues/304))
- Scheduler meta data export ([xo-web#315](https://github.com/vatesfr/xo-web/issues/315))
- Better heatmap ([xo-web#330](https://github.com/vatesfr/xo-web/issues/330))
- Faster dashboard ([xo-web#331](https://github.com/vatesfr/xo-web/issues/331))
- Faster sunburst ([xo-web#332](https://github.com/vatesfr/xo-web/issues/332))
- GitHub provider for auth ([xo-web#334](https://github.com/vatesfr/xo-web/issues/334))
- Filter networks for users ([xo-web#347](https://github.com/vatesfr/xo-web/issues/347))
- Add networks in ACLs ([xo-web#348](https://github.com/vatesfr/xo-web/issues/348))
- Better looking login page ([xo-web#341](https://github.com/vatesfr/xo-web/issues/341))
- Real time dataviz (dashboard) ([xo-web#349](https://github.com/vatesfr/xo-web/issues/349))
### Bug fixes
- Typo in dashboard ([xo-web#355](https://github.com/vatesfr/xo-web/issues/355))
- Global RAM usage fix ([xo-web#356](https://github.com/vatesfr/xo-web/issues/356))
- Re-allowing XO behind a reverse proxy ([xo-web#361](https://github.com/vatesfr/xo-web/issues/361))
## **4.4.0** (2015-08-28)
SSO and Dataviz are the main features for this release.
### Enhancements
- Dataviz storage usage ([xo-web#311](https://github.com/vatesfr/xo-web/issues/311))
- Heatmap in health view ([xo-web#329](https://github.com/vatesfr/xo-web/issues/329))
- SSO for SAML and other providers ([xo-web#327](https://github.com/vatesfr/xo-web/issues/327))
- Better UI for ACL objects attribution ([xo-web#320](https://github.com/vatesfr/xo-web/issues/320))
- Refresh the browser after an update ([xo-web#318](https://github.com/vatesfr/xo-web/issues/318))
- Clean CSS and Flexbox usage ([xo-web#239](https://github.com/vatesfr/xo-web/issues/239))
### Bug fixes
- Admin only accessible views ([xo-web#328](https://github.com/vatesfr/xo-web/issues/328))
- Hide "base copy" VDIs ([xo-web#324](https://github.com/vatesfr/xo-web/issues/324))
- ACLs on VIFs for non-admins ([xo-web#322](https://github.com/vatesfr/xo-web/issues/322))
- Updater display problems ([xo-web#313](https://github.com/vatesfr/xo-web/issues/313))
## **4.3.0** (2015-07-22)
Scheduler for rolling backups
### Enhancements
- Rolling backup scheduler ([xo-web#278](https://github.com/vatesfr/xo-web/issues/278))
- Clean snapshots of removed VMs ([xo-web#301](https://github.com/vatesfr/xo-web/issues/301))
### Bug fixes
- VM export ([xo-web#307](https://github.com/vatesfr/xo-web/issues/307))
- Remove VM VDIs ([xo-web#303](https://github.com/vatesfr/xo-web/issues/303))
- Pagination fails ([xo-web#302](https://github.com/vatesfr/xo-web/issues/302))
## **4.2.0** (2015-06-29)
Huge performance boost, scheduler for rolling snapshots and backward compatibility for XS 5.x series
### Enhancements
- Rolling snapshots scheduler ([xo-web#176](https://github.com/vatesfr/xo-web/issues/176))
- Huge perf boost ([xen-api#1](https://github.com/julien-f/js-xen-api/issues/1))
- Backward compatibility ([xo-web#296](https://github.com/vatesfr/xo-web/issues/296))
### Bug fixes
- VDI attached on a VM missing in SR view ([xo-web#294](https://github.com/vatesfr/xo-web/issues/294))
- Better VM creation process ([xo-web#292](https://github.com/vatesfr/xo-web/issues/292))
## **4.1.0** (2015-06-10)
Add the drag'n drop support from VM live migration, better ACLs groups UI.
### Enhancements
- Drag'n drop VM in tree view for live migration ([xo-web#277](https://github.com/vatesfr/xo-web/issues/277))
- Better group view with objects ACLs ([xo-web#276](https://github.com/vatesfr/xo-web/issues/276))
- Hide non-visible objects ([xo-web#272](https://github.com/vatesfr/xo-web/issues/272))
### Bug fixes
- Convert to template displayed when the VM is not halted ([xo-web#286](https://github.com/vatesfr/xo-web/issues/286))
- Lost some data when refresh some views ([xo-web#271](https://github.com/vatesfr/xo-web/issues/271))
- Suspend button don't trigger any permission message ([xo-web#270](https://github.com/vatesfr/xo-web/issues/270))
- Create network interfaces shouldn't call xoApi directly ([xo-web#269](https://github.com/vatesfr/xo-web/issues/269))
- Don't plug automatically a disk or a VIF if the VM is not running ([xo-web#287](https://github.com/vatesfr/xo-web/issues/287))
## **4.0.2** (2015-06-01)
An issue in `xo-server` with the password of default admin account and also a UI fix.
### Bug fixes
- Cannot modify admin account ([xo-web#265](https://github.com/vatesfr/xo-web/issues/265))
- Password field seems to keep empty/reset itself after 1-2 seconds ([xo-web#264](https://github.com/vatesfr/xo-web/issues/264))
## **4.0.1** (2015-05-30)
An issue with the updater in HTTPS was left in the *4.0.0*. This patch release fixed
it.
### Bug fixes
- allow updater to work in HTTPS ([xo-web#266](https://github.com/vatesfr/xo-web/issues/266))
## **4.0.0** (2015-05-29)
[Blog post of this release](https://xen-orchestra.com/blog/xen-orchestra-4-0).
### Enhancements
- advanced ACLs ([xo-web#209](https://github.com/vatesfr/xo-web/issues/209))
- xenserver update management ([xo-web#174](https://github.com/vatesfr/xo-web/issues/174) & [xo-web#259](https://github.com/vatesfr/xo-web/issues/259))
- docker control ([xo-web#211](https://github.com/vatesfr/xo-web/issues/211))
- better responsive design ([xo-web#252](https://github.com/vatesfr/xo-web/issues/252))
- host stats ([xo-web#255](https://github.com/vatesfr/xo-web/issues/255))
- pagination ([xo-web#221](https://github.com/vatesfr/xo-web/issues/221))
- web updater
- better VM creation process([xo-web#256](https://github.com/vatesfr/xo-web/issues/256))
- VM boot order([xo-web#251](https://github.com/vatesfr/xo-web/issues/251))
- new mapped collection([xo-server#47](https://github.com/vatesfr/xo-server/issues/47))
- resource location in ACL view ([xo-web#245](https://github.com/vatesfr/xo-web/issues/245))
### Bug fixes
- wrong calulation of RAM amounts ([xo-web#51](https://github.com/vatesfr/xo-web/issues/51))
- checkbox not aligned ([xo-web#253](https://github.com/vatesfr/xo-web/issues/253))
- VM stats behavior more robust ([xo-web#250](https://github.com/vatesfr/xo-web/issues/250))
- XO not on the root of domain ([xo-web#254](https://github.com/vatesfr/xo-web/issues/254))
## **3.9.1** (2015-04-21)
A few bugs hve made their way into *3.9.0*, this minor release fixes
them.
### Bug fixes
- correctly keep the VM guest metrics up to date ([xo-web#172](https://github.com/vatesfr/xo-web/issues/172))
- fix edition of a VM snapshot ([b04111c](https://github.com/vatesfr/xo-server/commit/b04111c79ba8937778b84cb861bb7c2431162c11))
- do not fetch stats if the VM state is transitioning ([a5c9880](https://github.com/vatesfr/xo-web/commit/a5c98803182792d2fe5ceb840ae1e23a8b767923))
- fix broken Angular due to new version of Babel which breaks ngAnnotate ([0a9c868](https://github.com/vatesfr/xo-web/commit/0a9c868678d239e5b3e54b4d2bc3bd14b5400120))
- feedback when connecting/disconnecting a server ([027d1e8](https://github.com/vatesfr/xo-web/commit/027d1e8cb1f2431e67042e1eec51690b2bc54ad7))
- clearer error message if a server is unreachable ([06ca007](https://github.com/vatesfr/xo-server/commit/06ca0079b321e757aaa4112caa6f92a43193e35d))
## **3.9.0** (2015-04-20)
[Blog post of this release](https://xen-orchestra.com/blog/xen-orchestra-3-9).
## Enhancements
### Enhancements
- ability to manually connect/disconnect a server ([xo-web#88](https://github.com/vatesfr/xo-web/issues/88) & [xo-web#234](https://github.com/vatesfr/xo-web/issues/234))
- display the connection status of a server ([xo-web#103](https://github.com/vatesfr/xo-web/issues/103))
- better feedback when connecting to a server ([xo-web#210](https://github.com/vatesfr/xo-web/issues/210))
- ability to add a local LVM SR ([xo-web#219](https://github.com/vatesfr/xo-web/issues/219))
- display virtual GPUs in VM view ([xo-web#223](https://github.com/vatesfr/xo-web/issues/223))
- ability to automatically start a VM with its host ([xo-web#224](https://github.com/vatesfr/xo-web/issues/224))
- ability to create networks ([xo-web#225](https://github.com/vatesfr/xo-web/issues/225))
- live charts for a VM CPU/disk/network & RAM ([xo-web#228](https://github.com/vatesfr/xo-web/issues/228) & [xo-server#51](https://github.com/vatesfr/xo-server/issues/51))
@@ -17,7 +286,7 @@
- XO-Server sources are compiled to JS prior distribution: less bugs & faster startups ([xo-server#50](https://github.com/vatesfr/xo-server/issues/50))
- use XAPI `event.from()` instead of `event.next()` which leads to faster connection ([xo-server#52](https://github.com/vatesfr/xo-server/issues/52))
## Bug
### Bug fixes
- removed servers are properly disconnected ([xo-web#61](https://github.com/vatesfr/xo-web/issues/61))
- fix VM creation with multiple interfaces ([xo-wb#229](https://github.com/vatesfr/xo-wb/issues/229))
@@ -27,7 +296,7 @@
[Blog post of this release](https://xen-orchestra.com/blog/xen-orchestra-3-8).
## Enhancements
### Enhancements
- initial plugin system ([xo-server#37](https://github.com/vatesfr/xo-server/issues/37))
- new authentication system based on providers ([xo-server#39](https://github.com/vatesfr/xo-server/issues/39))
@@ -36,7 +305,7 @@
- network creation on the VM page ([xo-web#216](https://github.com/vatesfr/xo-web/issues/216))
- charts on the host and SR pages ([xo-web#217](https://github.com/vatesfr/xo-web/issues/217))
## Bug
### Bug fixes
- fix *Invalid parameter(s)* message on the settings page ([xo-server#49](https://github.com/vatesfr/xo-server/issues/49))
- fix mouse clicks in console ([xo-web#205](https://github.com/vatesfr/xo-web/issues/205))

View File

@@ -1,5 +1,7 @@
# Xen Orchestra Web
![](http://i.imgur.com/tRffA5y.png)
XO-Web is part of [Xen Orchestra](https://github.com/vatesfr/xo), a web interface for XenServer or XAPI enabled hosts.
It is a web client for [XO-Server](https://github.com/vatesfr/xo-server).
@@ -39,6 +41,9 @@ Otherwise, please consider using the [bugtracker of the general repository](http
# Switch to the master branch.
git checkout master
# Fetches latest changes.
git pull --ff-only
# Merge changes of the next-release branch.
git merge next-release
@@ -50,10 +55,10 @@ git checkout next-release
# Fetches the last changes (the merge and version bump) from master to
# next-release.
git pull --fast-forward master
git merge --ff-only master
# Push the changes on git.
git push origin master:master next-release:next-release
git push --follow-tags origin master next-release
# Publish this release to npm.
npm publish

View File

@@ -1,181 +0,0 @@
// Must be loaded before angular.
import 'angular-file-upload';
import angular from 'angular';
import uiBootstrap from'angular-ui-bootstrap';
import uiIndeterminate from'angular-ui-indeterminate';
import uiRouter from'angular-ui-router';
import uiSelect from'angular-ui-select';
import naturalSort from 'angular-natural-sort';
import xeditable from 'angular-xeditable';
import xoDirectives from 'xo-directives';
import xoFilters from 'xo-filters';
import xoServices from 'xo-services';
import aboutState from './modules/about';
import consoleState from './modules/console';
import deleteVmsState from './modules/delete-vms';
import genericModalState from './modules/generic-modal';
import hostState from './modules/host';
import listState from './modules/list';
import loginState from './modules/login';
import navbarState from './modules/navbar';
import newSrState from './modules/new-sr';
import newVmState from './modules/new-vm';
import poolState from './modules/pool';
import settingsState from './modules/settings';
import srState from './modules/sr';
import treeState from './modules/tree';
import vmState from './modules/vm';
import isoDevice from './modules/iso-device';
import '../dist/bower_components/angular-chart.js/dist/angular-chart.js';
//====================================================================
export default angular.module('xoWebApp', [
uiBootstrap,
uiIndeterminate,
uiRouter,
uiSelect,
naturalSort,
xeditable,
xoDirectives,
xoFilters,
xoServices,
aboutState,
consoleState,
deleteVmsState,
genericModalState,
hostState,
listState,
loginState,
navbarState,
newSrState,
newVmState,
poolState,
settingsState,
srState,
treeState,
vmState,
isoDevice,
'chart.js'
])
// Prevent Angular.js from mangling exception stack (interfere with
// source maps).
.factory('$exceptionHandler', () => function (exception) {
throw exception;
})
.config(function (
$compileProvider,
$stateProvider,
$urlRouterProvider,
$tooltipProvider,
uiSelectConfig
) {
// Disable debug data to improve performance.
//
// In case of a bug, simply use `angular.reloadWithDebugInfo()` in
// the console.
//
// See https://docs.angularjs.org/guide/production
$compileProvider.debugInfoEnabled(false);
// Redirect to default state.
$stateProvider.state('index', {
url: '/',
controller: function ($state, xoApi) {
let isAdmin = xoApi.user && (xoApi.user.permission === 'admin');
$state.go(isAdmin ? 'tree' : 'list');
},
});
// Redirects unmatched URLs to `/`.
$urlRouterProvider.otherwise('/');
// Changes the default settings for the tooltips.
$tooltipProvider.options({
appendToBody: true,
placement: 'bottom',
});
uiSelectConfig.theme = 'bootstrap';
uiSelectConfig.resetSearchInput = true;
})
.run(function (
$anchorScroll,
$rootScope,
$state,
editableOptions,
editableThemes,
notify,
xoApi,
xo
) {
$rootScope.$on('$stateChangeStart', function (event, state, stateParams) {
let {user} = xoApi;
let loggedIn = !!user;
if (state.name === 'login') {
if (loggedIn) {
event.preventDefault();
$state.go('index');
}
return;
}
if (!loggedIn) {
event.preventDefault();
// FIXME: find a better way to pass info to the login controller.
$rootScope._login = { state, stateParams };
$state.go('login');
return;
}
if (user.permission === 'admin') {
return;
}
// The user must have the `admin` permission to access the
// settings pages.
if (/^settings\..*|tree$/.test(state.name)) {
event.preventDefault();
notify.error({
title: 'Restricted area',
message: 'You do not have the permission to view this page',
});
}
let {id} = stateParams;
if (id && !xo.canAccess(id)) {
event.preventDefault();
notify.error({
title: 'Restricted area',
message: 'You do not have the permission to view this page',
});
}
});
// Work around UI Router bug (https://github.com/angular-ui/ui-router/issues/1509)
$rootScope.$on('$stateChangeSuccess', function () {
$anchorScroll();
});
editableThemes.bs3.inputClass = 'input-sm';
editableThemes.bs3.buttonsClass = 'btn-sm';
editableOptions.theme = 'bs3';
})
.name
;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,64 +0,0 @@
//- 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")
link(rel="stylesheet", href="bower_components/angular-chart.js/dist/angular-chart.css")
body(
ng-app = 'xoWebApp'
ng-strict-di
)
toaster-container
//- Navigation bar.
navbar
//- Main content (managed by the router).
.view-main(ui-view = "")
script(src="bower_components/Chart.js/Chart.min.js")
script(src="app.js")

View File

@@ -1,23 +0,0 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import pkg from '../../../package';
//====================================================================
module.exports = angular.module('xoWebApp.about', [
uiRouter,
])
.config(function ($stateProvider) {
$stateProvider.state('about', {
url: '/about',
controller: 'AboutCtrl',
template: require('./view'),
});
})
.controller('AboutCtrl', function ($scope) {
$scope.pkg = pkg;
})
// A module exports its name.
.name
;

View File

@@ -1,50 +0,0 @@
//- TODO: lots of stuff.
.grid
.panel.panel-default
p.page-title About Xen Orchestra
p.text-center ({{pkg.name}} {{pkg.version}})
.grid
//- Vates
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-lightbulb-o(style="color: #e25440;")
| Vates
.panel-body
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
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-thumbs-up(style="color: #e25440;")
| Open Source
.panel-body
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
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-truck(style="color: #e25440;")
| Pro Support Delivered
.panel-body
p.text-center
| Our XO Appliance can be delivered with professional support: stay relaxed, we got your back! You can also have assitance for deploying or upgrade your virtualized infrastructure through our deep understanding of Xen.
p.text-center
img(src="images/support.png")
p.text-center
a.btn.btn-primary(href="https://xen-orchestra.com/services/")
i.fa.fa-envelope
| Get services

View File

@@ -1,81 +0,0 @@
angular = require 'angular'
includes = require('lodash.includes')
#=====================================================================
module.exports = angular.module 'xoWebApp.console', [
require 'angular-ui-router'
require 'angular-no-vnc'
]
.config ($stateProvider) ->
$stateProvider.state 'consoles_view',
url: '/consoles/:id'
controller: 'ConsoleCtrl'
template: require './view'
.controller 'ConsoleCtrl', ($scope, $stateParams, xoApi, xo) ->
{id} = $stateParams
{get} = xoApi
push = Array::push.apply.bind Array::push
merge = do ->
(args...) ->
result = []
for arg in args
push result, arg if arg?
result
$scope.$watch(
-> xoApi.get id
(VM) ->
$scope.consoleUrl = null
unless xoApi.user
$scope.VDIs = []
return
$scope.VM = VM
return unless (
VM? and
VM.power_state is 'Running' and
not includes(VM.current_operations, 'clean_reboot')
)
pool = get VM.poolRef
return unless pool
$scope.consoleUrl = "/consoles/#{id}"
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
# A module exports its name.
.name

View File

@@ -1,65 +0,0 @@
.container: .panel.panel-default
//- Title
p.page-title
span.fa-stack
i.fa.fa-square-o.fa-stack-2x
i.xo-icon-console.fa-stack-1x(class = 'xo-color-{{VM.power_state | lowercase}}')
| &nbsp;
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
| &nbsp;
| Ctrl+Alt+Del
//- Action panel
.col-sm-3
.btn-group
button.btn.btn-default.inversed(
ng-if = "VM.power_state == ('Running' || 'Paused')"
tooltip = "Stop VM"
type = "button"
xo-click = "stopVM(VM.UUID)"
)
i.fa.fa-stop.fa-fw
button.btn.btn-default.inversed(
ng-if = "VM.power_state == ('Halted')"
tooltip = "Start VM"
type = "button"
xo-click = "startVM(VM.UUID)"
)
i.fa.fa-play.fa-fw
button.btn.btn-default.inversed(
ng-if = "VM.power_state == ('Running' || 'Paused')"
tooltip = "Reboot VM"
type = "button"
xo-click = "rebootVM(VM.UUID)"
)
i.fa.fa-refresh.fa-fw
//- Console
.list-group-item
no-vnc(
url = '{{consoleUrl}}'
remote-control = 'vncRemote'
)

View File

@@ -1,61 +0,0 @@
import angular from 'angular';
import uiBootstrap from 'angular-ui-bootstrap';
import xoServices from 'xo-services';
import view from './view';
//====================================================================
export default angular.module('xoWebApp.deleteVms', [
uiBootstrap,
xoServices,
])
.controller('DeleteVmsCtrl', function (
$scope,
$modalInstance,
xoApi,
VMsIds
) {
$scope.$watchCollection(() => xoApi.get(VMsIds), function (VMs) {
$scope.VMs = VMs;
});
// Do disks have to be deleted for a given VM.
let disks = $scope.disks = {};
angular.forEach(VMsIds, id => {
disks[id] = true;
});
$scope.delete = function () {
let value = [];
angular.forEach(VMsIds, id => {
value.push([id, disks[id]]);
});
$modalInstance.close(value);
};
})
.service('deleteVmsModal', function ($modal, xo) {
return function (ids) {
return $modal.open({
controller: 'DeleteVmsCtrl',
template: view,
resolve: {
VMsIds: () => ids
}
}).result.then(function (toDelete) {
let promises = [];
angular.forEach(toDelete, ([id, deleteDisks]) => {
promises.push(xo.vm.delete(id, deleteDisks));
});
return promises;
});
};
})
// A module exports its name.
.name
;

View File

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

View File

@@ -1,40 +0,0 @@
import angular from 'angular';
import uiBootstrap from 'angular-ui-bootstrap';
//====================================================================
export default angular.module('xoWebApp.genericModal', [
uiBootstrap,
])
.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;
}
};
})
// A module exports its name.
.name
;

View File

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

View File

@@ -1,214 +0,0 @@
angular = require 'angular'
throttle = require 'lodash.throttle'
#=====================================================================
module.exports = angular.module 'xoWebApp.host', [
require 'angular-file-upload'
require 'angular-ui-router'
]
.config ($stateProvider) ->
$stateProvider.state 'hosts_view',
url: '/hosts/:id'
controller: 'HostCtrl'
template: require './view'
.controller 'HostCtrl', (
$scope, $stateParams
$upload
$window
xoApi, xo, modal, notify, bytesToSizeFilter
) ->
$window.bytesToSize = bytesToSizeFilter # FIXME dirty workaround to custom a Chart.js tooltip template
host = null
$scope.$watch(
-> xoApi.get $stateParams.id
(host) ->
$scope.host = host
return unless host?
$scope.pool = xoApi.get host.poolRef
SRsToPBDs = $scope.SRsToPBDs = Object.create null
for PBD in host.$PBDs
PBD = xoApi.get PBD
# If this PBD is unknown, just skips it.
continue unless PBD
SRsToPBDs[PBD.SR] = PBD
)
$scope.removeMessage = xo.message.delete
$scope.cancelTask = (id) ->
modal.confirm({
title: 'Cancel task'
message: 'Are you sure you want to cancel this task?'
}).then ->
xo.task.cancel id
$scope.destroyTask = (id) ->
modal.confirm({
title: 'Destroy task'
message: 'Are you sure you want to destroy this task?'
}).then ->
xo.task.destroy id
$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.enableHost = (id) ->
xo.host.enable id
notify.info {
title: 'Host action'
message: 'Host is enabled'
}
$scope.disableHost = (id) ->
modal.confirm({
title: 'Disable host'
message: 'Are you sure you want to disable this host? In disabled state, no new VMs can be started and currently active VMs on the host continue to execute.'
}).then ->
xo.host.disable id
.then ->
notify.info {
title: 'Host action'
message: 'Host is disabled'
}
$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.deleteAllLog = ->
modal.confirm({
title: 'Log deletion'
message: 'Are you sure you want to delete all the logs?'
}).then ->
for log in $scope.host.messages
console.log "Remove log #{log}"
xo.log.delete log
$scope.deleteLog = (id) ->
console.log "Remove log #{id}"
xo.log.delete id
$scope.connectPBD = (UUID) ->
console.log "Connect PBD #{UUID}"
xoApi.call 'pbd.connect', {id: UUID}
$scope.disconnectPBD = (UUID) ->
console.log "Disconnect PBD #{UUID}"
xoApi.call 'pbd.disconnect', {id: UUID}
$scope.removePBD = (UUID) ->
console.log "Remove PBD #{UUID}"
xoApi.call 'pbd.delete', {id: UUID}
$scope.connectPIF = (UUID) ->
console.log "Connect PIF #{UUID}"
xoApi.call 'pif.connect', {id: UUID}
$scope.disconnectPIF = (UUID) ->
console.log "Disconnect PIF #{UUID}"
xoApi.call 'pif.disconnect', {id: UUID}
$scope.removePIF = (UUID) ->
console.log "Remove PIF #{UUID}"
xoApi.call 'pif.delete', {id: UUID}
$scope.importVm = ($files, id) ->
file = $files[0]
notify.info {
title: 'VM import started'
message: "Starting the VM import"
}
xo.vm.import id
.then ({ $sendTo: url }) ->
return $upload.http {
method: 'POST'
url
data: file
}
.then (result) ->
throw result.status if result.status isnt 200
notify.info
title: 'VM import'
message: 'Success'
$scope.createNetwork = (name, description, pif, mtu, vlan) ->
$scope.createNetworkWaiting = true # disables form fields
notify.info {
title: 'Network creation...'
message: 'Creating the network'
}
params = {
host: $scope.host.UUID
name,
}
if mtu then params.mtu = mtu
if pif then params.pif = pif
if vlan then params.vlan = vlan
if description then params.description = description
xoApi.call 'host.createNetwork', params
.then ->
$scope.creatingNetwork = false
$scope.createNetworkWaiting = false
# A module exports its name.
.name

View File

@@ -1,382 +0,0 @@
.grid
.panel.panel-default
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}}
| )
p.center {{host.bios_strings["system-manufacturer"]}} {{host.bios_strings["system-product-name"]}}
.grid
.panel.panel-default
.panel-heading.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
.panel-body
form(editable-form="", name="hostSettings", onbeforesave="saveHost($data)")
dl.dl-horizontal
dt Name
dd
span(editable-text="host.name_label", e-name="name_label", e-form="hostSettings")
| {{host.name_label}}
dt Description
dd
span(editable-text="host.name_description", e-name="name_description", e-form="hostSettings")
| {{host.name_description}}
dt Enabled
dd
span(editable-checkbox="host.enabled", e-name="enabled", e-form="hostSettings")
| {{host.enabled}}
dt Tags
dd(ng-if="host.tags.length")
span(ng-repeat="tag in host.tags")
span.label.label-primary {{tag}}
dd(ng-if="!host.tags.length")
em No tags.
dt CPUs
dd {{host.CPUs["cpu_count"]}}x {{host.CPUs["modelname"]}}
dt Hostname
dd
| {{host.hostname}}
dt UUID
dd {{host.UUID}}
dt iQN
dd {{host.iSCSI_name}}
.btn-form(ng-show="hostSettings.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="hostSettings.$waiting", ng-click="hostSettings.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="submit", ng-disabled="hostSettings.$waiting")
i.fa.fa-save
| Save
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-stats(style="color: #e25440;")
| Stats
.panel-body
.grid
.grid-cell
p.stat-name CPU usage:
canvas(
id="bar"
class="chart chart-bar"
data="[[host.$vCPUs], [host.CPUs['cpu_count']]]"
labels="['']"
series="['vCPUs','CPUs']"
options="{scaleShowGridLines: false, barDatasetSpacing : 10, showScale: false}"
)
.grid-cell
p.stat-name RAM used:
canvas(id="doughnut", class="chart chart-doughnut", data="[(host.memory.usage), (host.memory.size - host.memory.usage)]", labels="['Used', 'Free']", options='{tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= bytesToSize(value) %>"}')
.grid-cell
p.stat-name Running VMs:
p.center.big-stat {{host.VMs.length}}
//- Action panel
.grid
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-flash(style="color: #e25440;")
| Actions
.panel-body.text-center
.grid
.grid-cell.btn-group
button.btn(tooltip="Add SR", type="button", style="width: 90%", xo-sref="SRs_new({container: host.UUID})")
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(ng-if="host.enabled")
button.btn(tooltip="Disable host", type="button", style="width: 90%", xo-click="disableHost(host.UUID)")
i.fa.fa-times-circle.fa-2x.fa-fw
.grid-cell.btn-group(ng-if="!host.enabled")
button.btn(tooltip="Enable host", type="button", style="width: 90%", xo-click="enableHost(host.UUID)")
i.fa.fa-check-circle.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="Import VM"
type="button"
style="width: 90%"
ng-file-select = 'importVm($files, host.UUID)'
)
i.fa.fa-upload.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
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-memory(style="color: #e25440;")
| Memory
.panel-body.text-center
.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
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-sr(style="color: #e25440;")
| Storage
.panel-body
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")
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="SRsToPBDs[SR.ref].attached")
span.label.label-success Connected
span.pull-right.btn-group.quick-buttons
a(tooltip="Disconnect this SR", xo-click="disconnectPBD(SRsToPBDs[SR.ref].ref)")
i.fa.fa-unlink.fa-lg
td(ng-if="!SRsToPBDs[SR.ref].attached")
span.label.label-default Disconnected
span.pull-right.btn-group.quick-buttons
a(tooltip="Reconnect this SR", xo-click="connectPBD(SRsToPBDs[SR.ref].ref)")
i.fa.fa-link.fa-lg
a(tooltip="Forget this SR", xo-click="removePBD(SRsToPBDs[SR.ref].ref)")
i.fa.fa-ban.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")
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="SRsToPBDs[SR.ref].attached")
span.label.label-success Connected
span.pull-right.btn-group.quick-buttons
a(tooltip="Disconnect this SR", xo-click="disconnectPBD(SRsToPBDs[SR.ref].ref)")
i.fa.fa-unlink.fa-lg
td(ng-if="!SRsToPBDs[SR.ref].attached")
span.label.label-default Disconnected
span.pull-right.btn-group.quick-buttons
a(tooltip="Reconnect this SR", xo-click="connectPBD(SRsToPBDs[SR.ref].ref)")
i.fa.fa-link.fa-lg
a(tooltip="Forget this SR", xo-click="removePBD(SRsToPBDs[SR.ref].ref)")
i.fa.fa-ban.fa-lg
//- Networks/Interfaces panel
.grid
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-network(style="color: #e25440;")
| Interfaces
.panel-body
table.table.table-hover
th.col-md-1 Device
th.col-md-1 VLAN
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.label.label-primary(ng-if="PIF.management") XAPI
td
span(ng-if="PIF.vlan > -1")
| {{PIF.vlan}}
span(ng-if="PIF.vlan == -1")
| -
td {{PIF.IP}} ({{PIF.mode}})
td {{PIF.MAC}}
td {{PIF.MTU}}
td(ng-if="PIF.attached")
span.label.label-success Connected
span.pull-right.btn-group.quick-buttons
a(tooltip="Disconnect this interface", xo-click="disconnectPIF(PIF.ref)")
i.fa.fa-unlink.fa-lg
td(ng-if="!PIF.attached")
span.label.label-default Disconnected
span.pull-right.btn-group.quick-buttons
a(tooltip="Connect this interface", xo-click="connectPIF(PIF.ref)")
i.fa.fa-link.fa-lg
a(tooltip="Remove this interface", xo-click="removePIF(PIF.ref)")
i.fa.fa-trash-o.fa-lg
.text-right
button.btn(type="button", ng-class = '{"btn-success": creatingNetwork, "btn-primary": !creatingNetwork}', ng-click="creatingNetwork = !creatingNetwork")
i.fa.fa-plus(ng-if = '!creatingNetwork')
i.fa.fa-minus(ng-if = 'creatingNetwork')
| Create Network
br
form.form-inline.text-right#createNetworkForm(ng-if = 'creatingNetwork', name = 'createNetworkForm', ng-submit = 'createNetwork(newNetworkName, newNetworkDescription, newNetworkPIF, newNetworkMTU, newNetworkVlan)')
fieldset(ng-attr-disabled = '{{ createNetworkWaiting ? true : undefined }}')
.form-group
label(for = 'newNetworkPIF') Interface&nbsp;
select.form-control(ng-model = 'newNetworkPIF', ng-change = 'updateMTU(newNetworkPIF)', ng-options='(PIF | resolve).device for PIF in host.$PIFs')
option(value = '', disabled) None
| &nbsp;
.form-group
label.control-label(for = 'newNetworkName') Name&nbsp;
input#newNetworkName.form-control(type = 'text', ng-model = 'newNetworkName', required)
| &nbsp;
.form-group
label.control-label(for = 'newNetworkDescription') Description&nbsp;
input#newNetworkDescription.form-control(type = 'text', ng-model = 'newNetworkDescription', placeholder= 'Network created with Xen Orchestra')
| &nbsp;
.form-group
label.control-label(for = 'newNetworkVlan') VLAN&nbsp;
input#newNetworkVlan.form-control(type = 'text', ng-model = 'newNetworkVlan', placeholder = 'Defaut: no VLAN')
| &nbsp;
.form-group
label(for = 'newNetworkMTU') MTU&nbsp;
input#newNetworkMTU.form-control(type = 'text', ng-model = 'newNetworkMTU', placeholder = 'Default: 1500')
| &nbsp;
.form-group
button.btn.btn-primary(type = 'submit')
i.fa.fa-plus-square
| Create
span(ng-if = 'createNetworkWaiting')
| &nbsp;
i.fa.fa-spin.fa-circle-o-notch
br
//- CPU and Logs panels
.grid
//- Task panel
.panel.panel-default
.panel-heading.panel-title(ng-if="host.tasks.length")
i.fa.fa-spinner.fa-pulse(style="color: #e25440;")
| Pending tasks
.panel-heading.panel-title(ng-if="!host.tasks.length")
i.fa.fa-spinner(style="color: #e25440;")
| Pending tasks
.panel-body
p.center(ng-if="!host.tasks.length") No recent tasks
table.table.table-hover(ng-if="host.tasks.length")
th Date
th Progress
th 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
.progress-condensed
.progress-bar.progress-bar-success.progress-bar-striped.active.progress-bar-black(role="progressbar", aria-valuemin="0", aria-valuenow="{{task.progress*100}}", aria-valuemax="100", style="width: {{task.progress*100}}%", tooltip="Progress: {{task.progress*100 | number:1}}%")
| {{task.progress*100 | number:1}}%
td
| {{task.name_label}}
span.pull-right.btn-group.quick-buttons
a(xo-click="cancelTask(task.UUID)")
i.fa.fa-times.fa-lg(tooltip="Cancel this task")
a(xo-click="destroyTask(task.UUID)")
i.fa.fa-trash-o.fa-lg(tooltip="Destroy this task")
//- Logs panel
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-comments(style="color: #e25440;")
| Logs
span.quick-edit(ng-if="host.messages.length", tooltip="Remove all logs", ng-click="deleteAllLog()")
i.fa.fa-trash-o.fa-fw
.panel-body
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}}
span.pull-right.btn-group.quick-buttons
a(xo-click="deleteLog(message.UUID)")
i.fa.fa-trash-o.fa-lg(tooltip="Remove this log entry")
.grid
//- Patches panel
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-file-code-o(style="color: #e25440;")
| Patches
.panel-body
p.center(ng-if="!host.patches.length") No patches
table.table.table-hover(ng-if="host.patches.length")
th Applied on
th Name
th Description
th Status
tr(ng-repeat="patch in host.patches | resolve | orderBy:'-time'")
td {{patch.time*1e3 | date:"medium"}}
td {{(patch.pool_patch | resolve).name_label}}
td {{(patch.pool_patch | resolve).name_description}}
//- TODO: allow patch application and removal
td
span(ng-if="patch.applied")
span.label.label-success Applied
span(ng-if="!patch.applied")
span.label.label-error Not applied
//- span.pull-right.btn-group.quick-buttons
//- a(xo-click="deletePatch(patch.UUID)")
//- i.fa.fa-trash-o.fa-lg(tooltip="Remove this patch")
.grid
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-plug(style="color: #e25440;")
| PCI Devices
.panel-body
p.center(ng-if="!host.$PCIs") No PCI devices available
table.table.table-hover(ng-if="host.$PCIs")
th PCI Info
th Device Name
tr(ng-repeat="pci in host.$PCIs | resolve | orderBy:'pci_id' track by pci.UUID")
td {{pci.pci_id}} ({{pci.class_name}})
td {{pci.device_name}}
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-desktop(style="color: #e25440;")
| GPUs
.panel-body
p.center(ng-if="host.$PGPUs.length === 0") No GPUs available
table.table.table-hover(ng-if="host.$PGPUs.length !== 0")
th Device
tr(ng-repeat="pgpu in host.$PGPUs | resolve | orderBy:'device' track by pgpu.UUID")
td {{pgpu.device}}

View File

@@ -1,27 +0,0 @@
angular = require 'angular'
#=====================================================================
module.exports = angular.module 'xoWebApp.isoDevice', []
.directive 'isoDevice', -> {
restrict: 'E'
template: require './view'
scope: {
isos: '='
vm: '='
}
controller: 'IsoDevice as isoDevice'
bindToController: true
}
.controller 'IsoDevice', (xo) ->
this.eject = (VM) ->
xo.vm.ejectCd VM.UUID
this.insert = (VM, disc_id) ->
xo.vm.insertCd VM.UUID, disc_id, true
# A module exports its name.
.name

View File

@@ -1,15 +0,0 @@
.form-inline
.form-group
.input-group
select.form-control(
ng-model = 'isoDevice.isos.mounted'
ng-change = 'isoDevice.insert(isoDevice.vm, isoDevice.isos.mounted)'
ng-options = 'iso.iso.UUID as iso.label group by iso.sr for iso in isoDevice.isos.opts'
)
option(value = '', disabled) -- CD Drive (empty) --
.input-group-btn
button.btn.btn-default(
ng-click = 'isoDevice.eject(isoDevice.vm)'
ng-disabled = '!isoDevice.isos.mounted'
)
i.fa.fa-eject

View File

@@ -1,27 +0,0 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import xoApi from 'xo-api';
import view from './view';
//====================================================================
export default angular.module('xoWebApp.list', [
uiRouter,
xoApi,
])
.config(function ($stateProvider) {
$stateProvider.state('list', {
url: '/list',
controller: 'ListCtrl as list',
template: view,
});
})
.controller('ListCtrl', function (xoApi) {
this.byTypes = xoApi.byTypes;
})
// A module exports its name.
.name
;

View File

@@ -1,164 +0,0 @@
//- TODO: print a message when no entries.
//- If it's a (named) pool.
.grid.flat-object(ng-repeat="pool in list.byTypes.pool | xoHideUnauthorized | 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 list.byTypes.host | xoHideUnauthorized | 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"]}} cores
.grid-cell.flat-cell
.progress-condensed
.progress-bar(role="progressbar", aria-valuemin="0", aria-valuenow="{{100*host.memory.usage/host.memory.size}}", aria-valuemax="100", style="width: {{[host.memory.usage, host.memory.size] | %}}", tooltip="RAM: {{[host.memory.usage, host.memory.size] | %}} allocated")
| {{[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 list.byTypes.VM | xoHideUnauthorized | 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:&nbsp;
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:&nbsp;
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 list.byTypes.SR | xoHideUnauthorized | 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&nbsp;
a(ui-sref="hosts_view({id: container.UUID})") {{container.name_label}}
//- /Properties.
//- Tags.
.grid
.grid-cell
.grid-cell.flat-cell-tag
i.fa.fa-tag
span(ng-repeat="tag in SR.tags")
span.label.label-primary {{tag}}
//- /Tags.
//- /Properties & tags.
//- /SR.

View File

@@ -1,65 +0,0 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import view from './view';
//====================================================================
export default angular.module('xoWebApp.login', [
uiRouter,
])
.config(function ($stateProvider) {
$stateProvider.state('login', {
url: '/login',
controller: 'LoginCtrl',
template: view,
});
})
.controller('LoginCtrl', function($scope, $state, $rootScope, xoApi, notify) {
var toState, toStateParams;
{
let tmp = $rootScope._login;
if (tmp) {
toState = tmp.state.name;
toStateParams = tmp.stateParams;
delete $rootScope._login;
} else {
toState = 'index';
}
}
$scope.$watch(() => xoApi.user, function (user) {
// When the user is logged in, go the wanted view, fallbacks on
// the index view if necessary.
if (user) {
$state.go(toState, toStateParams).catch(function () {
$state.go('index');
});
}
});
Object.defineProperties($scope, {
user: {
get() {
return xoApi.user;
},
},
status: {
get() {
return xoApi.status;
}
},
});
$scope.logIn = (...args) => {
xoApi.logIn(...args).catch(error => {
notify.warning({
title: 'Authentication failed',
message: error.message,
});
});
};
})
// A module exports its name.
.name
;

View File

@@ -1,54 +0,0 @@
//- 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 = '$broadcast("fixAutofill"); logIn(email, password, true)'
)
fieldset
legend.login: h3 Sign in
div.form-group
div.col-sm-12
.input-group
span.input-group-addon: i.fa.fa-user.fa-fw
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-group
span.input-group-addon: i.fa.fa-key.fa-fw
input.form-control.input-sm(
name = 'password'
type = 'password'
placeholder = 'Password'
ng-model = 'password'
required
fix-autofill
)
div.form-group
div.col-sm-12
button.btn.btn-login.btn-block.btn-success(
id = 'login'
name = 'login'
)
i.fa.fa-sign-in
| Login
p.status(ng-if = '"disconnected" === status')
i.xo-icon-error.fa-2x(tooltip = 'You are not connected to XO-Server')
p.status(ng-if = '"connecting" === status')
i.fa.fa-refresh.fa-spin.fa-2x(tooltip = 'Connecting to XO-Server')
p.status(ng-if = '"connected" === status')
i.xo-icon-success.fa-2x(tooltip = 'You are connected to XO-Server')

View File

@@ -1,59 +0,0 @@
import angular from 'angular';
import filter from 'lodash.filter';
import uiRouter from 'angular-ui-router';
import xoServices from 'xo-services';
import view from './view';
//====================================================================
export default angular.module('xoWebApp.navbar', [
uiRouter,
xoServices,
])
.controller('NavbarCtrl', function ($state, xoApi, xo, $scope) {
// TODO: It would make sense to inject xoApi in the scope.
Object.defineProperties(this, {
status: {
get: () => xoApi.status,
},
user: {
get: () => xoApi.user,
},
});
this.logIn = xoApi.logIn;
this.logOut = function () {
xoApi.logOut();
$state.go('login');
};
// When a searched is entered, we must switch to the list view if
// necessary.
this.ensureListView = function () {
$state.go('list');
};
const ALIVE_STATUS = {
cancelling: true,
pending: true,
};
let {canAccess} = xo;
let sieve = (task) => ALIVE_STATUS[task.status] && canAccess(task.$host);
$scope.$watchCollection(() => xoApi.byTypes.task, (tasks) => {
this.tasks = filter(tasks, sieve);
});
})
.directive('navbar', function () {
return {
restrict: 'E',
controller: 'NavbarCtrl as navbar',
template: view,
scope: {},
};
})
// A module exports its name.
.name
;

View File

@@ -1,123 +0,0 @@
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 = 'index')
img.navbar-logo(src="images/logo.png")
| 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 = 'navbar.ensureListView()'
)
span.input-group-btn
button.btn.btn-search(
type = 'button'
ng-click = 'navbar.ensureListView()'
)
i.fa.fa-search
//- /Search form.
ul.nav.navbar-nav
li
a(href="https://xen-orchestra.com/#/pricing?pk_campaign=xoa_source", target="_blank")
i.xo-icon-info.text-danger
| Unregistered version: no support provided!
//- Right items of the navbar.
ul.nav.navbar-nav.navbar-right
li.navbar-text(ng-if="'disconnected' === navbar.status")
i.xo-icon-error
| Disconnected from XO-Server
li.navbar-text(ng-if="'connecting' === navbar.status")
i.fa.fa-refresh.fa-spin
| Connecting to XO-Server
//- Running tasks
li.disabled(ng-if="!navbar.tasks.length", tooltip="No running tasks")
a.dropdown-toggle.inverse
i.xo-icon-task
li.dropdown(dropdown, ng-if="navbar.tasks.length")
a.dropdown-toggle.inverse(dropdown-toggle)
i.xo-icon-task
ul.dropdown-menu.inverse
li.task-menu(
ng-repeat="task in navbar.tasks | orderBy:natural('name_label') track by task.id"
)
a(
ui-sref="hosts_view({id: task.$host})"
tooltip = "{{task.name_label}}"
)
//- i.fa.fa-spinner.fa-fw
//- | {{task.name_label}}
.progress-condensed
.progress-bar.progress-bar-success.progress-bar-striped.active.progress-bar-black(
role = "progressbar"
aria-valuemin = "0"
aria-valuenow = "{{task.progress*100}}"
aria-valuemax = "100"
style = "width: {{task.progress*100}}%"
)
| {{task.progress*100 | number:1}}%
//- Main menu.
li.dropdown(dropdown)
a.dropdown-toggle.inverse(dropdown-toggle)
i.fa.fa-th
ul.dropdown-menu.inverse
li(
ui-sref-active = 'active'
ng-class = '{ disabled: navbar.user.permission !== "admin" }'
)
a(ui-sref = 'tree')
i.fa.fa-indent
| Tree view
li(ui-sref-active="active")
a(ui-sref="list")
i.fa.fa-align-justify
| Flat view
//- li.disabled(ui-sref-active="active")
//- 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'
ng-class = '{ disabled: navbar.user.permission !== "admin" }'
)
a(ui-sref="settings.index")
i.fa.fa-cog
| Settings
li.divider
li(ui-sref-active="active")
a(ui-sref="about")
i.fa.fa-info-circle(style="color:#5bc0de")
| About
//- /Main menu.
li
a
i.fa.fa-user
| {{navbar.user.email}}
li
a(ng-click = 'navbar.logOut()')
i.fa.fa-sign-out
| &nbsp;
| &nbsp;
//- /Right items.
//- /Navbar items.
//- /Navbar.

View File

@@ -1,476 +0,0 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import Bluebird from 'bluebird';
import view from './view';
import _indexOf from 'lodash.indexof';
//====================================================================
export default angular.module('xoWebApp.newSr', [
uiRouter
])
.config(function ($stateProvider) {
$stateProvider.state('SRs_new', {
url: '/srs/new/:container',
controller: 'NewSrCtrl as newSr',
template: view,
});
})
.controller('NewSrCtrl', function ($scope, $state, $stateParams, xo, xoApi, notify, modal, bytesToSizeFilter) {
this.reset = function (data = {}) {
this.data = {};
delete this.lockCreation;
this.lock = !(
('Local' === data.srType) &&
(data.srPath && data.srPath.path)
);
};
this.resetLists = function() {
delete this.data.nfsList;
delete this.data.scsiList;
delete this.lockCreation;
this.lock = true;
this.resetErrors();
};
this.resetErrors = function () {
delete this.data.error;
};
/*
* Loads NFS paths and iScsi iqn`s
*/
this.populateSettings = function (type, server, auth, user, password) {
this.reset();
this.loading = true;
server = this._parseAddress(server);
if ('NFS' === type || 'NFS_ISO' === type) {
xoApi.call('sr.probeNfs', {
host: this.container.UUID,
server: server.host
})
.then(response => this.data.paths = response)
.catch(error => notify.warning({
title : 'NFS Detection',
message : error.message
}))
.finally(() => this.loading = false)
;
} else if ('iSCSI' === type) {
let params = {
host: this.container.UUID
};
if (auth) {
params.chapUser = user;
params.chapPassword = password;
}
params.target = server.host;
if (server.port) {
params.port = server.port;
}
xoApi.call('sr.probeIscsiIqns', params)
.then(response => {
if (response.length > 0) {
this.data.iqns = response;
} else {
notify.warning({
title : 'iSCSI Detection',
message : 'No IQNs found'
});
}
})
.catch(error => notify.warning({
title : 'iSCSI Detection',
message : error.message
}))
.finally(() => this.loading = false)
;
} else {
this.loading = false;
}
};
/*
* Loads iScsi LUNs
*/
this.populateIScsiIds = function (iqn, auth, user, password) {
delete this.data.iScsiIds;
this.loading = true;
let params = {
host: this.container.UUID,
target: iqn.ip,
targetIqn: iqn.iqn
};
if (auth) {
params.chapUser = user;
params.chapPassword = password;
}
xoApi.call('sr.probeIscsiLuns', params)
.then(response => {
response.forEach(item => {
item.display = 'LUN ' + item.id + ': ' +
item.serial + ' ' + bytesToSizeFilter(item.size) +
' (' + item.vendor + ')';
});
this.data.iScsiIds = response;
})
.catch(error => notify.warning({
title : 'LUNs Detection',
message : error.message
}))
.finally(() => this.loading = false)
;
};
this._parseAddress = function (address) {
let index = address.indexOf(':');
let port = false;
let host = address;
if (-1 < index) {
port = address.substring(index + 1);
host = address.substring(0, index);
}
return {
host,
port
};
};
this._prepareNfsParams = function (data) {
let server = this._parseAddress(data.srServer);
let params = {
host: this.container.UUID,
nameLabel: data.srName,
nameDescription: data.srDesc,
server: server.host,
serverPath: data.srPath.path
};
return params;
};
this._prepareScsiParams = function(data) {
let params = {
host: this.container.UUID,
nameLabel: data.srName,
nameDescription: data.srDesc,
target: data.srIqn.ip,
targetIqn: data.srIqn.iqn,
scsiId: data.srIScsiId.scsiId,
};
let server = this._parseAddress(data.srServer);
if (server.port) {
params.port = server.port;
}
if (data.srAuth) {
params.chapUser = data.srChapUser;
params.chapPassword = data.srChapPassword;
}
return params;
};
this.createSR = function (data) {
this.lock = true;
this.creating = true;
let operationToPromise;
switch(data.srType) {
case 'NFS':
let nfsParams = this._prepareNfsParams(data);
operationToPromise = this._checkNfsExistence(nfsParams)
.then(() => xoApi.call('sr.createNfs', nfsParams))
;
break;
case 'iSCSI':
let scsiParams = this._prepareScsiParams(data);
operationToPromise = this._checkScsiExistence(scsiParams)
.then(() => xoApi.call('sr.createIscsi', scsiParams))
;
break;
case 'lvm':
let device = data.srDevice.device;
operationToPromise = xoApi.call('sr.createLvm', {
host: this.container.UUID,
nameLabel: data.srName,
nameDescription: data.srDesc,
device
});
break;
case 'NFS_ISO':
case 'Local':
let server = this._parseAddress(data.srServer || '');
let path = (('NFS_ISO' === data.srType) ?
server.host + ':' :
'') + data.srPath.path;
operationToPromise = xoApi.call('sr.createIso', {
host: this.container.UUID,
nameLabel: data.srName,
nameDescription: data.srDesc,
path
});
break;
default:
operationToPromise = Bluebird.reject({message: 'Unhanled SR Type'});
break;
}
operationToPromise
.then(id => {
$state.go('SRs_view', {id});
})
.catch(error => {
notify.error({
title : 'Storage Creation Error',
message : error.message
});
})
.finally(() => {
this.lock = false;
this.creating = false;
})
;
};
this._checkScsiExistence = function (params) {
this.resetLists();
return xoApi.call('sr.probeIscsiExists', params)
.then(response => {
if (response.length > 0) {
this.data.scsiList = response;
return modal.confirm({
title: 'Previous LUN Usage',
message: 'This LUN has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation. Are you sure?'
});
} else {
return Bluebird.resolve(true);
}
})
;
};
this._checkNfsExistence = function (params) {
this.resetLists();
return xoApi.call('sr.probeNfsExists', params)
.then(response => {
if (response.length > 0) {
this.data.nfsList = response;
return modal.confirm({
title: 'Previous Path Usage',
message: 'This path has been previously used as a Storage by a XenServer host. All data will be lost if you choose to continue the SR creation. Are you sure?'
});
} else {
return Bluebird.resolve(true);
}
})
;
};
this._gatherConnectedUuids = function() {
let SRs = [];
let pool = xoApi.get(this.container.poolRef);
pool.SRs.forEach(ref => SRs.push(xoApi.get(ref).UUID));
let hosts = [];
pool.hosts.forEach(ref => hosts.push(xoApi.get(ref)));
hosts.forEach(h => h.SRs.forEach(ref => SRs.push(xoApi.get(ref).UUID)));
return SRs;
};
this._processSRList = function (list) {
let inUse = false;
let SRs = this._gatherConnectedUuids();
list.forEach(item => {
inUse = (item.used = _indexOf(SRs, item.uuid) > -1) || inUse;
});
this.lockCreation = inUse;
return list;
};
this.loadScsiList = function(data) {
this.resetLists();
let params = this._prepareScsiParams(data);
xoApi.call('sr.probeIscsiExists', params)
.then(response => {
if (response.length > 0) {
this.data.scsiList = this._processSRList(response);
}
this.lock = !Boolean(data.srIScsiId);
})
.catch(error => {
notify.error({
title : 'iSCSI Error',
message : error.message
});
})
;
};
this.loadNfsList = function (data) {
this.resetLists();
let server = this._parseAddress(data.srServer);
xoApi.call('sr.probeNfsExists', {
host: this.container.UUID,
server: server.host,
serverPath: data.srPath.path
})
.then(response => {
if (response.length > 0) {
this.data.scsiList = this._processSRList(response);
}
this.lock = !Boolean(data.srPath.path);
})
.catch(error => {
notify.error({
title : 'NFS error',
message : error.message
});
})
;
};
this.reattachNfs = function (uuid, {name, nameError}, {desc, descError}, iso) {
this._reattach(uuid, 'nfs', {name, nameError}, {desc, descError}, iso);
};
this.reattachIScsi = function (uuid, {name, nameError}, {desc, descError}) {
this._reattach(uuid, 'iscsi', {name, nameError}, {desc, descError});
};
this._reattach = function(uuid, type, {name, nameError}, {desc, descError}, iso = false) {
this.resetErrors();
let method = 'sr.reattach' + (iso ? 'Iso' : '');
if (nameError || descError) {
this.data.error = {
name: nameError,
desc: descError
};
notify.warning({
title: 'Missing parameters',
message: 'Complete the General section information, please'
});
} else {
this.lock = true;
this.attaching = true;
xoApi.call(method, {
host: this.container.UUID,
uuid,
nameLabel: name,
nameDescription: desc,
type
})
.then(id => {
$state.go('SRs_view', {id});
})
.catch(error => notify.error({
title : 'reattach',
message : error.message
})
)
.finally(() => {
this.lock = false;
this.attaching = false;
})
;
}
};
this.reset();
$scope.$watch(() => xoApi.get($stateParams.container), container => {
this.container = container;
});
})
// A module exports its name.
.name
;

View File

@@ -1,183 +0,0 @@
.grid
.panel.panel-default
p.page-title
i.xo-icon-sr
| Add SR on&nbsp;
a(ng-if="'pool' === newSr.container.type", ui-sref="pools_view({id: newSr.container.UUID})")
| {{newSr.container.name_label}}
a(ng-if="'host' === newSr.container.type", ui-sref="hosts_view({id: newSr.container.UUID})")
| {{newSr.container.name_label}}
form.form-horizontal(name = 'srForm' ng-submit="newSr.createSR(formData)")
.grid
//- Choose SR type panel
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-info-circle(style="color: #e25440;")
| General
.panel-body
.form-group
label.col-sm-3.control-label Type
.col-sm-9
select.form-control(ng-change = 'newSr.reset(formData)', ng-model = 'formData.srType', name = 'srType', ng-required = 'true')
option(value="") -- Choose a type of SR --
optgroup(label="VDI SR")
option(value="NFS") NFS
option(value="iSCSI") iSCSI
option(value="lvm") Local LVM
optgroup(label="ISO SR")
option(value="Local") Local
option(value="NFS_ISO") NFS ISO
.form-group(ng-class = '{"has-error": newSr.data.error.name}')
label.col-sm-3.control-label Name
.col-sm-9
input.form-control(type="text", placeholder="", name = 'srName', ng-model = 'formData.srName', ng-required = 'true')
.form-group(ng-class = '{"has-error": newSr.data.error.desc}')
label.col-sm-3.control-label Description
.col-sm-9
input.form-control(type="text", placeholder="SR Created by Xen Orchestra", name = 'srDesc', ng-model = 'formData.srDesc', ng-required = 'true')
//- Choose SR details
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-cogs(style="color: #e25440;")
| Settings
.panel-body
.form-group(ng-if = 'formData.srType === "NFS" || formData.srType === "iSCSI"')
label.col-sm-3.control-label
| Server
span(ng-if = 'formData.srType === "iSCSI"')
| &nbsp;(auth&nbsp;
input(type = 'checkbox', ng-model = 'formData.srAuth')
| &nbsp;)
.col-sm-9
.input-group
input.form-control(type="text", placeholder='address{{ formData.srType === "iSCSI" ? "[:port]" : "" }}', name = 'srServer', ng-model = 'formData.srServer', required)
span.input-group-btn
button.btn.btn-default(type = 'button', ng-click = 'newSr.populateSettings(formData.srType, formData.srServer, formData.srAuth, formData.srChapUser, formData.srChapPassword)')
i.fa.fa-search
//- For Local LVM
.form-group(ng-if = 'formData.srType === "lvm"')
label.col-sm-3.control-label Device
.col-sm-9
input.form-control(
ng-if = 'formData.srType === "lvm"'
type = 'text'
name = 'srDevice'
ng-model = 'formData.srDevice.device'
placeholder = 'Device, e.g /dev/sda...'
ng-change = 'newSr.lock = !formData.srDevice.device'
required
)
.form-group(ng-if = 'newSr.data.paths || formData.srType === "Local"')
label.col-sm-3.control-label Path
.col-sm-9
//- For NFS
select.form-control(
ng-if = 'newSr.data.paths'
name = 'srPath'
ng-change = 'newSr.loadNfsList(formData)'
ng-model = 'formData.srPath'
ng-options = 'item.path for item in newSr.data.paths', required)
option(value = '', disabled) -- Choose path --
//- For Local
input.form-control(
ng-if = 'formData.srType === "Local"'
type = 'text'
name = 'srPath'
ng-model = 'formData.srPath.path'
ng-change = 'newSr.lock = !formData.srPath.path'
required
)
//- For iScsi
.form-group(ng-if = 'formData.srType === "iSCSI"')
.col-sm-9.col-sm-offset-3.form-inline(ng-if = 'formData.srAuth')
label.sr-only(for = 'chapUser') User
input#chapUser.form-control(type = 'text', ng-model = 'formData.srChapUser', placeholder = 'user', ng-required = 'formData.srAuth')
| &ensp;
label.sr-only(for = 'chapUser') Password
input#chapPassword.form-control(type = 'password', ng-model = 'formData.srChapPassword', placeholder = 'password', ng-required = 'formData.srAuth')
.form-group(ng-if = 'newSr.data.iqns')
label.col-sm-3.control-label IQN
.col-sm-9
select.form-control(ng-change = 'newSr.populateIScsiIds(formData.srIqn, formData.srAuth, formData.srChapUser, formData.srChapPassword)', name = 'srIqn', ng-model = 'formData.srIqn', ng-options = '(item.iqn + " (" + item.ip + ")") for item in newSr.data.iqns', required)
option(value = '', disabled) -- Choose IQN --
.form-group(ng-if = 'newSr.data.iScsiIds')
label.col-sm-3.control-label LUN
.col-sm-9
select.form-control(name = 'srIScsiId', ng-change = 'newSr.loadScsiList(formData)', ng-model = 'formData.srIScsiId', ng-options = 'item.display for item in newSr.data.iScsiIds', required)
option(value = '', disabled) -- Choose LUN --
.form-group.text-center(ng-if = 'newSr.loading')
i.fa.fa-circle-o-notch.fa-spin.fa-2x
.grid(ng-if = 'newSr.data.nfsList && newSr.data.nfsList.length > 0')
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-eye(style="color: #e25440;")
| NFS storage use
.panel-body
table.table.table-condensed
tr
th.text-center Storage UUID
th
tr(ng-repeat = 'nfsSr in newSr.data.nfsList')
td.text-center {{ nfsSr.uuid }}
td.text-center(ng_if = '!nfsSr.used')
button.btn.btn-sm.btn-primary(type = 'button', ng-class = '{disabled: newSr.lock}', ng-click = 'newSr.reattachNfs(nfsSr.uuid, {name: formData.srName, nameError: srForm.srName.$error.required}, {desc: formData.srDesc, descError: srForm.srDesc.$error.required}, "NFS_ISO" === formData.srType)') Reattach
td.text-center(ng_if = 'nfsSr.used', ng-class = '{disabled: newSr.lock}')
button.btn.btn-sm.btn-danger(ui-sref = 'SRs_view({id: nfsSr.uuid})', ng-class = '{disabled: newSr.lock}')
i.fa.fa-eye
| In use
p.text-center(ng-if = 'newSr.attaching')
i.fa.fa-circle-o-notch.fa-spin.fa-2x
.grid(ng-if = 'newSr.data.scsiList && newSr.data.scsiList.length > 0')
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-eye(style="color: #e25440;")
| iSCSI storage use
.panel-body
table.table.table-condensed
tr
th.text-center Storage UUID
th
tr(ng-repeat = 'scsiSr in newSr.data.scsiList')
td.text-center {{ scsiSr.uuid }}
td.text-center(ng_if = '!scsiSr.used')
button.btn.btn-sm.btn-primary(type = 'button', ng-class = '{disabled: newSr.lock}', ng-click = 'newSr.reattachIScsi(scsiSr.uuid, {name: formData.srName, nameError: srForm.srName.$error.required}, {desc: formData.srDesc, descError: srForm.srDesc.$error.required})') Reattach
td.text-center(ng_if = 'scsiSr.used')
button.btn.btn-sm.btn-danger(ui-sref = 'SRs_view({id: scsiSr.uuid})', ng-class = '{disabled: newSr.lock}')
i.fa.fa-eye
| In use
p.text-center(ng-if = 'newSr.attaching')
i.fa.fa-circle-o-notch.fa-spin.fa-2x
//- Summary
.grid
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-flag-checkered(style="color: #e25440;")
| Summary
.panel-body
.grid
.grid-cell
p.stat-name
| Name:
p.center.big {{formData.srName}}
.grid-cell
p.stat-name
| Type:
p.center.big {{formData.srType}}
.grid-cell
div(ng-if = 'formData.srType === "iSCSI"')
p.stat-name Size
p.center.big {{formData.srIScsiId.size | bytesToSize}}
div(ng-if = 'formData.srType === "NFS"')
p.stat-name Path
p.center.big {{formData.srPath.path}}
p.center
button.btn.btn-lg.btn-primary(type="submit", ng-disabled = 'newSr.lock || newSr.lockCreation')
i.fa.fa-play
| &nbsp;Create SR&nbsp;
i.fa.fa-circle-o-notch.fa-spin(ng-if = 'newSr.creating')

View File

@@ -1,232 +0,0 @@
angular = require 'angular'
#=====================================================================
module.exports = angular.module 'xoWebApp.newVm', [
require 'angular-ui-router'
]
.config ($stateProvider) ->
$stateProvider.state 'VMs_new',
url: '/vms/new/:container'
controller: 'NewVmsCtrl'
template: require './view'
.controller 'NewVmsCtrl', (
$scope, $stateParams, $state
xoApi, xo
bytesToSizeFilter, sizeToBytesFilter
notify
) ->
{get} = xoApi
removeItems = do ->
splice = Array::splice.call.bind Array::splice
(array, index, n) -> splice array, index, n ? 1
merge = do ->
push = Array::push.apply.bind Array::push
(args...) ->
result = []
for arg in args
push result, arg if arg?
result
pool = default_SR = null
$scope.$watch(
-> get $stateParams.container
(container) ->
$scope.container = container
# If the container was not found, no need to continue.
return unless container?
if container.type is 'host'
host = container
pool = (get container.poolRef) ? {}
else
host = {}
pool = container
default_SR = get pool.default_SR
default_SR = if default_SR
default_SR.UUID
else
''
# Computes the list of templates.
$scope.templates = get (merge pool.templates, host.templates)
# FIXME: We should filter on connected SRs (PBDs)!
# Computes the list of SRs.
SRs = get (merge pool.SRs, host.SRs)
# Computes the list of ISO SRs.
$scope.ISO_SRs = (SR for SR in SRs when SR.content_type is 'iso')
# Computes the list of writable SRs.
$scope.writable_SRs = (SR for SR in SRs when SR.content_type isnt 'iso')
# Computes the list of networks.
$scope.networks = get pool.networks
)
$scope.availableMethods = {}
$scope.CPUs = ''
$scope.installation_cdrom = ''
$scope.installation_method = ''
$scope.installation_network = ''
$scope.memory = ''
$scope.name_description = ''
$scope.name_label = ''
$scope.template = ''
$scope.VDIs = []
$scope.VIFs = []
$scope.addVIF = do ->
id = 0
->
$scope.VIFs.push {
id: id++
network: ''
}
$scope.addVIF()
$scope.removeVIF = (index) -> removeItems $scope.VIFs, index
$scope.moveVDI = (index, direction) ->
{VDIs} = $scope
newIndex = index + direction
[VDIs[index], VDIs[newIndex]] = [VDIs[newIndex], VDIs[index]]
$scope.removeVDI = (index) -> removeItems $scope.VDIs, index
VDI_id = 0
$scope.addVDI = ->
$scope.VDIs.push {
id: VDI_id++
bootable: false
size: ''
SR: default_SR
type: 'system'
}
# When the selected template changes, updates other variables.
$scope.$watch 'template', (template) ->
return unless template
{install_methods} = template.template_info
availableMethods = $scope.availableMethods = Object.create null
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
VDI.id = VDI_id++
VDI.size = bytesToSizeFilter VDI.size
VDI.SR or= default_SR
$scope.createVM = ->
{
CPUs
installation_cdrom
installation_method
installation_network
memory
name_description
name_label
template
VDIs
VIFs
} = $scope
# Does not edit the displayed data directly.
VDIs = angular.copy VDIs
for VDI, index in VDIs
# Removes the dummy identifier used for AngularJS.
delete VDI.id
# Adds the device number based on the index.
VDI.device = "#{index}"
# Transforms the size from human readable format to bytes.
VDI.size = sizeToBytesFilter VDI.size
# TODO: handles invalid values.
# Does not edit the displayed data directly.
VIFs = angular.copy VIFs
for VIF in VIFs
# Removes the dummy identifier used for AngularJS.
delete VIF.id
# Removes the MAC address if empty.
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 if installation_network
matches = /^(http|ftp|nfs)/i.exec installation_network
throw new Error 'invalid network URL' unless matches
installation = {
method: matches[1].toLowerCase()
repository: installation_network
}
else
installation = undefined
data = {
installation
name_label
template: template.UUID
VDIs
VIFs
}
# TODO:
# - disable the form during creation
# - indicate the progress of the operation
notify.info {
title: 'VM creation'
message: 'VM creation started'
}
xoApi.call('vm.create', data).then (id) ->
# If nothing to sets, just stops.
return id unless CPUs or name_description or memory
data = {
id
}
data.CPUs = +CPUs if CPUs
if name_description
data.name_description = name_description
if memory
memory = sizeToBytesFilter memory
# FIXME: handles invalid entries.
data.memory = memory
xoApi.call('vm.set', data).then -> id
.then (id) ->
$state.go 'VMs_view', { id }
.catch (error) ->
notify.error {
title: 'VM creation'
message: 'The creation failed'
}
console.log error
# A module exports its name.
.name

View File

@@ -1,203 +0,0 @@
.grid
.panel.panel-default
p.page-title
i.xo-icon-vm
| Create VM on&nbsp;
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
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-info-circle(style="color: #e25440;")
| VM info
.panel-body
.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")
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-dashboard(style="color: #e25440;")
| Performances
.panel-body
.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
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-download(style="color: #e25440;")
| Install settings
.panel-body
.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
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-network(style="color: #e25440;")
| Interfaces
.panel-body
table.table.table-hover
tr
th MAC
th Network
th.col-md-1 &#160;
//- Buttons
tr(ng-repeat="VIF in VIFs track by VIF.id")
td
input.form-control(type="text", ng-model="VIF.MAC", ng-pattern="/^\s*[0-9a-f]{2}(:[0-9a-f]{2}){5}\s*$/i", placeholder="00:00:00:00:00")
td
select.form-control(
ng-options = 'network.UUID as network.name_label for network in (networks | orderBy:natural("name_label"))'
ng-model = 'VIF.network'
required
)
option(value = '') Please select
td
.pull-right
button.btn.btn-default(type="button", ng-click="removeVIF($index)", title="Remove this interface")
i.fa.fa-times
.btn-form
p.center
.btn-form
p.center
button.btn.btn-success(type="button", ng-click="addVIF()")
i.fa.fa-plus
| Add interface
//- end of misc and interface panel
//- Disk panel
.grid
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-sr(style="color: #e25440;")
| Disks
.panel-body
table.table.table-hover
tr
th.col-md-2 SR
th.col-md-1 Bootable?
th.col-md-2 Size
th.col-md-2 Name
th.col-md-4 Description
th.col-md-1 &#160;
//- Buttons
tr(ng-repeat="VDI in VDIs track by VDI.id")
td
select.form-control(ng-model="VDI.SR", ng-options="SR.UUID as (SR.name_label + ' (' + (SR.size - SR.usage | bytesToSize) + ' free)') 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
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-flag-checkered(style="color: #e25440;")
| Summary
.panel-body
.grid
.grid-cell
p.stat-name
| Name:
p.center.big {{name_label}}
.grid-cell
p.stat-name
| Template:
p.center {{template.name_label}}
.grid
.grid-cell
p.stat-name vCPUs
p.center.big {{CPUs || template.CPUs.number}}
.grid-cell
p.stat-name RAM
p.center.big {{(memory) || (template.memory.size | bytesToSize)}}
.grid-cell
p.stat-name Disks
p.center.big {{(VDIs.length) || (template.$VBDs.length) || 0}}
.grid-cell
p.stat-name Interfaces
p.center.big {{VIFs.length}}
p.center
button.btn.btn-lg.btn-primary(type="submit")
i.fa.fa-play
| Create VM

View File

@@ -1,87 +0,0 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import view from './view';
//====================================================================
export default angular.module('xoWebApp.pool', [
uiRouter,
])
.config(function ($stateProvider) {
$stateProvider.state('pools_view', {
url: '/pools/:id',
controller: 'PoolCtrl',
template: view,
});
})
.controller('PoolCtrl', function ($scope, $stateParams, xoApi, xo, modal) {
$scope.$watch(() => xoApi.get($stateParams.id), function (pool) {
$scope.pool = pool;
});
$scope.savePool = function ($data) {
let {pool} = $scope;
let {name_label, name_description} = $data;
$data = {
id: pool.UUID,
}
if (name_label !== pool.name_label) {
$data.name_label = name_label;
}
if (name_description !== pool.name_description) {
$data.name_description = name_description;
}
xoApi.call('pool.set', $data);
};
$scope.deleteAllLog = function () {
return modal.confirm({
title: 'Log deletion',
message: 'Are you sure you want to delete all the logs?',
}).then(function () {
// TODO: return all promises.
angular.forEach($scope.pool.messages, function(value, key) {
xo.log.delete(value);
console.log('Remove log', value);
});
});
};
$scope.deleteLog = function (id) {
console.log('Remove log', id);
return xo.log.delete(id);
};
// $scope.patchPool = ($files, id) ->
// file = $files[0]
// xo.pool.patch id
// .then ({ $sendTo: url }) ->
// return $upload.http {
// method: 'POST'
// url
// data: file
// }
// .progress throttle(
// (event) ->
// percentage = (100 * event.loaded / event.total)|0
// notify.info
// title: 'Upload patch'
// message: "#{percentage}%"
// 6e3
// )
// .then (result) ->
// throw result.status if result.status isnt 200
// notify.info
// title: 'Upload patch'
// message: 'Success'
})
// A module exports its name.
.name
;

View File

@@ -1,147 +0,0 @@
//- TODO: lots of stuff.
.grid
.panel.panel-default
p.page-title
i.xo-icon-pool
| {{pool.name_label}}
.grid
.panel.panel-default
.panel-heading.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
.panel-body
form(editable-form="", name="poolSettings", onbeforesave="savePool($data)")
dl.dl-horizontal
dt Name
dd
span(editable-text="pool.name_label", e-name="name_label", e-form="poolSettings")
| {{pool.name_label}}
dt Description
dd
span(editable-text="pool.name_description", e-name="name_description", e-form="poolSettings")
| {{pool.name_description}}
dt Master
dd(ng-repeat="master in [pool.master] | resolve")
a(ui-sref="hosts_view({id: master.UUID})")
| {{master.name_label}}
dt Tags
dd
span(ng-repeat="tag in pool.tags")
span.label.label-primary {{tag}}
dt(ng-if="pool.default_SR") Default SR
dd(ng-if="pool.default_SR", ng-init="default_SR = (pool.default_SR | resolve)")
a(ui-sref="SRs_view({id: default_SR.UUID})") {{default_SR.name_label}}
dt HA
dd
| {{pool.HA_enabled}}
dt UUID
dd {{pool.UUID}}
.btn-form(ng-show="poolSettings.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="poolSettings.$waiting", ng-click="poolSettings.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="submit", ng-disabled="poolSettings.$waiting")
i.fa.fa-save
| Save
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-stats(style="color: #e25440;")
| Stats
.grid
.grid-cell
p.stat-name Hosts:
p.center.big-stat {{pool.hosts.length}}
.grid-cell
p.stat-name Running:
p.center.big-stat {{pool.$running_hosts.length}}
//- Action panel
.grid
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-flash(style="color: #e25440;")
| Actions
.panel-body
.grid-cell.text-center
.grid
.grid-cell.btn-group
button.btn(tooltip="Add SR", type="button", style="width: 90%")
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: pool.UUID})")
i.xo-icon-vm.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Patch the pool", type="button", style="width: 90%", ng-file-select = "patchPool($files, pool.UUID)")
i.fa.fa-file-code-o.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Add Host", type="button", style="width: 90%")
i.xo-icon-host.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Disconnect", type="button", style="width: 90%; margin-bottom: 0.5em")
i.fa.fa-unlink.fa-2x.fa-fw
//- Hosts panel
.grid
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-host(style="color: #e25440;")
| Hosts
.panel-body
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
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-sr(style="color: #e25440;")
| Shared SR
.panel-body
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] | %}}")
//- Logs panel
.grid
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-comments(style="color: #e25440;")
| Logs
span.quick-edit(ng-if="pool.messages.length", tooltip="Remove all logs", xo-click="deleteAllLog()")
i.fa.fa-trash-o.fa-fw
.panel-body
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}}
span.pull-right.btn-group.quick-buttons
a(xo-click="deleteLog(message.UUID)")
i.fa.fa-trash-o.fa-lg(tooltip="Remove this log entry")

View File

@@ -1,80 +0,0 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import uiSelect from 'angular-ui-select';
import filter from 'lodash.filter';
import xoApi from 'xo-api';
import xoServices from 'xo-services';
import view from './view';
export default angular.module('settings.acls', [
uiRouter,
uiSelect,
xoApi,
xoServices,
])
.config(function ($stateProvider) {
$stateProvider.state('settings.acls', {
controller: 'SettingsAcls as ctrl',
url: '/acls',
resolve: {
acls(xo) {
return xo.acl.get();
},
users(xo) {
return xo.user.getAll();
},
},
template: view,
});
})
.controller('SettingsAcls', function ($scope, acls, users, xoApi, xo) {
this.acls = acls;
this.users = users;
{
let usersById = this.usersById = Object.create(null);
for (let user of users) {
usersById[user.id] = user;
}
}
this.objects = xoApi.all;
let refreshAcls = () => {
xo.acl.get().then(acls => {
this.acls = acls;
});
};
this.getUser = (id) => {
for (let user of this.users) {
if (user.id === id) {
return user;
}
}
};
this.addAcl = () => {
xo.acl.add(this.subject.id, this.object.id).then(refreshAcls);
};
this.removeAcl = (subject, object) => {
xo.acl.remove(subject, object).then(refreshAcls);
};
})
.filter('selectHighLevel', () => {
const HIGH_LEVEL_OBJECTS = {
pool: true,
host: true,
VM: true,
SR: true,
};
let isHighLevel = (object) => HIGH_LEVEL_OBJECTS[object.type];
return (objects) => filter(objects, isHighLevel);
})
.name
;

View File

@@ -1,66 +0,0 @@
.grid
.panel.panel-default
p.page-title
i.fa.fa-key(style="color: #e25440;")
| ACLs
.grid
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-plus-circle(style="color: #e25440;")
| Create
.panel-body.text-center
form(
ng-submit = 'ctrl.addAcl()'
)
.form-group
ui-select(
ng-model = 'ctrl.subject'
)
ui-select-match(
placeholder = 'Choose a user'
)
div
i.fa.fa-user
| {{$select.selected.email}}
ui-select-choices(
repeat = 'user in ctrl.users | filter:{ permission: "!admin" } | filter:$select.search'
)
div
i.fa.fa-user
| {{user.email}}
.form-group
ui-select(
ng-model = 'ctrl.object'
)
ui-select-match(
placeholder = 'Choose an object'
)
div
i(class = 'xo-icon-{{$select.selected.type | lowercase}}')
| {{$select.selected.name_label}}
ui-select-choices(
repeat = 'object in ctrl.objects | selectHighLevel | filter:$select.search | orderBy:["type", "name_label"]'
)
div
i(class = 'xo-icon-{{object.type | lowercase}}')
| {{object.name_label}}
button.btn.btn-success
i.fa.fa-plus
| Create
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-street-view(style="color: #e25440;")
| Manage
.panel-body
table.table.table-hover
tr
th User
th Object
th Action
tr(ng-repeat = 'acl in ctrl.acls')
td {{ctrl.usersById[acl.subject].email}}
td {{(acl.object | resolve).name_label}}
td
button.btn.btn-sm.btn-danger(ng-click = 'ctrl.removeAcl(acl.subject, acl.object)')
i.fa.fa-trash

View File

@@ -1,33 +0,0 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import acls from './acls';
import servers from './servers';
import users from './users';
import view from './view';
export default angular.module('settings', [
uiRouter,
acls,
servers,
users,
])
.config(function ($stateProvider) {
$stateProvider.state('settings', {
abstract: true,
template: view,
url: '/settings',
});
// Redirect to default sub-state.
$stateProvider.state('settings.index', {
url: '',
controller: function ($state) {
$state.go('settings.servers');
}
});
})
.name
;

View File

@@ -1,119 +0,0 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import uiSelect from 'angular-ui-select';
import filter from 'lodash.filter';
import xoApi from 'xo-api';
import xoServices from 'xo-services';
import view from './view';
export default angular.module('settings.servers', [
uiRouter,
uiSelect,
xoApi,
xoServices,
])
.config(function ($stateProvider) {
$stateProvider.state('settings.servers', {
controller: 'SettingsServers as ctrl',
url: '/servers',
resolve: {
servers(xo) {
return xo.server.getAll();
},
},
template: view,
});
})
.controller('SettingsServers', function ($scope, $interval, servers, xoApi, xo, notify) {
this.servers = servers;
const selected = this.selectedServers = {};
const newServers = this.newServers = [];
const refreshServers = () => {
xo.server.getAll().then(servers => {
this.servers = servers;
});
};
const interval = $interval(refreshServers, 10e3)
$scope.$on('$destroy', () => {
$interval.cancel(interval)
})
this.connectServer = (id) => {
xo.server.connect(id).catch(error => {
notify.error({
title: 'Server connection error',
message: error.message
});
});
};
this.disconnectServer = (id) => {
xo.server.disconnect(id);
};
this.addServer = () => {
newServers.push({
// Fake (unique) id needed by Angular.JS
id: Math.random(),
status: 'connecting'
});
};
this.addServer();
this.saveServers = () => {
const newServers = this.newServers;
const servers = this.servers;
const updateServers = [];
for (let i = 0, len = servers.length; i < len; i++) {
const server = servers[i];
const {id} = server;
if (selected[id]) {
delete selected[id];
xo.server.remove(id);
}
else {
if (!server.password) {
delete server.password;
}
xo.server.set(server);
delete server.password;
updateServers.push(server);
}
}
for (let i = 0, len = newServers.length; i < len; i++) {
const server = newServers[i];
const {host, username, password} = server;
if (!host) {
continue;
}
xo.server.add({
host,
username,
password,
autoConnect: false,
}).then(function(id) {
server.id = id;
xo.server.connect(id).catch(error => {
notify.error({
title: 'Server connection error',
message: error.message
});
});
});
delete server.password;
updateServers.push(server);
}
this.servers = updateServers;
this.newServers.length = 0;
this.addServer();
};
})
.name
;

View File

@@ -1,80 +0,0 @@
.grid
.panel.panel-default
p.page-title
i.fa.fa-cloud(style="color: #e25440;")
| Servers
.grid
.panel.panel-default
//- .panel-heading.panel-title
//- i.fa.fa-cloud(style="color: #e25440;")
//- | Connections
form(ng-submit="ctrl.saveServers()", autocomplete="off").panel-body
table.table.table-hover
tr
th.col-md-5 Host
th.col-md-2 User
th.col-md-3 Password
th.col-md-1.text.center Actions
th.col-md-1.text-center
i.fa.fa-trash-o.fa-lg(tooltip="Forget server")
tr(ng-repeat="server in ctrl.servers | orderBy:natural('host') track by server.id")
td
.input-group
span.input-group-addon(ng-if="server.status === 'connected'")
i.fa.fa-check-circle.fa-lg.text-success(tooltip="Connected")
span.input-group-addon(ng-if="server.status === 'disconnected'")
i.fa.fa-times-circle.fa-lg.text-danger(tooltip="Disconnected")
span.input-group-addon(ng-if="server.status === 'connecting'")
i.fa.fa-cog.fa-lg.fa-spin(tooltip="Connecting...")
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.text-center
button.btn.btn-default(
ng-if="server.status === 'disconnected'",
type="button",
ng-click="ctrl.connectServer(server.id)",
tooltip="Reconnect this server"
)
i.fa.fa-link
button.btn.btn-danger(
ng-if="server.status === 'connected'",
type="button",
ng-click="ctrl.disconnectServer(server.id)"
tooltip="Disconnect this server"
)
i.fa.fa-unlink
td.text-center
input(type="checkbox", ng-model="ctrl.selectedServers[server.id]")
tr(ng-repeat="server in ctrl.newServers")
td
input.form-control(
type = "text"
ng-model = "server.host"
placeholder = "address[:port]"
)
td
input.form-control(
type = "text"
ng-model = "server.username"
ng-required = "server.host"
placeholder = "user"
)
td
input.form-control(
type="password"
ng-model="server.password"
ng-required = "server.host"
placeholder="password"
)
td &#160;
td &#160;
p.text-center
button.btn.btn-primary(type="submit")
i.fa.fa-save
| Save
| &nbsp;
button.btn.btn-success(type="button", ng-click="ctrl.addServer()")
i.fa.fa-plus

View File

@@ -1,109 +0,0 @@
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import uiSelect from 'angular-ui-select';
import filter from 'lodash.filter';
import xoApi from 'xo-api';
import xoServices from 'xo-services';
import view from './view';
export default angular.module('settings.users', [
uiRouter,
uiSelect,
xoApi,
xoServices,
])
.config(function ($stateProvider) {
$stateProvider.state('settings.users', {
controller: 'SettingsUsers as ctrl',
url: '/users',
resolve: {
users(xo) {
return xo.user.getAll();
},
},
template: view,
});
})
.controller('SettingsUsers', function ($scope, $interval, users, xoApi, xo) {
this.users = users;
this.permissions = [
{
label: 'User',
value: 'none'
},
{
label: 'Admin',
value: 'admin'
}
];
const selected = this.selectedUsers = {};
const newUsers = this.newUsers = [];
const refreshUsers = () => {
xo.user.getAll().then(users => {
this.users = users;
});
};
const interval = $interval(refreshUsers, 5e3)
$scope.$on('$destroy', () => {
$interval.cancel(interval)
})
this.addUser = () => {
newUsers.push({
// Fake (unique) id needed by Angular.JS
id: Math.random(),
permission: 'none'
});
};
this.addUser();
this.saveUsers = () => {
const newUsers = this.newUsers;
const users = this.users;
const updateUsers = [];
for (let i = 0, len = users.length; i < len; i++) {
const user = users[i];
const {id} = user;
if (selected[id]) {
delete selected[id];
xo.user.delete(id);
}
else {
if (!user.password) {
delete user.password;
}
xo.user.set(user);
delete user.password;
updateUsers.push(user);
}
}
for (let i = 0, len = newUsers.length; i < len; i++) {
const user = newUsers[i];
const {email, permission, password} = user;
if (!email) {
continue;
}
xo.user.create({
email,
permission,
password,
}).then(function(id) {
user.id = id;
});
delete user.password;
updateUsers.push(user);
}
this.users = updateUsers;
this.newUsers.length = 0;
this.addUser();
};
})
.name
;

View File

@@ -1,55 +0,0 @@
.grid
.panel.panel-default
p.page-title
i.fa.fa-user(style="color: #e25440;")
| Users
.grid
.panel.panel-default
//- .panel-heading.panel-title
//- i.fa.fa-users(style="color: #e25440;")
//- | Users
form(ng-submit="ctrl.saveUsers()", autocomplete="off").panel-body
table.table.table-hover
tr
th.col-md-4 Email
th.col-md-4 Permissions
th.col-md-3 Password
th.col-md-1.text-center
i.fa.fa-trash-o.fa-lg(tooltip="Remove user")
tr(ng-repeat="user in ctrl.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 ctrl.permissions", ng-model="user.permission")
td
input.form-control(type="password", ng-model="user.password", placeholder="Fill to change the password")
td.text-center
input(type="checkbox", ng-model="ctrl.selectedUsers[user.id]")
tr(ng-repeat="user in ctrl.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 ctrl.permissions"
ng-model = "user.permission"
ng-required = "user.email"
)
td
input.form-control(
type = "password"
ng-model = "user.password"
ng-required = "user.email"
placeholder = "password"
)
td &#160;
p.text-center
button.btn.btn-primary(type="submit")
i.fa.fa-save
| Save
| &nbsp;
button.btn.btn-success(type="button", ng-click="ctrl.addUser()")
i.fa.fa-plus

View File

@@ -1,20 +0,0 @@
.grid
//- Side menu
.settings-menu
ul.nav
li
a(ui-sref = '.servers', ui-sref-active = 'active')
i.fa.fa-fw.fa-cloud.fa-menu
| Servers
a(ui-sref = '.users')
i.fa.fa-fw.fa-user.fa-menu
| Users
a.disabled(ui-sref = '.groups')
i.fa.fa-fw.fa-users.fa-menu
| Groups
a(ui-sref = '.acls')
i.fa.fa-fw.fa-key.fa-menu
| ACLs
//- Content
div.settings-content(ui-view = '')

View File

@@ -1,189 +0,0 @@
import angular from 'angular';
import isEmpty from 'isempty';
import uiRouter from 'angular-ui-router';
import Bluebird from 'bluebird';
import view from './view';
//====================================================================
export default angular.module('xoWebApp.sr', [
uiRouter,
])
.config(function ($stateProvider) {
$stateProvider.state('SRs_view', {
url: '/srs/:id',
controller: 'SrCtrl',
template: view,
});
})
.controller('SrCtrl', function ($scope, $stateParams, $state, $q, notify, xoApi, xo, modal, $window, bytesToSizeFilter) {
$window.bytesToSize = bytesToSizeFilter; // FIXME dirty workaround to custom a Chart.js tooltip template
let {get} = xoApi;
$scope.$watch(() => xoApi.get($stateParams.id), function (SR) {
$scope.SR = SR;
});
$scope.saveSR = function ($data) {
let {SR} = $scope;
let {name_label, name_description} = $data;
$data = {
id: SR.UUID,
};
if (name_label !== SR.name_label) {
$data.name_label = name_label;
}
if (name_description !== SR.name_description) {
$data.name_description = name_description;
}
return xoApi.call('sr.set', $data);
};
$scope.deleteVDI = function (UUID) {
console.log('Delete VDI', UUID);
return modal.confirm({
title: 'VDI deletion',
message: 'Are you sure you want to delete this VDI? This operation is irreversible.',
}).then(function () {
return xo.vdi.delete(UUID);
});
};
$scope.disconnectVBD = function (UUID) {
console.log('Disconnect VBD', UUID);
return xoApi.call('vbd.disconnect', {id: UUID});
};
$scope.connectPBD = function (UUID) {
console.log('Connect PBD', UUID);
return xoApi.call('pbd.connect', {id: UUID});
};
$scope.disconnectPBD = function (UUID) {
console.log('Disconnect PBD', UUID);
return xoApi.call('pbd.disconnect', {id: UUID});
};
$scope.reconnectAllHosts = function () {
// TODO: return a Bluebird.all(promises).
for (let id of $scope.SR.$PBDs) {
let pbd = xoApi.get(id);
xoApi.call('pbd.connect', {id: pbd.ref});
}
};
$scope.disconnectAllHosts = function () {
return modal.confirm({
title: 'Disconnect hosts',
message: 'Are you sure you want to disconnect all hosts to this SR?',
}).then(function () {
for (let id of $scope.SR.$PBDs) {
let pbd = xoApi.get(id);
xoApi.call('pbd.disconnect', {id: pbd.ref});
console.log(pbd.ref)
}
});
};
$scope.rescanSr = function (UUID) {
console.log('Rescan SR', UUID);
return xoApi.call('sr.scan', {id: UUID});
};
$scope.removeSR = function (UUID) {
console.log('Remove SR', UUID);
return modal.confirm({
title: 'SR deletion',
message: 'Are you sure you want to delete this SR? This operation is irreversible.',
}).then(function () {
return Bluebird.map($scope.SR.$PBDs, pbdId => {
let pbd = xoApi.get(pbdId);
return xoApi.call('pbd.disconnect', { id: pbd.id });
});
}).then(function () {
return xoApi.call('sr.destroy', {id: UUID});
}).then(function () {
$state.go('index');
notify.info({
title: 'SR remove',
message: 'SR is removed',
});
});
};
$scope.forgetSR = function (UUID) {
console.log('Forget SR', UUID);
return modal.confirm({
title: 'SR forget',
message: 'Are you sure you want to forget this SR? No VDI on this SR will be removed.',
}).then(function () {
return Bluebird.map($scope.SR.$PBDs, pbdId => {
let pbd = xoApi.get(pbdId);
return xoApi.call('pbd.disconnect', { id: pbd.id });
});
}).then(function () {
return xoApi.call('sr.forget', {id: UUID});
}).then(function () {
$state.go('index');
notify.info({
title: 'SR forget',
message: 'SR is forgotten',
});
});
};
$scope.saveDisks = function (data) {
// Group data by disk.
let disks = {};
angular.forEach(data, function (value, key) {
let i = key.indexOf('/');
let id = key.slice(0, i);
let prop = key.slice(i + 1);
(disks[id] || (disks[id] = {}))[prop] = value;
});
let promises = [];
angular.forEach(disks, function (attributes, id) {
// Keep only changed attributes.
let disk = get(id);
angular.forEach(attributes, function (value, name) {
if (value === disk[name]) {
delete attributes[name];
}
});
if (!isEmpty(attributes)) {
// Inject id.
attributes.id = id;
// Ask the server to update the object.
promises.push(xoApi.call('vdi.set', attributes));
}
});
return $q.all(promises);
};
})
// A module exports its name.
.name
;

View File

@@ -1,203 +0,0 @@
.grid
.panel.panel-default
p.page-title
i.xo-icon-sr
| {{SR.name_label}}
.grid
.panel.panel-default
.panel-heading.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
.panel-body
form(editable-form="", name="srSettings", onbeforesave="saveSR($data)")
dl.dl-horizontal
dt Name
dd
span(editable-text="SR.name_label", e-name="name_label", e-form="srSettings")
| {{SR.name_label}}
dt Description
dd
span(editable-text="SR.name_description", e-name="name_description", e-form="srSettings")
| {{SR.name_description}}
dt Content type:
dd {{SR.SR_type}}
dt Tags
dd(ng-if="SR.tags.length")
span(ng-repeat="tag in SR.tags")
span.label.label-primary {{tag}}
dd(ng-if="!SR.tags.length")
em No tags.
dt Shared
div(ng-repeat="container in [SR.$container] | resolve")
dd(ng-if="'pool' === container.type")
| Yes (
a(ui-sref="pools_view({id: container.UUID})") {{container.name_label}}
| )
dd(ng-if="'host' === container.type") No
dt Size
dd {{SR.size | bytesToSize}}
dt UUID
dd {{SR.UUID}}
.btn-form(ng-show="srSettings.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="srSettings.$waiting", ng-click="srSettings.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="submit", ng-disabled="srSettings.$waiting")
i.fa.fa-save
| Save
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-stats(style="color: #e25440;")
| Stats
.panel-body
.grid
.grid-cell
p.stat-name Physical Alloc:
canvas(id="doughnut", class="chart chart-doughnut", data="[(SR.physical_usage), (SR.size - SR.physical_usage)]", labels="['Used', 'Free']", options='{tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= bytesToSize(value) %>"}')
.grid-cell
p.stat-name Virtual Alloc:
canvas(id="doughnut", class="chart chart-doughnut", data="[(SR.usage), (SR.size - SR.usage)]", labels="['Used', 'Free']", options='{tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= bytesToSize(value) %>"}')
.grid-cell
p.stat-name VDIs:
p.center.big-stat {{SR.VDIs.length}}
//- Action panel
.grid
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-flash(style="color: #e25440;")
| Actions
.panel-body.text-center
.grid
.grid-cell.btn-group
button.btn(tooltip="Rescan all the VDI", type="button", style="width: 90%", ng-click="rescanSr(SR.UUID)")
i.fa.fa-refresh.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Reconnect all hosts", type="button", style="width: 90%", ng-click="reconnectAllHosts()")
i.fa.fa-retweet.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Disconnect all hosts", type="button", style="width: 90%", xo-click="disconnectAllHosts()")
i.fa.fa-power-off.fa-2x.fa-fw
.grid-cell.btn-group
button.btn(tooltip="Forget SR", type="button", style="width: 90%", xo-click="forgetSR(SR.UUID)")
i.fa.fa-2x.fa-fw.fa-ban
.grid-cell.btn-group
button.btn(tooltip="Remove SR", type="button", style="width: 90%", xo-click="removeSR(SR.UUID)")
i.fa.fa-2x.fa-trash-o
//- TODO: Space panel
.grid
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-memory(style="color: #e25440;")
| VDI Map
.panel-body
.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] | %}}", 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
form(name = "disksForm" editable-form = '' onbeforesave = 'saveDisks($data)').panel.panel-default
.panel-heading.panel-title
i.fa.fa-hdd-o(style="color: #e25440;")
| Virtual disks
span.quick-edit(tooltip="Edit disks", ng-click="disksForm.$show()")
i.fa.fa-edit.fa-fw
span.quick-edit(tooltip="Rescan", ng-click="rescanSr(SR.UUID)")
i.fa.fa-refresh.fa-fw
.panel-body
table.table.table-hover
tr
th Name
th Description
th Size
th Virtual Machine:
tr(ng-repeat="VDI in SR.VDIs | resolve | orderBy:natural('name_label')")
td
span(
editable-text="VDI.name_label"
e-name = '{{VDI.UUID}}/name_label'
)
| {{VDI.name_label}} &nbsp;
span.label.label-info(ng-if="VDI.$snapshot_of") snapshot
td
span(
editable-text="VDI.name_description"
e-name = '{{VDI.UUID}}/name_description'
)
| {{VDI.name_description}}
td
//- FIXME: should be editable, but the server needs first
//- to accept a human readable string.
| {{VDI.size | bytesToSize}}
td {{((VDI.$VBD | resolve).VM | resolve).name_label}}
span.pull-right.btn-group.quick-buttons
a(ng-if="(VDI.$VBD | resolve).attached", xo-click="disconnectVBD(VBD.UUID)")
i.fa.fa-unlink.fa-lg(tooltip="Disconnect this disk")
a(ng-if="!(VDI.$VBD | resolve).attached", xo-click="deleteVDI(VDI.UUID)")
i.fa.fa-trash-o.fa-lg(tooltip="Destroy this disk")
//- TODO: Ability to create new VDIs.
.btn-form(ng-show="disksForm.$visible")
p.center
button.btn.btn-default(
type="reset"
ng-disabled="disksForm.$waiting"
ng-click="disksForm.$cancel()"
)
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(
type="submit"
ng-disabled="disksForm.$waiting"
)
i.fa.fa-save
| Save
//- /VDIs.
//- Hosts.
.grid
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-link(style="color: #e25440;")
| Connected hosts
span.quick-edit(tooltip="Reconnect all hosts", ng-click="reconnectAllHosts()")
i.fa.fa-plus-square.fa-fw
.panel-body
table.table.table-hover
th Name
th Status
tr(ng-repeat="PBD in SR.$PBDs | resolve", xo-sref="hosts_view({id: (PBD.host | resolve).UUID})")
td {{(PBD.host | resolve).name_label}}
td(ng-if="PBD.attached")
span.label.label-success Connected
span.pull-right.btn-group.quick-buttons
a(xo-click="disconnectPBD(PBD.UUID)")
i.fa.fa-unlink.fa-lg(tooltip="Disconnect to this host")
td(ng-if="!PBD.attached")
span.label.label-default Disconnected
span.pull-right.btn-group.quick-buttons
a(xo-click="connectPBD(PBD.UUID)")
i.fa.fa-link.fa-lg(tooltip="Reconnect to this host")
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-comments(style="color: #e25440;")
| Logs
.panel-body
p.center(ng-if="!SR.messages.length") No recent logs
table.table.table-hover(ng-if="SR.messages.length")
th.col-md-1 Date
th.col-md-1 Name
tr(ng-repeat="message in SR.messages | resolve | orderBy:'-time' track by message.UUID")
td {{message.time*1e3 | date:"medium"}}
td
| {{message.name}}
a.quick-remove(tooltip="Remove log")
i.fa.fa-trash-o.fa-fw
//- /Hosts.

View File

@@ -1,255 +0,0 @@
angular = require 'angular'
throttle = require 'lodash.throttle'
#=====================================================================
module.exports = angular.module 'xoWebApp.tree', [
require 'angular-file-upload'
require 'angular-ui-router'
require 'xo-api'
require 'xo-services'
require '../delete-vms'
]
.config ($stateProvider) ->
$stateProvider.state 'tree',
url: '/tree'
controller: 'TreeCtrl'
template: require './view'
.controller 'TreeCtrl', (
$scope
$upload
dateFilter
deleteVmsModal
modal
notify
xo
xoApi
) ->
Object.defineProperties($scope, {
xo: { get: -> xoApi.byTypes.xo?[0] },
pools: { get: -> xoApi.byTypes.pool },
hosts: { get: -> xoApi.byTypes.host },
VMs: { get: -> xoApi.byTypes.VM },
})
$scope.pool_disconnect = xo.pool.disconnect
$scope.new_sr = xo.pool.new_sr
$scope.pool_addHost = (id) ->
xo.host.attach id
$scope.enableHost = (id) ->
xo.host.enable id
notify.info {
title: 'Host action'
message: 'Host is enabled'
}
$scope.disableHost = (id) ->
modal.confirm({
title: 'Disable host'
message: 'Are you sure you want to disable this host? In disabled state, no new VMs can be started and currently active VMs on the host continue to execute.'
}).then ->
xo.host.disable id
.then ->
notify.info {
title: 'Host action'
message: 'Host is disabled'
}
$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.startHost = (id) ->
xo.host.start id
$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.suspendVM = (id) -> xo.vm.suspend id, true
$scope.resumeVM = (id) -> xo.vm.resume id, true
$scope.migrateVM = (id, hostId) ->
(xo.vm.migrate id, hostId).catch (error) ->
modal.confirm
title: 'VM migrate'
message: 'This VM can\'t be migrated with Xen Motion to this host because they don\'t share any storage. Do you want to try a Xen Storage Motion?'
.then ->
notify.info {
title: 'VM migration'
message: 'The migration process started'
}
xo.vm.migratePool {
id
target_host_id: hostId
}
$scope.snapshotVM = (id) ->
vm = xoApi.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
false
# extract a value in a object
$scope.values = (object) ->
value for _, value of object
$scope.deleteVMs = ->
{selected_VMs} = $scope
deleteVmsModal (id for id, selected of selected_VMs when selected)
$scope.osType = (osName) ->
switch osName
when 'debian','ubuntu','centos','redhat','oracle','gentoo','suse','fedora','sles'
'linux'
when 'windows'
'windows'
else
'other'
# VMs checkboxes.
do ->
# This map marks which VMs are selected.
selected_VMs = $scope.selected_VMs = Object.create null
# Number of selected VMs.
$scope.n_selected_VMs = 0
# This is the master checkbox.
# Three states: true/false/null
$scope.master_selection = false
# Wheter all VMs are selected.
$scope.all = false
# Whether no VMs are selected.
$scope.none = true
# Updates `all`, `none` and `master_selection` when necessary.
$scope.$watch 'n_selected_VMs', (n) ->
$scope.all = (xoApi.byTypes.VM?.length is n)
$scope.none = (n is 0)
# When the master checkbox is clicked from indeterminate
# state, it should go to unchecked like Gmail.
$scope.master_selection = (n isnt 0)
make_matcher = (sieve) ->
(item) ->
for key, val of sieve
return false unless item[key] is val
true
$scope.selectVMs = (sieve) ->
VMs = xoApi.byTypes.VM
if (sieve is true) or (sieve is false)
$scope.n_selected_VMs = if sieve then VMs.length else 0
selected_VMs[VM.UUID] = sieve for VM in VMs
return
n = 0
matcher = make_matcher sieve
++n for VM in VMs when (selected_VMs[VM.UUID] = matcher VM)
$scope.n_selected_VMs = n
$scope.updateVMSelection = (UUID) ->
if selected_VMs[UUID]
++$scope.n_selected_VMs
else
--$scope.n_selected_VMs
$scope.bulkAction = (action, args...) ->
fn = $scope[action]
unless angular.isFunction fn
throw new Error "invalid action #{action}"
for UUID, selected of selected_VMs
fn UUID, args... if selected
# Unselects all VMs.
$scope.selectVMs false
$scope.importVm = ($files, id) ->
file = $files[0]
notify.info {
title: 'VM import started'
message: "Starting the VM import"
}
xo.vm.import id
.then ({ $sendTo: url }) ->
return $upload.http {
method: 'POST'
url
data: file
}
.then (result) ->
throw result.status if result.status isnt 200
notify.info
title: 'VM import'
message: 'Success'
$scope.patchPool = ($files, id) ->
file = $files[0]
xo.pool.patch id
.then ({ $sendTo: url }) ->
return $upload.http {
method: 'POST'
url
data: file
}
.progress throttle(
(event) ->
percentage = (100 * event.loaded / event.total)|0
notify.info
title: 'Upload patch'
message: "#{percentage}%"
6e3
)
.then (result) ->
throw result.status if result.status isnt 200
notify.info
title: 'Upload patch'
message: 'Success'
# A module exports its name.
.name

View File

@@ -1,400 +0,0 @@
//- @todo Remove code duplication for the VMs listing by using a macro.
.sub-bar
.grid
.grid-cell.overview
//- Stats
i(tooltip="{{xo.pools.length}} pools")
i.small {{xo.pools.length}}x
| &nbsp;
i.xo-icon-pool
| &nbsp;
| &nbsp;
i(tooltip="{{hosts.length}} hosts")
i.small {{hosts.length}}x
| &nbsp;
i.xo-icon-host
| &nbsp;
| &nbsp;
i(tooltip="{{xo.$running_VMs.length}} of {{VMs.length}} VMs running")
i.small {{xo.$running_VMs.length}}x
| &nbsp;
i.xo-icon-vm
| &nbsp;
| &nbsp;
i(tooltip="{{xo.$vCPUs}} vCPUs used of {{xo.$CPUs}} CPUs")
i.small {{xo.$vCPUs}}x
| &nbsp;
i.xo-icon-cpu
| &nbsp;
| &nbsp;
i(tooltip="{{xo.$memory.usage | bytesToSize}} RAM allocated of {{xo.$memory.size | bytesToSize}}")
i.small {{xo.$memory.usage | bytesToSize}}
| &nbsp;
i.xo-icon-memory
.grid-cell
.btn-group.before-action-bar.dropdown(dropdown)
a.btn.navbar-btn.btn-default.dropdown-toggle.inversed(dropdown-toggle)
input.inverse(type="checkbox", ng-model="master_selection", ng-change="selectVMs(master_selection)", ui-indeterminate="!(all || none)", stop-event="click")
| &nbsp;
i.fa.fa-caret-down
ul.dropdown-menu.inverse(role="menu")
li(ng-repeat="power_state in ['Halted', 'Running']")
a(ng-click="selectVMs({power_state: power_state})")
i.fa-fw(class="xo-icon-{{power_state | lowercase}}")
| {{power_state}}
li.divider
li(ng-repeat="host in hosts | orderBy:natural('name_label') track by host.UUID", ng-if="host.VMs.length")
a(ng-click="selectVMs({$container: host.ref})")
i.xo-icon-host.fa-fw
| On {{host.name_label}}
.action-bar(ng-if="!none")
| &nbsp;
.btn-group
button.btn.navbar-btn.btn-default.inversed(tooltip="Stop VM", type="button", ng-click="bulkAction('stopVM')")
i.fa.fa-stop
button.btn.navbar-btn.btn-default.inversed(tooltip="Start VM", type="button", ng-click="bulkAction('startVM')")
i.fa.fa-play
button.btn.navbar-btn.btn-default.inversed(tooltip="Reboot VM", type="button", ng-click="bulkAction('rebootVM')")
i.fa.fa-refresh
| &nbsp;
.btn-group.dropdown(dropdown)
button.btn.navbar-btn.btn-default.dropdown-toggle.inversed(
dropdown-toggle
tooltip="Migrate VM"
type="button"
)
i.fa.fa-share
| &nbsp;
i.fa.fa-caret-down
ul.dropdown-menu.inverse(role="menu")
li(ng-repeat="host in hosts | orderBy:natural('name_label') track by host.UUID")
a(ng-click="bulkAction('migrateVM',host.UUID)")
i.xo-icon-host.fa-fw
| To {{host.name_label}}
| &nbsp;
.btn-group.dropdown(dropdown)
button.btn.navbar-btn.btn-default.dropdown-toggle.inversed(
dropdown-toggle
type="button"
)
| More
| &nbsp;
i.fa.fa-caret-down
ul.dropdown-menu.inverse(role="menu")
li
a(ng-click="bulkAction('suspendVM')")
i.fa.fa-pause.fa-fw
| Suspend
li
a(ng-click="bulkAction('resumeVM')")
i.fa.fa-play.fa-fw
| Resume
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
.panel.panel-default.text-center
h1 Welcome on Xen Orchestra!
h3 It seems you aren't connected to any Xen server:
br
a.btn.btn-success.big(ui-sref="settings.index")
i.fa.fa-plus-circle
| Add server
br
br
br
p You can add a new host anytime by clicking on the menu icon "
i.fa.fa-th
| " and choose "
i.fa.fa-cog
| Settings"
p Enjoy Xen Orchestra!
//- 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'), 'id'] 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
//- Header (name + dropdown menu).
.dropdown.dropdown-pool(dropdown)
a.pool-name(ui-sref="pools_view({id: pool.UUID})")
span(ng-if="pool.name_label")
| {{pool.name_label}}
span.text-muted(ng-if="!pool.name_label")
| {{(pool.master | resolve).name_label}}
a.dropdown-toggle(ng-if="pool.name_label", dropdown-toggle)
| &nbsp;
i.fa.fa-caret-down.big-caret
ul.dropdown-menu.left(role="menu")
//- TODO: remove until handled this properly
//- li
//- a(xo-sref="SRs_new({container: pool.UUID})")
//- i.xo-icon-sr.fa-fw
//- | Add SR
li
a(xo-sref="VMs_new({container: pool.UUID})")
i.xo-icon-vm.fa-fw
| Create VM
//- TODO: solve the "a" problem for ng-file-select
li(ng-file-select="patchPool($files, pool.UUID)")
a
i.fa.fa-file-code-o.fa-fw
| Patch
li.divider
li
a.disabled(xo-click="pool_disconnect(pool.UUID)")
i.fa.fa-unlink.fa-fw
| Disconnect
//- /Header.
//- Stats & SRs list.
div
//- Stats.
ul.list-unstyled.stats
li
i(tooltip="{{pool.hosts.length}} hosts connected")
i.small {{pool.hosts.length}}x
| &nbsp;
i.xo-icon-host
| &nbsp;
| &nbsp;
i(tooltip="{{pool.$running_VMs.length}} of {{pool.$VMs.length}} VMs running")
i.small {{pool.$running_VMs.length}}x
| &nbsp;
i.xo-icon-vm
li(ng-if="pool.master")
| Master:
| &nbsp;
a(ui-sref="hosts_view({id: (pool.master | resolve).UUID})") {{(pool.master | resolve).name_label}}
//- /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}", title="{{SR.name_label}}")
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.grid--gutters.host-cell
//- Header (name + dropdown menu).
.dropdown.dropdown-pool(dropdown)
a.host-name(ui-sref="hosts_view({id: host.UUID})")
| {{host.name_label}}
a.dropdown-toggle(dropdown-toggle)
| &nbsp;
i.fa.fa-caret-down
ul.dropdown-menu.left(role="menu")
li
a(xo-sref="SRs_new({container: host.UUID})")
i.xo-icon-sr.fa-fw
| Add SR
li
a(xo-sref="VMs_new({container: host.UUID})")
i.xo-icon-vm.fa-fw
| Create VM
//- TODO: solve the "a" problem for ng-file-select
li(ng-file-select="importVm($files, host.UUID)")
a
i.fa.fa-upload.fa-fw
| Import 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(ng-if="!host.enabled")
a(xo-click="enableHost(host.UUID)")
i.fa.fa-check-circle.fa-fw
| Enable
li(ng-if="host.enabled")
a(xo-click="disableHost(host.UUID)")
i.fa.fa-times-circle.fa-fw
| Disable
li
a(xo-click="rebootHost(host.UUID)")
i.fa.fa-refresh.fa-fw
| Reboot
li(ng-if="host.power_state === 'Halted'")
a(xo-click="startHost(host.UUID)")
i.fa.fa-power-off.fa-fw
| Start
li(ng-if="host.power_state === 'Running'")
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-cell.grid--gutters.vm-cell
//- If no VMs, fill the space with a message.
.vms-notice(ng-if="!host.VMs.length")
//- | Host halted.
p(ng-if="host.power_state === 'Halted'")
| Host halted.
div(ng-if="host.power_state === 'Running'")
p(ng-if="!host.enabled")
| Host disabled.
p(ng-if="host.enabled")
| No VMs on this host.
//- /Message if no VMs.
//- TODO: comment
.table-responsive(ng-if="host.VMs.length")
table.table.table-hover.table-condensed
//- Contains a VM.
tr(ng-repeat="VM in host.VMs | resolve | orderBy:natural('name_label') track by VM.UUID", xo-sref="VMs_view({id: VM.UUID})")
//- Handle used for drag & drop.
td.grab
//- Checkbox used for selection.
td.select-vm
input(type="checkbox", ng-model="selected_VMs[VM.UUID]", ng-change="updateVMSelection(VM.UUID)")
//- Power state
td.vm-power-state
i.xo-icon-working(ng-if="isVMWorking(VM)", tooltip="{{VM.power_state}} and {{values(VM.current_operations)[0]}}")
i(class="xo-icon-{{VM.power_state | lowercase}}",ng-if="!isVMWorking(VM)", tooltip="{{VM.power_state}}")
//- VM name.
td.vm-name.col-md-2
p.vm {{VM.name_label}}
//- Quick actions.
td.vm-quick-buttons.col-md-2
.quick-buttons
a(tooltip="Shutdown VM", xo-click="stopVM(VM.UUID)")
i.fa.fa-stop
a(tooltip="Start VM", xo-click="startVM(VM.UUID)")
i.fa.fa-play
a(tooltip="Reboot VM", xo-click="rebootVM(VM.UUID)")
i.fa.fa-refresh
a(tooltip="VM Console", xo-sref="consoles_view({id: VM.UUID})")
i.xo-icon-console
//- Description.
td.vm-description.col-md-4
i(class="xo-icon-{{osType(VM.os_version.distro)}}",ng-if="VM.os_version.distro", tooltip="{{VM.os_version.name}}")
| &nbsp;
i.fa.fa-fw(ng-if="!VM.os_version.distro")
| {{VM.name_description}}
//- Metrics.
//- Memory
td.vm-memory-stat.col-md-2
.cpu
| {{VM.memory.size | bytesToSize}}
i.fa.fa-fw(ng-if="VM.PV_drivers")
i.xo-icon-info.fa-fw(ng-if="!VM.PV_drivers", tooltip="Xen tools not installed")
//- /Metrics.
//- Address.
td.text-muted.text-right.col-md-2
| {{VM.addresses["0/ip"]}}
//- Contains a pseudo-host which contains all VMs not in any hosts.
.grid(ng-if="pool.VMs.length")
//- This is where the information about a host would be displayed.
.grid-cell.host-cell
//- Contains all the VMs of this pool.
.grid.grid-cell.vm-cell
//- TODO: comment
.table-responsive
table.table.table-hover.table-condensed
//- Contains a VM.
tr(ng-repeat="VM in pool.VMs | resolve | orderBy:natural('name_label') track by VM.UUID", xo-sref="VMs_view({id: VM.UUID})")
//- Handle used for drag & drop.
td.grab
//- Checkbox used for selection.
td.select-vm
input(type="checkbox", ng-model="selected_VMs[VM.UUID]", ng-change="updateVMSelection(VM.UUID)")
//- Power state
td.vm-power-state
i.xo-icon-working(ng-if="isVMWorking(VM)", tooltip="{{VM.power_state}} and {{values(VM.current_operations)[0]}}")
i(class="xo-icon-{{VM.power_state | lowercase}}",ng-if="!isVMWorking(VM)", tooltip="{{VM.power_state}}")
//- VM name.
td.vm-name.col-md-2
p.vm {{VM.name_label}}
//- Quick actions.
td.vm-quick-buttons.col-md-2
.quick-buttons
a(tooltip="Shutdown VM", xo-click="stopVM(VM.UUID)")
i.fa.fa-stop
a(ng-if="VM.power_state == 'Suspended'", tooltip="Resume VM", xo-click="resumeVM(VM.UUID)")
i.fa.fa-play
a(ng-if="VM.power_state != 'Suspended'", tooltip="Start VM", xo-click="startVM(VM.UUID)")
i.fa.fa-play
a(tooltip="Reboot VM", xo-click="rebootVM(VM.UUID)")
i.fa.fa-refresh
a(tooltip="VM Console")
i.xo-icon-console
//- Description.
td.vm-description.col-md-4
i(class="xo-icon-{{osType(VM.os_version.distro)}}",ng-if="VM.os_version.distro", tooltip="{{VM.os_version.name}}")
| &nbsp;
i.fa.fa-fw(ng-if="!VM.os_version.distro")
| {{VM.name_description}}
//- Metrics.
//- Memory
td.vm-memory-stat.col-md-2
.cpu
| {{VM.memory.size | bytesToSize}}
i.fa.fa-fw(ng-if="VM.PV_drivers")
i.xo-icon-info.fa-fw(ng-if="!VM.PV_drivers", tooltip="Xen tools not installed")
//- /Metrics.
//- Address.
td.text-muted.text-right.col-md-2
| {{VM.addresses["0/ip"]}}
//- /Pseudo host containing VMs not on any hosts.
//- /Hosts of this pool.
//- /Pool with its children.

View File

@@ -1,681 +0,0 @@
angular = require 'angular'
isEmpty = require 'isempty'
_difference = require 'lodash.difference'
_sortBy = require 'lodash.sortby'
#=====================================================================
module.exports = angular.module 'xoWebApp.vm', [
require 'angular-ui-router'
]
.config ($stateProvider) ->
$stateProvider.state 'VMs_view',
url: '/vms/:id'
controller: 'VmCtrl'
template: require './view'
.controller 'VmCtrl', (
$scope, $state, $stateParams, $location, $q
xoApi, xo
sizeToBytesFilter, bytesToSizeFilter
modal
$window
$timeout
dateFilter
notify
) ->
$window.bytesToSize = bytesToSizeFilter # FIXME dirty workaround to custom a Chart.js tooltip template
{get} = xoApi
merge = do ->
push = Array::push.apply.bind Array::push
(args...) ->
result = []
for arg in args
push result, arg if arg?
result
# Provides a fibonacci behaviour for stats refresh on failure
$scope.refreshStatControl = refreshStatControl = {
baseStatInterval: 5000
timeout: null
running: false
start: () ->
return if this.running
this.running = true
this._reset()
$scope.$on('$destroy', () =>
this.stop()
)
$scope.refreshStats($scope.VM.UUID)
return this._trig(Date.now())
_trig: (t1) ->
if this.running
t2 = Date.now()
timeLeft = Math.max(this.baseStatInterval * this._factor() - Math.max(t2 - t1 - (this.baseStatInterval * this._factor(true)), 0), 0)
return this.timeout = $timeout(
() => $scope.refreshStats($scope.VM.UUID),
timeLeft
)
.then () =>
this._reset()
return this._trig(t2)
.catch (err) =>
if !this.running || $scope.VM.power_state isnt 'Running'
this.stop()
else
this._next()
this._trig(t2)
if this.running
throw err
_reset: () ->
this.terms = [1,1]
_next: () ->
this.terms = [this.terms[1], this.terms[0] + this.terms[1]]
_factor: (p) ->
return this.terms[if p then 0 else 1]
stop: () ->
if this.timeout
$timeout.cancel(this.timeout)
this.running = false
return
}
$scope.$watch(
-> get $stateParams.id, 'VM'
(VM) ->
$scope.VM = VM
{byTypes} = xoApi
$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
mountedIso = ''
VDIs = []
for VBD in VM.$VBDs
oVbd = get VBD
oVdi = get oVbd?.VDI
VDIs.push oVdi if oVdi? && not oVbd.is_cd_drive
if oVbd.is_cd_drive && oVdi? # "Load" the cd drive
mountedIso = oVdi.UUID
$scope.VDIs = _sortBy(VDIs, (value) -> (get resolveVBD(value))?.position);
container = get VM.$container
if container.type is 'host'
host = container
pool = (get container.poolRef) ? {}
else
host = {}
pool = container
$scope.networks = get pool.networks
default_SR = get pool.default_SR
default_SR = if default_SR
default_SR.UUID
else
''
SRs = $scope.SRs = get (merge pool.SRs, host.SRs)
# compute writable accessible SR from this VM
$scope.writable_SRs = (SR for SR in SRs when SR.content_type isnt 'iso')
prepareDiskData mountedIso
if VM.power_state is 'Running'
refreshStatControl.start()
else
refreshStatControl.stop()
)
descriptor = (obj) ->
return obj.name_label + (if obj.name_description.length then ' - ' + obj.name_description else '')
prepareDiskData = (mounted) ->
# For populating adding position choice
unfreePositions = [];
maxPos = 0;
# build VDI list of this VM
for VBD in $scope.VM.$VBDs
oVbd = get VBD
oVdi = get oVbd?.VDI
if oVdi?
unfreePositions.push parseInt oVbd.position
maxPos = if (oVbd.position > maxPos) then parseInt oVbd.position else maxPos
# $scope.vdiFreePos = _difference([0..++maxPos], unfreePositions)
$scope.maxPos = maxPos
$scope.VDIOpts = []
ISOOpts = []
for SR in $scope.SRs
if 'iso' isnt SR.SR_type
for rVdi in SR.VDIs
oVdi = get rVdi
$scope.VDIOpts.push({
sr: descriptor(SR),
label: descriptor(oVdi),
vdi: oVdi
})
else
for rIso in SR.VDIs
oIso = get rIso
ISOOpts.push({
sr: SR.name_label,
label: descriptor(oIso),
iso: oIso
})
$scope.isoDeviceData = {
opts: ISOOpts
mounted
}
$scope.refreshStats = (id) ->
return xo.vm.refreshStats id
.then (result) ->
result.cpuSeries = []
result.cpus.forEach (v,k) ->
result.cpuSeries.push 'CPU ' + k
return
result.vifSeries = []
result.vifs.forEach (v,k) ->
result.vifSeries.push '#' + Math.floor(k/2) + ' ' + if k % 2 then 'out' else 'in'
return
result.xvdSeries = []
result.xvds.forEach (v,k) ->
# 97 is ascii code of 'a'
result.xvdSeries.push 'xvd' + String.fromCharCode(Math.floor(k/2) + 97, ) + ' ' + if k % 2 then 'write' else 'read'
return
result.date.forEach (v,k) ->
result.date[k] = new Date(v*1000).toLocaleTimeString()
$scope.stats = result
$scope.startVM = (id) ->
xo.vm.start id
notify.info {
title: 'VM starting...'
message: 'Start VM'
}
$scope.stopVM = (id) ->
xo.vm.stop id
notify.info {
title: 'VM shutdown...'
message: 'Gracefully shutdown the VM'
}
$scope.force_stopVM = (id) ->
xo.vm.stop id, true
notify.info {
title: 'VM force shutdown...'
message: 'Force shutdown the VM'
}
$scope.rebootVM = (id) ->
xo.vm.restart id
notify.info {
title: 'VM reboot...'
message: 'Gracefully reboot the VM'
}
$scope.force_rebootVM = (id) ->
xo.vm.restart id, true
notify.info {
title: 'VM reboot...'
message: 'Force reboot the VM'
}
$scope.suspendVM = (id) ->
xo.vm.suspend id, true
notify.info {
title: 'VM suspend...'
message: 'Suspend the VM'
}
$scope.resumeVM = (id) ->
xo.vm.resume id, true
notify.info {
title: 'VM resume...'
message: 'Resume the VM'
}
$scope.migrateVM = (id, hostId) ->
(xo.vm.migrate id, hostId).catch (error) ->
modal.confirm
title: 'VM migrate'
message: 'This VM can\'t be migrated with Xen Motion to this host because they don\'t share any storage. Do you want to try a Xen Storage Motion?'
.then ->
notify.info {
title: 'VM migration'
message: 'The migration process started'
}
xo.vm.migratePool {
id
target_host_id: hostId
}
$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 'index'
notify.info {
title: 'VM deletion'
message: 'VM is removed'
}
$scope.saveSnapshot = (id, $data) ->
snapshot = get (id)
result = {
id: snapshot.UUID
name_label: $data
}
if $data isnt snapshot.name_label
result.name_label = $data
xoApi.call 'vm.set', result
$scope.saveVM = ($data) ->
{VM} = $scope
{CPUs, memory, name_label, name_description, high_availability, auto_poweron} = $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
if high_availability isnt VM.high_availability
$data.high_availability = high_availability
if auto_poweron isnt VM.auto_poweron
$data.auto_poweron = auto_poweron
xoApi.call 'vm.set', $data
#-----------------------------------------------------------------
# Disks
#-----------------------------------------------------------------
# TODO: implement in XO-Server.
$scope.moveDisk = (index, direction) ->
{VDIs} = $scope
newIndex = index + direction
[VDIs[index], VDIs[newIndex]] = [VDIs[newIndex], VDIs[index]]
return
migrateDisk = (id, sr_id) ->
return modal.confirm({
title: 'Disk migration'
message: 'Are you sure you want to migrate (move) this disk to another SR?'
}).then ->
notify.info {
title: 'Disk migration'
message: 'Disk migration started'
}
xo.vdi.migrate id, sr_id
return
$scope.saveDisks = (data) ->
# Group data by disk.
disks = {}
angular.forEach data, (value, key) ->
i = key.indexOf '/'
(disks[key.slice 0, i] ?= {})[key.slice i + 1] = value
return
promises = []
# Handle SR change.
angular.forEach disks, (attributes, id) ->
disk = get id
if attributes.$SR isnt disk.$SR
promises.push (migrateDisk id, attributes.$SR)
return
angular.forEach disks, (attributes, id) ->
# Keep only changed attributes.
disk = get id
angular.forEach attributes, (value, name) ->
delete attributes[name] if value is disk[name]
return
unless isEmpty attributes
# Inject id.
attributes.id = id
# Ask the server to update the object.
promises.push xoApi.call 'vdi.set', attributes
return
# Handle Position changes
mountedPos = (get resolveVBD(get $scope.isoDeviceData.mounted))?.position
{VDIs} = $scope
VDIs.forEach (vdi, index) ->
oVbd = get resolveVBD(vdi)
offset = if (mountedPos? && index >= mountedPos) then 1 else 0
if oVbd? && index isnt oVbd.position
promises.push xoApi.call 'vbd.set', {id: oVbd.id, position: String(index + offset)}
return $q.all promises
.catch (err) ->
console.log(err);
notify.error {
title: 'saveDisks'
message: err
}
$scope.deleteDisk = (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}
return
return
#-----------------------------------------------------------------
# returns the ref of the VBD that links the VDI to the VM
$scope.resolveVBD = resolveVBD = (vdi) ->
if not vdi?
return
for vbd in vdi.$VBDs
rVbd = vbd if (get vbd).VM is $scope.VM.ref
return rVbd || null
$scope.disconnectVBD = (vdi) ->
id = resolveVBD(vdi)
if id?
console.log "Disconnect VBD #{id}"
xo.vbd.disconnect id
$scope.connectVBD = (vdi) ->
id = resolveVBD(vdi)
if id?
console.log "Connect VBD #{id}"
xo.vbd.connect id
$scope.deleteVBD = (vdi) ->
id = resolveVBD(vdi)
if id?
console.log "Delete VBD #{id}"
modal.confirm({
title: 'VBD deletion'
message: 'Are you sure you want to delete this VM disk attachment (the disk will NOT be destroyed)?'
}).then ->
xo.vbd.delete id
$scope.connectVIF = (id) ->
console.log "Connect VIF #{id}"
xo.vif.connect id
$scope.disconnectVIF = (id) ->
console.log "Disconnect VIF #{id}"
xo.vif.disconnect id
$scope.deleteVIF = (id) ->
console.log "Delete VIF #{id}"
modal.confirm({
title: 'VIF deletion'
message: 'Are you sure you want to delete this Virtual Interface (VIF)?'
}).then ->
xo.vif.delete id
$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}"
notify.info {
title: 'Clone creation'
message: 'Clone creation started'
}
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}"
notify.info {
title: 'Snapshot creation'
message: 'Snapshot creation started'
}
xo.vm.createSnapshot id, snapshot_name
$scope.exportVM = (id) ->
console.log "Export VM #{id}"
notify.info {
title: 'VM export'
message: 'VM export started'
}
xo.vm.export id
.then ({$getFrom: url}) ->
window.open url
$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.connectPci = (id, pciId) ->
console.log "Connect PCI device "+pciId+" on VM "+id
xo.vm.connectPci id, pciId
$scope.disconnectPci = (id) ->
xo.vm.disconnectPci id
$scope.deleteAllLog = ->
modal.confirm({
title: 'Log deletion'
message: 'Are you sure you want to delete all the logs?'
}).then ->
for log in $scope.VM.messages
console.log "Remove log #{log}"
xo.log.delete log
$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 ->
notify.info {
title: 'Reverting to snapshot'
message: 'VM revert started'
}
xo.vm.revert id
$scope.osType = (osName) ->
switch osName
when 'debian','ubuntu','centos','redhat','oracle','gentoo','suse','fedora','sles'
'linux'
when 'windows'
'windows'
else
'other'
$scope.isVMWorking = (VM) ->
return false unless VM
return true for _ of VM.current_operations
false
# extract a value in a object
$scope.values = (object) ->
value for _, value of object
$scope.addVdi = (vdi, readonly, bootable) ->
$scope.addWaiting = true # disables form fields
position = $scope.maxPos + 1
params = {
bootable
mode : if (readonly || !isFreeForWriting(vdi)) then 'RO' else 'RW'
position: String(position)
vdi: vdi.UUID
vm: $scope.VM.UUID
}
console.log(params)
return xoApi.call 'vm.attachDisk', params
.then -> $scope.adding = false # Closes form block
.catch (err) ->
console.log(err);
notify.error {
title: 'vm.attachDisk'
message: err
}
.finally ->
$scope.addWaiting = false
$scope.isConnected = isConnected = (vdi) -> (get resolveVBD(vdi))?.attached
$scope.isFreeForWriting = isFreeForWriting = (vdi) ->
free = true
for vbd in vdi.$VBDs
oVbd = get vbd
free = free && (!oVbd?.attached || oVbd?.read_only)
return free
$scope.createVdi = (name, size, sr, bootable, readonly) ->
$scope.createVdiWaiting = true # disables form fields
position = $scope.maxPos + 1
params = {
name
size: String(size)
sr
}
# console.log(params)
return xoApi.call 'disk.create', params
.then (diskUuid) ->
params = {
bootable,
mode: if readonly then 'RO' else 'RW'
position: String(position)
vdi: diskUuid
vm: $scope.VM.UUID
}
# console.log(params)
return xoApi.call 'vm.attachDisk', params
.then -> $scope.creating = false # Closes form block
.catch (err) ->
console.log(err);
notify.error {
title: 'Attach Disk'
message: err
}
.catch (err) ->
console.log(err);
notify.error {
title: 'Create Disk'
message: err
}
.finally ->
$scope.createVdiWaiting = false
$scope.updateMTU = (network) ->
$scope.newInterfaceMTU = network.MTU
$scope.createInterface = (network, mtu, automac, mac) ->
$scope.createVifWaiting = true # disables form fields
position = 0
$scope.VM.VIFs.forEach (vf) ->
int = get vf
position = if int?.device > position then (get vf)?.device else position
position++
params = {
vm: $scope.VM.UUID
network: network.UUID
position: String(position) # TODO
mtu: String(mtu) || String(network.mtu)
}
if !automac
params.mac = mac
# console.log(params)
return xoApi.call 'vm.createInterface', params
.then (id) ->
$scope.creatingVif = false
# console.log(id)
xoApi.call 'vif.connect', {id}
.catch (err) ->
console.log(err);
notify.error {
title: 'Create Interface'
message: err
}
.finally ->
$scope.createVifWaiting = false
$scope.statView = {
cpuOnly: false,
ramOnly: false,
netOnly: false,
diskOnly: false
}
# A module exports its name.
.name

View File

@@ -1,683 +0,0 @@
.grid
.panel.panel-default
p.page-title
i.xo-icon-vm(ng-if="isVMWorking(VM)", class="xo-color-pending", tooltip="{{VM.power_state}} and {{values(VM.current_operations)[0]}}")
i.xo-icon-vm(class="xo-color-{{VM.power_state | lowercase}}",ng-if="!isVMWorking(VM)", tooltip="{{VM.power_state}}")
| {{VM.name_label}}
.grid
.panel.panel-default
.panel-heading.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
.panel-body
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")
span(ng-if = 'container.type === "host"')
a(xo-sref="hosts_view({id: container.UUID})")
| {{container.name_label}}
small
span(ng-if="(container.poolRef | resolve).name_label")
| (
a(ui-sref="pools_view({id: (container.poolRef | resolve).UUID})") {{(container.poolRef | resolve).name_label}}
| )
a(
ng-if = 'container.type === "pool"'
xo-sref="pools_view({id: container.UUID})"
)
| {{container.name_label}}
dt(ng-if="VM.addresses") Address
dd(ng-repeat="IP in VM.addresses") {{IP}}
dt(ng-if="!(VM.poolRef | resolve).HA_enabled") Auto Power
dd(ng-if="!(VM.poolRef | resolve).HA_enabled")
span(
editable-checkbox="VM.auto_poweron"
e-name="auto_poweron"
e-form="vmSettings"
)
| {{VM.auto_poweron}}
dt(ng-if="(VM.poolRef | resolve).HA_enabled") HA
dd(ng-if="(VM.poolRef | resolve).HA_enabled")
span(
editable-checkbox="VM.high_availability"
e-name="high_availability"
e-form="vmSettings"
)
| {{VM.high_availability}}
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}}
dt(ng-if="refreshStatControl.running && stats") Xen tools:
dd(ng-if="refreshStatControl.running && stats")
span(ng-if="VM.PV_drivers", style="color:green;") Installed
span(ng-if="!VM.PV_drivers") NOT installed
dt(ng-if="refreshStatControl.running && stats && VM.os_version.distro") OS:
dd(ng-if="refreshStatControl.running && stats && VM.os_version.distro")
| {{VM.os_version.name}} ({{VM.os_version.distro}})
dt(ng-if="refreshStatControl.running && stats && VM.os_version.uname") Kernel:
dd(ng-if="refreshStatControl.running && stats && VM.os_version.uname")
| {{VM.os_version.uname}}
.btn-form(ng-show="vmSettings.$visible")
p.center
button.btn.btn-default(
type="button"
ng-disabled="vmSettings.$waiting"
ng-click="vmSettings.$cancel()"
)
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(
type="submit"
ng-disabled="vmSettings.$waiting"
)
i.fa.fa-save
| Save
.panel.panel-default.panel-height.center
.panel-heading.panel-title
i.xo-icon-stats(style="color: #e25440;", xo-click="refreshStats(VM.UUID)")
| Stats
.panel-body-stats(ng-if="refreshStatControl.running && stats")
div(ng-if="statView.cpuOnly", ng-click="statView.cpuOnly = false")
p.stat-name
i.fa.fa-tachometer
| &nbsp; CPU usage
canvas.chart.chart-line.chart-stat-full(
id="bigCpu"
data="stats.cpus"
labels="stats.date"
series="stats.cpuSeries"
colours="['#0000ff', '#9999ff', '#000099', '#5555ff', '#000055']"
legend="true"
options='{responsive: true, maintainAspectRatio: false, tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= Math.round(10*value)/10 %>", multiTooltipTemplate:"<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= Math.round(10*value)/10 %>", pointDot: false, showScale: false, animation: false, datasetStrokeWidth: 0.8, scaleOverride: true, scaleSteps: 100, scaleStartValue: 0, scaleStepWidth: 1, pointHitDetectionRadius: 0}'
)
div(ng-if="statView.ramOnly", ng-click="statView.ramOnly = false")
p.stat-name
//- i.fa.fa-bar-chart
i.fa.fa-tasks
//- i.fa.fa-server
| &nbsp; RAM usage
canvas.chart.chart-line.chart-stat-full(
id="bigRam"
data="[stats.memoryUsed,stats.memory]"
labels="stats.date"
series="['Used RAM', 'Total RAM']"
colours="['#ff0000', '#ffbbbb']"
legend="true"
options=' {responsive: true, maintainAspectRatio: false, tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= bytesToSize(value) %>", multiTooltipTemplate:"<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= bytesToSize(value) %>", datasetStrokeWidth: 0.8, pointDot: false, showScale: false, scaleBeginAtZero: true, animation: false, pointHitDetectionRadius: 0}'
)
div(ng-if="statView.netOnly", ng-click="statView.netOnly = false")
p.stat-name
i.fa.fa-sitemap
| &nbsp; Network I/O
canvas.chart.chart-line.chart-stat-full(
id="bigNet"
data="stats.vifs"
labels="stats.date"
series="stats.vifSeries"
colours="['#dddd00', '#dddd77', '#777700', '#dddd55', '#555500', '#ffdd00']"
legend="true"
options=' {responsive: true, maintainAspectRatio: false, tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= bytesToSize(value) %>", multiTooltipTemplate:"<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= bytesToSize(value) %>", datasetStrokeWidth: 0.8, pointDot: false, showScale: false, scaleBeginAtZero: true, animation: false, pointHitDetectionRadius: 0}'
)
div(ng-if="statView.diskOnly", ng-click="statView.diskOnly = false")
p.stat-name
i.fa.fa-hdd-o
| &nbsp; Disk I/O
canvas.chart.chart-line.chart-stat-full(
id="bigDisk"
data="stats.xvds"
labels="stats.date"
series="stats.xvdSeries"
colours="['#00dd00', '#77dd77', '#007700', '#33dd33', '#003300']"
legend="true"
options=' {responsive: true, maintainAspectRatio: false, multiTooltipTemplate:"<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= bytesToSize(value) %>", datasetStrokeWidth: 0.8, pointDot: false, showScale: false, scaleBeginAtZero: true, animation: false, pointHitDetectionRadius: 0}'
)
div(ng-if="!statView.netOnly && !statView.diskOnly && !statView.cpuOnly && !statView.ramOnly")
.row
.col-md-6(ng-click="statView.cpuOnly=true")
p.stat-name
i.fa.fa-tachometer
| &nbsp; CPU usage
canvas.chart.chart-line.chart-stat-preview(
id="smallCpu"
data="stats.cpus"
labels="stats.date"
series="stats.cpuSeries"
colours="['#0000ff', '#9999ff', '#000099', '#5555ff', '#000055']"
options='{responsive: true, maintainAspectRatio: false, showTooltips: false, pointDot: false, showScale: false, animation: false, datasetStrokeWidth: 0.8, scaleOverride: true, scaleSteps: 100, scaleStartValue: 0, scaleStepWidth: 1}'
)
.col-md-6(ng-click="statView.ramOnly=true")
p.stat-name
//- i.fa.fa-bar-chart
i.fa.fa-tasks
//- i.fa.fa-server
| &nbsp; RAM usage
canvas.chart.chart-line.chart-stat-preview(
id="smallRam"
data="[stats.memoryUsed,stats.memory]"
labels="stats.date"
series="['Used RAM', 'Total RAM']"
colours="['#ff0000', '#ffbbbb']"
options=' {responsive: true, maintainAspectRatio: false, showTooltips: false, datasetStrokeWidth: 0.8, pointDot: false, showScale: false, scaleBeginAtZero: true, animation: false}'
)
.row
.col-md-6(ng-click="statView.netOnly=true")
p.stat-name
i.fa.fa-sitemap
| &nbsp; Network I/O
canvas.chart.chart-line.chart-stat-preview(
id="smallNet"
data="stats.vifs"
labels="stats.date"
series="stats.vifSeries"
colours="['#dddd00', '#dddd77', '#777700', '#dddd55', '#555500', '#ffdd00']"
options=' {responsive: true, maintainAspectRatio: false, showTooltips: false, datasetStrokeWidth: 0.8, pointDot: false, showScale: false, scaleBeginAtZero: true, animation: false}'
)
.col-md-6(ng-click="statView.diskOnly=true")
p.stat-name
i.fa.fa-hdd-o
| &nbsp; Disk I/O
canvas.chart.chart-line.chart-stat-preview(
id="smallDisk"
data="stats.xvds"
labels="stats.date"
series="stats.xvdSeries"
colours="['#00dd00', '#77dd77', '#007700', '#33dd33', '#003300']"
options=' {responsive: true, maintainAspectRatio: false, showTooltips: false, datasetStrokeWidth: 0.8, pointDot: false, showScale: false, scaleBeginAtZero: true, animation: false}'
)
.panel-body(ng-if="!refreshStatControl.running || !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
p.center(ng-if="refreshStatControl.running")
i.fa.fa-circle-o-notch.fa-spin.fa-2x
| &nbsp; Fetching stats...
.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
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-flash(style="color: #e25440;")
| Actions
.panel-body.text-center
.grid
.grid-cell.btn-group(ng-if="VM.power_state == ('Running' || 'Paused')")
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(ng-if="VM.power_state == ('Running')")
button.btn(tooltip="Suspend VM", type="button", style="width: 90%", xo-click="suspendVM(VM.UUID)")
i.fa.fa-pause.fa-2x.fa-fw
.grid-cell.btn-group(ng-if="VM.power_state == ('Suspended')")
button.btn(tooltip="Resume VM", type="button", style="width: 90%", xo-click="resumeVM(VM.UUID)")
i.fa.fa-play.fa-2x.fa-fw
.grid-cell.btn-group(ng-if="VM.power_state == ('Halted')")
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(ng-if="VM.power_state == ('Running' || 'Paused')")
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(
ng-if="VM.power_state == ('Running' || 'Paused')"
dropdown
)
button.btn.dropdown-toggle(
dropdown-toggle
tooltip="Migrate VM"
type="button"
style="width: 90%"
)
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(ng-if="VM.power_state == ('Running' || 'Paused')")
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(ng-if="VM.power_state == ('Running' || 'Paused')")
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(ng-if="VM.power_state == ('Halted')")
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(
ng-if="VM.power_state == ('Halted')"
dropdown
)
button.btn.dropdown-toggle(
dropdown-toggle
tooltip="Create a clone"
style="width: 90%"
type="button"
)
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
button.btn(tooltip="Export the VM", style="width: 90%", type="button", xo-click="exportVM(VM.UUID)")
i.fa.fa-download.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
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-sr(style="color: #e25440;")
| Disk
span.quick-edit(
ng-if="!disksForm.$visible"
tooltip="Edit disks"
ng-click="disksForm.$show()"
)
i.fa.fa-edit.fa-fw
span.quick-edit(
ng-if="disksForm.$visible"
tooltip="Cancel Edition"
ng-click="disksForm.$cancel()"
)
i.fa.fa-undo.fa-fw
.panel-body
form(name = "disksForm", editable-form = '', onbeforesave = 'saveDisks($data)')
table.table.table-hover
tr
th Name
th Description
th Size
th SR
th Status
th(ng-show="disksForm.$visible")
//- FIXME: ng-init seems to disrupt the implicit $watch.
tr(ng-repeat = 'VDI in VDIs track by VDI.UUID')
td
span(
editable-text="VDI.name_label"
e-name = '{{VDI.UUID}}/name_label'
)
| {{VDI.name_label}}
td
span(
editable-text="VDI.name_description"
e-name = '{{VDI.UUID}}/name_description'
)
| {{VDI.name_description}}
td
//- FIXME: should be editable, but the server needs first
//- to accept a human readable string.
| {{VDI.size | bytesToSize}}
td
span(
editable-select="(VDI.$SR | resolve).ref"
e-ng-options="SR.ref as (SR.name_label + ' (' + (SR.size - SR.usage | bytesToSize) + ' free)') for SR in writable_SRs"
e-name = '{{VDI.UUID}}/$SR'
)
//- Are SR editable? will trigger moving VDI to the new SR
a(xo-sref="SRs_view({id: (VDI.$SR | resolve).UUID})")
| {{(VDI.$SR | resolve).name_label}}
td(ng-if="isConnected(VDI)")
span.label.label-success Connected
span.pull-right.btn-group.quick-buttons
a(
tooltip="Disconnect this disk"
xo-click="disconnectVBD(VDI)"
)
i.fa.fa-unlink.fa-lg
td(ng-if="!isConnected(VDI)")
span.label.label-default Disconnected
span.pull-right.btn-group.quick-buttons
a(
tooltip="Plug this disk"
ng-if="VM.power_state == ('Running' || 'Paused')"
xo-click="connectVBD(VDI)"
)
i.fa.fa-plug.fa-lg
a(
tooltip="Forget this disk"
xo-click="deleteVBD(VDI)"
)
i.fa.fa-ban.fa-lg
a(
tooltip="Remove this disk"
xo-click="deleteDisk(VDI.UUID)"
)
i.fa.fa-trash-o.fa-lg
td(ng-show="disksForm.$visible")
.btn-group
button.btn.btn-default(
type="button"
ng-click="moveDisk($index, -1)"
ng-disabled="$first"
title="Move this disk up"
)
i.fa.fa-chevron-up
button.btn.btn-default(
type="button"
ng-click="moveDisk($index, 1)"
ng-disabled="$last"
title="Move this disk down"
)
i.fa.fa-chevron-down
//- TODO: Ability to create new VDIs.
.btn-form(ng-show="disksForm.$visible")
p.center
button.btn.btn-default(
type="reset"
ng-disabled="disksForm.$waiting"
ng-click="disksForm.$cancel()"
)
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(
type="submit"
ng-disabled="disksForm.$waiting"
)
i.fa.fa-save
| Save
.grid
.col-md-4
iso-device(ng-if = 'VM && isoDeviceData', vm = 'VM', isos = 'isoDeviceData')
.col-md-8.text-right
div
button.btn(type="button", ng-class = '{"btn-success": adding, "btn-primary": !adding}', ng-disabled="disksForm.$waiting", ng-click="adding = !adding;creatingVdi = false")
i.fa.fa-plus(ng-if = '!adding')
i.fa.fa-minus(ng-if = 'adding')
| Attach Disk
| &nbsp;
button.btn(type="button", ng-class = '{"btn-success": creatingVdi, "btn-primary": !creatingVdi}', ng-disabled="disksForm.$waiting", ng-click="creatingVdi = !creatingVdi;adding = false")
i.fa.fa-plus(ng-if = '!creatingVdi')
i.fa.fa-minus(ng-if = 'creatingVdi')
| New Disk
br
form.form-inline#addDiskForm(ng-if = 'adding', name = 'addForm', ng-submit = 'addVdi(vdiToAdd.vdi, vdiReadOnly, vdiBootable)')
fieldset(ng-attr-disabled = '{{ addWaiting ? true : undefined }}')
.form-group
select#vdiToAdd.form-control(ng-model = 'vdiToAdd', ng-options = 'vdi.label group by vdi.sr for vdi in VDIOpts', required)
option(value = '', disabled) -- Choose disk --
| &nbsp;
.form-group(ng-if = 'vdiToAdd')
//- .form-group
label(for = 'vdiPosition') Position&nbsp;
select#vdiPosition.form-control(ng-model = '$parent.vdiPos', ng-options = 'vPos for vPos in vdiFreePos', required)
option(value = '', disabled) --
| &nbsp;
.checkbox
label
input(type='checkbox', ng-model = '$parent.$parent.vdiBootable')
| Bootable&nbsp;
.checkbox
label
input(ng-if = '!isFreeForWriting(vdiToAdd.vdi)', type='checkbox', ng-model = '$parent.$parent.vdiReadOnly', ng-checked = 'true', ng-disabled = 'true')
input(ng-if = 'isFreeForWriting(vdiToAdd.vdi)', type='checkbox', ng-model = '$parent.$parent.vdiReadOnly')
| Read-only&nbsp;
.form-group
button.btn.btn-primary(type = 'submit', ng-disabled="disksForm.$waiting")
| Add
span(ng-if = 'addWaiting')
| &nbsp;
i.fa.fa-spin.fa-circle-o-notch
br
form.form-inline#createDiskForm(ng-if = 'creatingVdi', name = 'createForm', ng-submit = 'createVdi(newDiskName, newDiskSize, newDiskSR, newDiskBootable, newDiskReadonly)')
fieldset(ng-attr-disabled = '{{ createWaiting ? true : undefined }}')
.form-group
//- label(for = 'newDiskName') Name&nbsp;
input#newDiskName.form-control(type = 'text', ng-model = 'newDiskName', placeholder = 'Disk Name', required)
| &nbsp;
.form-group
//- label(for = 'newDiskSize') Size&nbsp;
input#newDiskSize.form-control(type = 'text', ng-model = 'newDiskSize', required, placeholder = 'Size e.g 128MB, 8GB, 2TB...')
| &nbsp;
.form-group
//- label(for = 'newDiskSR') SR&nbsp;
select.form-control(ng-model = 'newDiskSR', required, ng-options="SR.ref as (SR.name_label + ' (' + (SR.size - SR.usage | bytesToSize) + ' free)') for SR in writable_SRs")
option(value = '', disabled) Choose your SR
| &nbsp;
br
br
.checkbox
label
input(type='checkbox', ng-model = 'newDiskBootable')
| &nbsp;Bootable&nbsp;
.checkbox
label
input(type='checkbox', ng-model = 'newDiskReadonly')
| &nbsp;Read-only&nbsp;
//- .form-group
label(for = 'diskPosition') Position&nbsp;
select#diskPosition.form-control(ng-model = 'newDiskPosition', ng-options = 'vPos for vPos in vdiFreePos', required)
option(value = '', disabled) --
| &nbsp;
br
br
.form-group
button.btn.btn-primary(type = 'submit', ng-disabled="disksForm.$waiting")
i.fa.fa-plus-square
| &nbsp;Create
span(ng-if = 'createWaiting')
| &nbsp;
i.fa.fa-spin.fa-circle-o-notch
br
//- TODO: add interface in this panel
.grid
.panel.panel-default
.panel-heading.panel-title
i.xo-icon-network(style="color: #e25440;")
| Interface
.panel-body
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('device') track by VIF.UUID")
td VIF \#{{VIF.device}}
td
| {{VIF.MAC}}
td
| {{VIF.MTU}}
td
| {{(VIF.$network | resolve).name_label}}
td(ng-if="VIF.attached")
span.label.label-success Connected
span.pull-right.btn-group.quick-buttons
a(tooltip="Disconnect this interface", ng-if="VM.power_state == ('Running' || 'Paused')", xo-click="disconnectVIF(VIF.UUID)")
i.fa.fa-unlink.fa-lg
td(ng-if="!VIF.attached")
span.label.label-default Disconnected
span.pull-right.btn-group.quick-buttons
a(tooltip="Connect this interface", xo-click="connectVIF(VIF.UUID)")
i.fa.fa-link.fa-lg
a(tooltip="Remove this interface", xo-click="deleteVIF(VIF.UUID)")
i.fa.fa-trash-o.fa-lg
.text-right
button.btn(type="button", ng-class = '{"btn-success": creatingVif, "btn-primary": !creatingVif}', ng-click="creatingVif = !creatingVif")
i.fa.fa-plus(ng-if = '!creatingVif')
i.fa.fa-minus(ng-if = 'creatingVif')
| Create Interface
br
form.form-inline.text-right#createInterfaceForm(ng-if = 'creatingVif', name = 'createInterfaceForm', ng-submit = 'createInterface(newInterfaceNetwork, newInterfaceMTU, autoMac, newInterfaceMAC)')
fieldset(ng-attr-disabled = '{{ createVifWaiting ? true : undefined }}')
.form-group
label(for = 'newVifNetwork') Network&nbsp;
select.form-control(ng-model = 'newInterfaceNetwork', ng-change = 'updateMTU(newInterfaceNetwork)', required, ng-options='network.name_label for network in networks')
option(value = '', disabled) --
| &nbsp;
.form-group
fieldset(ng-attr-disabled = '{{ autoMac ? true : undefined }}')
label.control-label(for = 'newInterfaceMAC') MAC address&nbsp;
input#newInterfaceMAC.form-control(ng-class = '{hidden: autoMac}', type = 'text', ng-model = 'newInterfaceMAC', ng-required = '!autoMac')
| &nbsp;
.checkbox
label
input(type='checkbox', ng-model = 'autoMac')
| Auto-generate &nbsp;
| &nbsp;
.form-group
label(for = 'newInterfaceMTU') MTU&nbsp;
input#newInterfaceMTU.form-control(type = 'text', ng-model = 'newInterfaceMTU', required)
| &nbsp;
.form-group
button.btn.btn-primary(type = 'submit')
i.fa.fa-plus-square
| Create
span(ng-if = 'createVifWaiting')
| &nbsp;
i.fa.fa-spin.fa-circle-o-notch
br
//- Snap panel
.grid
form(editable-form="", name="vmSnap", oncancel="cancel()").panel.panel-default
.panel-heading.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
.panel-body
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.btn-group.quick-buttons
a(tooltip="Export this snapshot", type="button", xo-click="exportVM(snapshot.UUID)")
i.fa.fa-upload.fa-lg
a(tooltip="Revert VM to this snapshot", xo-click="revertSnapshot(snapshot.UUID)")
i.fa.fa-undo.fa-lg
a(tooltip="Remove this snapshot", xo-click="deleteSnapshot(snapshot.UUID)")
i.fa.fa-trash-o.fa-lg
.btn-form(ng-show="vmSnap.$visible")
p.center
button.btn.btn-default(type="button", ng-disabled="vmSnap.$waiting", ng-click="vmSnap.$cancel()")
i.fa.fa-times
| Cancel
| &nbsp;
button.btn.btn-primary(type="submit", ng-disabled="vmSnap.$waiting", ng-click="saveChanges()")
i.fa.fa-save
| Save
//- Logs panel
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-comments(style="color: #e25440;")
| Logs
span.quick-edit(ng-if="VM.messages.length", tooltip="Remove all logs", xo-click="deleteAllLog()")
i.fa.fa-trash-o.fa-fw
.panel-body
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}}
span.pull-right.btn-group.quick-buttons
a(xo-click="deleteLog(message.UUID)")
i.fa.fa-trash-o.fa-lg(tooltip="Remove this log entry")
.grid
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-plug(style="color: #e25440;")
| PCI Devices
.panel-body
p.center(ng-if="!(VM.$container | resolve).$PCIs") No PCI devices available
table.table.table-hover(ng-if="(VM.$container | resolve).$PCIs")
th PCI Info
th Device Name
th Status
tr(ng-repeat="pci in ((VM.$container | resolve).$PCIs | resolve) | orderBy:'pci_id' track by pci.UUID")
td {{pci.pci_id}} ({{pci.class_name}})
td {{pci.device_name}}
td(ng-if="pci.pci_id === VM.other.pci")
span.label.label-success Attached
span.pull-right.btn-group.quick-buttons
a(tooltip="Disconnect this PCI device", xo-click="disconnectPci(VM.UUID)")
i.fa.fa-unlink.fa-lg
td(ng-if="pci.pci_id !== VM.other.pci")
span.label.label-default Not attached
span.pull-right.btn-group.quick-buttons
a(tooltip="Connect this PCI device", xo-click="connectPci(VM.UUID, pci.pci_id)")
i.fa.fa-link.fa-lg
.panel.panel-default
.panel-heading.panel-title
i.fa.fa-desktop(style="color: #e25440;")
| vGPUs
.panel-body
p.center(ng-if="!VM.$VGPus") No vGPUs available
table.table.table-hover(ng-if="VM.$VGPus")
th Device
th Status
tr(ng-repeat="vgpu in VM.$VGPUs | resolve | orderBy:'device' track by vgpu.UUID")
td {{vgpu.device}}
td(ng-if="vgu.currentlyAttached")
span.label.label-success Attached
td(ng-if="!vgu.currentlyAttached")
span.label.label-default Not attached

View File

@@ -1,102 +0,0 @@
import angular from 'angular';
import {
format as formatUrl,
parse as parseUrl,
resolve as resolveUrl
} from 'url';
import {RFB} from 'novnc-node';
import view from './view';
//====================================================================
function parseRelativeUrl(url) {
/* global window: false */
return parseUrl(resolveUrl(String(window.location), url));
}
const PROTOCOL_ALIASES = {
'http:': 'ws:',
'https:': 'wss:',
};
function fixProtocol(url) {
let protocol = PROTOCOL_ALIASES[url.protocol];
if (protocol) {
url.protocol = protocol;
}
}
//====================================================================
export default angular.module('no-vnc', [])
.controller('NoVncCtrl', function ($attrs, $element, $scope) {
this.height = 480;
$attrs.$observe('height', (height) => {
this.height = height;
});
this.width = 640;
$attrs.$observe('width', (width) => {
this.width = width;
});
let rfb;
function clean() {
// If there was a previous connection.
if (rfb) {
rfb.disconnect();
rfb = undefined;
}
}
this.remoteControl = {
sendCtrlAltDel() {
if (rfb) {
rfb.sendCtrlAltDel();
}
},
};
let canvas = $element.find('canvas')[0];
$attrs.$observe('url', (url) => {
// Remove previous connection.
clean();
// If the URL is empty, stop now.
if (!url) {
return;
}
// Parse the URL.
url = parseRelativeUrl(url);
fixProtocol(url);
let isSecure = url.protocol === 'wss:';
rfb = new RFB({
encrypt: isSecure,
target: canvas,
wsProtocols: ['chat'],
});
// Connect.
rfb.connect(formatUrl(url));
});
$scope.$on('$destroy', clean);
})
.directive('noVnc', function () {
return {
bindToController: true,
controller: 'NoVncCtrl as noVnc',
restrict: 'E',
scope: {
remoteControl: '=',
},
template: view,
};
})
.name
;

View File

@@ -1,9 +0,0 @@
{
"private": true,
"browserify": {
"transform": [
"babelify",
"browserify-plain-jade"
]
}
}

View File

@@ -1,5 +0,0 @@
canvas.center-block(
height = "{{noVnc.height}}"
width = "{{noVnc.width}}"
)
| Sorry, your browser does not support the canvas element.

139
app/node_modules/xo-api/index.js generated vendored
View File

@@ -1,139 +0,0 @@
import angular from 'angular';
import indexOf from 'lodash.indexof';
let isArray = angular.isArray;
import 'angular-cookies';
import xoLib from 'xo-lib';
export default angular.module('xo-api', [
'ngCookies',
])
.run(function ($rootScope) {
// Ensure correct integration with Angular.
xoLib.setScheduler(function (fn) {
$rootScope.$evalAsync(fn);
});
})
.service('xoApi', function ($cookieStore) {
var xo = new xoLib.Xo();
try {
let token = $cookieStore.get('token');
// If there is a token, sign in with it.
if (token) {
xo.signIn({ token });
}
} catch (e) {
if (e instanceof SyntaxError) {
$cookieStore.remove('token');
} else {
throw e;
}
}
let getObject;
{
let {
all: byIds,
indexes: {
ref: byRefs,
UUID: byUuids
}
} = xo.objects;
// Look up an object by id, UUID or ref and optionally check its
// type.
getObject = (id, type) => {
let object = byIds[id];
// If not found by id, try by UUID and ref.
if (!object) {
object = byUuids[id] || byRefs[id];
if (!object) {
return;
}
// In indexes, the object is wrapped in an array.
object = object[0];
}
if (
// No type specifier.
!type ||
// A single type.
(type === object.type) ||
// An array of possible types.
isArray(type) && (indexOf(type, object.type) === -1)
) {
return object;
}
};
}
return {
//------------------
// Session
//------------------
logIn(email, password, persist) {
return xo.signIn({ email, password }).then(() => {
if (persist) {
xo.call('token.create').then(function (token) {
$cookieStore.put('token', token);
});
}
});
},
logOut() {
$cookieStore.remove('token');
return xo.signOut();
},
get status() {
return xo.status;
},
get user() {
return xo.user;
},
//------------------
// RPC
//------------------
call(method, params) {
return xo.call(method, params);
},
//------------------
// Objects
//------------------
get(id, types) {
if (isArray(id)) {
let objects = [];
angular.forEach(id, id => {
let object = getObject(id, types);
if (object) {
objects.push(object);
}
});
return objects;
}
return getObject(id, types);
},
all: xo.objects.all,
byTypes: xo.objects.indexes.type,
};
})
.name
;

View File

@@ -1,8 +0,0 @@
{
"private": true,
"browserify": {
"transform": [
"babelify"
]
}
}

View File

@@ -1,124 +0,0 @@
angular = require 'angular'
# 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) ->
$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[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[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()
updateValue = ->
current = $elem.val()
if ngModel.$pristine and current isnt previous
previous = current
ngModel.$setViewValue current
# Attempt to update the value.
$timeout updateValue, 5e2
# A refresh can be asked via the fixAutofill event.
$scope.$on 'fixAutofill', updateValue
# A module exports its name.
.name

View File

@@ -1,8 +0,0 @@
{
"private": true,
"browserify": {
"transform": [
"coffeeify"
]
}
}

View File

@@ -1,110 +0,0 @@
angular = require 'angular'
# TODO: split into multiple modules.
module.exports = angular.module 'xoWebApp.filters', [
require 'xo-api'
]
# The bytes filters takes a number and formats it using adapted
# units (KB, MB, etc.).
.filter 'bytesToSize', ->
(bytes, unit, base) ->
unit ?= 'B'
base ?= 1024
powers = ['', 'K', 'M', 'G', 'T', 'P']
i = 0
while bytes >= base
bytes /= base
++i
if bytes is -1
"-"
else
# Maximum 1 decimals.
bytes = ((bytes * 10)|0) / 10
"#{bytes}#{powers[i]}B"
.filter 'sizeToBytes', ->
regex = ///^
(\d+(?:\.\d+)?) # digits ('.' digits)?
\s* # Optional spaces beetween the digits and the unit.
([kmgtp])? # Optional unit modifier K/M/G/T/P.
b? # Optional unit (“b”), not meaningful.
$///i
factors = {
k: 1024
m: 1048576
g: 1073741824
t: 1099511627776
p: 1125899906842624
}
(size) ->
matches = regex.exec size
# If the input is invalid, just returns null.
return null unless matches
modifier = matches[2]
Math.round if modifier and (factor = factors[modifier.toLowerCase()])
factor * matches[1]
else
matches[1]
# Simply returns the number of elements in the collection.
.filter 'count', ->
(collection) ->
# Array.
if angular.isArray collection
return collection.length
# Object.
count = 0
for key of collection
++count if collection.hasOwnProperty key
count
# Resolves links between objects.
.filter('resolve', (xoApi) -> xoApi.get)
# Applies a function to a list of items.
#
# If a string is used instead of a function, it will be used as a
# property name to extract from each item.
#
# Note: This filter behaves nicely if the first argument is not an
# array.
.filter 'map', ->
(items, fn) ->
unless angular.isArray items
return []
if angular.isString fn
property = fn
fn = (item) -> item[property]
fn item for item in items
.filter '%', ->
(value) ->
# If `value` is an array of two values, divide the first by the
# second and mutiply by 100.
if value.length is 2
# Special case, if the divider is 0, simply returns "N/A".
return 'N/A' if value[1] is 0
result = 100 * value[0] / value[1]
if isNaN result
return 'N/A'
value = result
# No decimals at most.
value = (Math.round value * 1e0) / 1e0
"#{value}%"
# A module exports its name.
.name

View File

@@ -1,8 +0,0 @@
{
"private": true,
"browserify": {
"transform": [
"coffeeify"
]
}
}

View File

@@ -1,264 +0,0 @@
angular = require 'angular'
filter = require 'lodash.filter'
#=====================================================================
# TODO: split into multiple modules.
module.exports = angular.module 'xoWebApp.services', [
require 'angular-animate'
require 'angular-notify-toaster'
require 'xo-api'
]
.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 'xo', ($timeout, xoApi, notify) ->
# FIXME: default mapper should be identity.
defaultArgsMapper = (id) -> if id? then {id} else {}
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 ?= defaultArgsMapper
(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 = {
acl:
add: action('Adding an ACL entry', 'acl.add', {
argsMapper: (subject, object) => {subject, object},
})
get: action('Getting ACLs', 'acl.get')
remove: action('Remove an ACL entry', 'acl.remove', {
argsMapper: (subject, object) => {subject, object},
})
pool:
disconnect: action 'Disconnect pool'
new_sr: action 'New SR' #temp fix before creating SR
patch: action 'Upload patch', 'pool.patch', argsMapper: (pool) -> {pool}
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'
enable: action 'Enable host', 'host.enable'
stop: action 'Stop host', 'host.stop'
disable: action 'Disable host', 'host.disable'
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) -> angular.copy(params)
}
remove: action 'Remove server', 'server.remove', argsMapper: (id) -> {id}
getAll: action 'Getting server', 'server.getAll'
set: action 'Save server', 'server.set', {
argsMapper: (params) -> angular.copy(params)
}
connect: action 'Connect to a server', 'server.connect', notification: false, argsMapper: (id) -> {id}
disconnect: action 'Disconnect from a server', 'server.disconnect', argsMapper: (id) -> {id}
task:
cancel: action 'Cancel task', 'task.cancel', argsMapper: (id) -> {id}
destroy: action 'Destroy task', 'task.destroy', argsMapper: (id) -> {id}
user:
create: action 'Create user', 'user.create', {
argsMapper: (params) -> angular.copy(params)
}
delete: action 'Delete user', 'user.delete', argsMapper: (id) -> {id: "#{id}"}
getAll: action 'Getting users', 'user.getAll'
set: action 'Save user', 'user.set', {
argsMapper: (params) -> angular.copy(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}
}
export: action 'Export VM', 'vm.export', {
argsMapper: (vm, compress = true) -> {vm, compress}
}
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 }
}
import: action 'Import VM', 'vm.import', {
argsMapper: (host) -> { host }
}
migrate: action 'Migrate VM', 'vm.migrate', {
argsMapper: (id, host_id) -> { id, host_id }
}
migratePool: action 'Migrate VM to another pool', 'vm.migrate_pool', {
argsMapper: (params) -> angular.copy(params)
}
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'
suspend: action 'Suspend VM', 'vm.suspend'
resume: action 'Resume VM', 'vm.resume', {
argsMapper: (id, force = true) -> { id, force }
}
refreshStats: action 'Get Stats', 'vm.stats', {
argsMapper: (id) -> {id}
}
# TODO: create/set/pause
connectPci: action 'Connect PCI device', 'vm.attachPci', {
argsMapper: (vm, pciId) -> {vm, pciId}
}
disconnectPci: action 'Disconnect PCI device', 'vm.detachPci', {
argsMapper: (vm) -> {vm}
}
vdi:
delete: action 'Delete VDI', 'vdi.delete'
migrate: action 'Migrate VDI', 'vdi.migrate', {
argsMapper: (id, sr_id) -> { id, sr_id }
}
vif:
delete: action 'Delete VIF', 'vif.delete'
disconnect: action 'Disconnect VIF', 'vif.disconnect'
connect: action 'Connect VIF', 'vif.connect'
vbd:
delete: action 'Delete VBD', 'vbd.delete'
disconnect: action 'Disconnect VBD', 'vbd.disconnect'
connect: action 'Connect VBD', 'vbd.connect'
}
# TODO: should probably be merged in the main collection in xo-lib.
currentAcls = Object.create(null)
updateCurrentAcls = () ->
xoApi.call('acl.getCurrent').then((acls) ->
currentAcls = Object.create(null)
for acl in acls
object = xoApi.get(acl.object)
if object
currentAcls[object.id] = true
$timeout(updateCurrentAcls, 1e4)
return
)
updateCurrentAcls()
# Adds the dynamic properties.
Object.defineProperties(xo, {
byTypes: { get: ->
throw new Error('use xoApi.byTypes instead');
},
get: { get: ->
throw new Error('use xoApi.get() instead');
},
currentAcls: { get: -> currentAcls },
})
xo.canAccess = (id) ->
{id} = id if id.id
return (
# Administrators can access everything.
xoApi.user and (xoApi.user.permission is 'admin') or
# Check if the id is in the ACLs table.
(id of currentAcls) or
# Check if the id is in fact not a true id (maybe a ref or a
# UUID) and if we can resolve it to an id.
(id = xoApi.get(id)?.id) and (id of currentAcls)
)
# Returns the interface.
xo
.filter 'xoHideUnauthorized', (xo) ->
{canAccess} = xo
return (objects) -> filter objects, xo.canAccess
# A module exports its name.
.name

View File

@@ -1,8 +0,0 @@
{
"private": true,
"browserify": {
"transform": [
"coffeeify"
]
}
}

View File

@@ -1,645 +0,0 @@
//////////////////////////////////////////////////////////////////////
// Bootstrap
//////////////////////////////////////////////////////////////////////
@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap";
//////////////////////////////////////////////////////////////////////
// Font-Awesome 4.
//////////////////////////////////////////////////////////////////////
$fa-font-path: "";
@import "../../node_modules/font-awesome/scss/font-awesome";
// Replace Bootstrap's glyphicons by Font Awesome.
.glyphicon {
@extend .fa;
}
//////////////////////////////////////////////////////////////////////
// Angular xEditable.
//////////////////////////////////////////////////////////////////////
// TODO: do not use CSS import but includes
//
// This syntax is not yet supported for .css files.
//
// See https://github.com/sass/node-sass/issues/618
//@import "../../dist/bower_components/angular-xeditable/dist/css/xeditable";
@import "/bower_components/angular-xeditable/dist/css/xeditable.css";
//////////////////////////////////////////////////////////////////////
// Angular Notify Toaster.
//////////////////////////////////////////////////////////////////////
// TODO: do not use CSS import but includes
//
// This syntax is not yet supported for .css files.
//
// See https://github.com/sass/node-sass/issues/618
//@import "../../dist/bower_components/angular-notify-toaster/toaster";
@import "/bower_components/angular-notify-toaster/toaster.css";
//////////////////////////////////////////////////////////////////////
// UI Select.
//////////////////////////////////////////////////////////////////////
@import "/bower_components/angular-ui-select/dist/select.css";
//////////////////////////////////////////////////////////////////////
// Style applied to a message for outdated browsers (IE <= 7).
.browsehappy {
margin: 0.2em 0;
background: #cccccc;
color: black;
padding: 0.2em 0;
}
// Some links are used throught JavaScript and therefore does not have
// a `href` attribute, they should nevertheless have a *pointer*
// cursor.
// Same for all elements with the "ng-click" attribute.
a, [ng-click], [xo-click], [xo-sref] {
cursor: pointer;
}
// TODO: We want all our form inputs to be styled by Bootstrap.
//input, textarea, select
// @extend .form-control
// Click-through notifications.
#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
// - commented;
// - converted to SASS;
// - move into this file.
@import "to-clean";
//////////////////////////////////////////////////////////////////////
// Colors
//////////////////////////////////////////////////////////////////////
////
// Power states
////
.xo-color-running {
@extend .text-success;
}
.xo-color-halted {
@extend .text-danger;
}
.xo-color-paused, .xo-color-suspended {
@extend .text-info;
}
.xo-color-unknown {
@extend .text-muted;
}
.xo-color-pending {
@extend .text-warning;
}
//////////////////////////////////////////////////////////////////////
// XO icons
//////////////////////////////////////////////////////////////////////
////
// Alerts
////
.xo-icon-info {
@extend .fa;
@extend .fa-exclamation-circle;
@extend .text-muted;
}
.xo-icon-warning {
@extend .fa;
@extend .fa-exclamation-circle;
@extend .text-warning;
}
.xo-icon-error {
@extend .fa;
@extend .fa-exclamation-circle;
@extend .text-danger;
}
.xo-icon-success {
@extend .fa;
@extend .fa-check-circle;
@extend .text-success;
}
////
// Objects
////
.xo-icon-console {
@extend .fa;
@extend .fa-terminal;
}
.xo-icon-pool {
@extend .fa;
@extend .fa-cloud;
}
.xo-icon-host {
@extend .fa;
@extend .fa-server;
}
.xo-icon-vm {
@extend .fa;
@extend .fa-desktop;
}
.xo-icon-memory {
@extend .fa;
@extend .fa-sliders;
}
.xo-icon-cpu {
@extend .fa;
@extend .fa-dashboard;
}
.xo-icon-network {
@extend .fa;
@extend .fa-sitemap;
}
.xo-icon-sr {
@extend .fa;
@extend .fa-hdd-o;
}
.xo-icon-snapshot {
@extend .fa;
@extend .fa-camera;
}
.xo-icon-task {
@extend .fa;
@extend .fa-tasks;
}
.xo-icon-stats {
@extend .fa;
@extend .fa-line-chart;
}
.xo-icon-user {
@extend .fa;
@extend .fa-user;
}
.xo-icon-group {
@extend .fa-users;
}
////
// Power states
////
.xo-icon-running {
@extend .fa;
@extend .fa-circle;
@extend .xo-color-running;
}
.xo-icon-halted {
@extend .fa;
@extend .fa-circle;
@extend .xo-color-halted;
}
.xo-icon-suspended {
@extend .fa;
@extend .fa-circle;
@extend .xo-color-paused;
}
.xo-icon-paused {
@extend .fa;
@extend .fa-circle;
@extend .xo-color-paused;
}
.xo-icon-unknown {
@extend .fa;
@extend .fa-circle;
@extend .xo-color-unknown;
}
// if current operation
.xo-icon-working {
@extend .fa;
@extend .fa-circle;
@extend .text-warning;
}
////
// Others.
////
.xo-icon-cpu-low {
@extend .xo-icon-cpu;
@extend .text-success;
}
.xo-icon-cpu-medium {
@extend .xo-icon-cpu;
@extend .text-warning;
}
.xo-icon-cpu-high {
@extend .xo-icon-cpu;
@extend .text-danger;
}
////
// Guest OS
////
.xo-icon-linux {
@extend .fa;
@extend .fa-linux;
color: #666666;
}
.xo-icon-windows {
@extend .fa;
@extend .fa-windows;
color: #666666;
}
.xo-icon-other {
@extend .fa;
@extend .fa-question-circle;
color: #666666;
}
//////////////////////////////////////////////////////////////////////
// Navbar
//////////////////////////////////////////////////////////////////////
// The login form
.login-form {
padding: 1em;
width: 20em;
// small space between fields
.input-group {
margin: 0.2em;
}
// adapt button for the global theme (dark/blue/grey)
.input-group-addon {
background-color: #3a87ad;
color: #f8f8f8;
border: 0;
}
button {
margin-top: 1em;
}
// adapt form for the global theme
.form-control {
background-color: #666666;
color: #f8f8f8;
border: 0;
}
}
// background dark for the whole form
.login-form-dark {
@extend .login-form;
background-color: #2e3133;
}
//////////////////////////////////////////////////////////////////////
// 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;
@extend .text-muted;
font-size: 1.5em;
flex: 1;
align-self: center;
justify-content: center;
}
.quick-buttons {
display: inline;
float: right;
opacity: 0;
a {
color: #666666;
margin: 0 0.3em;
text-decoration: none;
&:hover {
color: black;
}
}
}
// Quick actions buttons are only visible when over their row.
tr:hover .quick-buttons {
opacity: 1;
}
// remove icon/button in last row on a table
.quick-remove {
display: inline;
float: right;
opacity: 0;
a {
color: #666666;
text-decoration: none;
&:hover {
color: black;
}
}
}
// remove only visible when over its row
tr:hover .quick-remove {
opacity: 1;
}
// edit icon/button in a panel title
.quick-edit {
display: inline;
float: right;
opacity: 1;
color: #666666;
&:hover {
color: black;
}
}
// Substats (less important host stats)
.substats {
opacity: 0;
}
// Substat displayed on hover
div.host-cell:hover .substats {
opacity: 1;
}
// Default SRs (where a new VM will be created).
.default-sr {
@extend .text-primary;
font-weight: bold;
}
// Prevents a host name from overflowing outside its box.
.host-cell, .pool-cell, .vm-cell {
@extend .panel;
& div {
position: relative;
}
}
.host-name, .pool-name, .sr-name {
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 80%;
overflow: hidden;
}
// Pool block
// A block display a whole pool
.pool-block {
margin-right: 0.8em;
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.8em;
}
/* individual host element */
.host-cell {
max-width: 156px;
background-color: white;
margin-bottom: 0.5em;
}
/* With gutters */
.grid--gutters {
// margin-left: -0.5em;
}
.grid--gutters > .grid-cell {
padding-left: 0.1em;
}
/* vm name in table */
.vm {
display: inline;
font-size: 10pt;
margin: 0;
}
.no-vm {
font-size: 1.5em;
margin-top: 1.8em;
color: #999;
}
.vm-cell table {
margin: 0;
}
.vm-cell td {
border-bottom: 1px solid #edece4 !important;
border-top: 0px !important;
}
//////////////////////////////////////////////////////////////////////
// Pool view
//////////////////////////////////////////////////////////////////////
// Allow progress bar in small spaces, like tables
.progress-condensed {
@extend .progress;
margin-bottom: 0;
}
//////////////////////////////////////////////////////////////////////
// Host view
//////////////////////////////////////////////////////////////////////
// Memory progress bar stacked for host
.progress-bar-host {
@extend .progress-bar;
border-right: 1px solid white;
}
// Memory progress bar stacked for VM
.progress-bar-vm {
@extend .progress-bar-success;
border-right: 1px solid white;
}
// Hover color for progress bar
.progress-bar:hover {
background-color: #e25440;
}
.grid {
display: flex;
}
//////////////////////////////////////////////////////////////////////
// General object view
//////////////////////////////////////////////////////////////////////
.panel {
flex: 1;
margin: 0.4em;
}
.panel-heading {
text-align: center;
font-variant: small-caps;
font-size: 1.3em;
font-weight: bold;
}
.panel-body-stats {
@extend .panel-body;
padding: 5px;
}
.chart-stat-preview {
max-height: 8em;
text-align: center;
}
.chart-stat-full {
max-height: 16em;
text-align: center;
}
.page-title {
text-align: center;
font-size: 2em;
margin: 0;
}
// display a big number for panel stats
.big-stat {
font-size: 5em;
color: #666;
}
//////////////////////////////////////////////////////////////////////
// Flat view
//////////////////////////////////////////////////////////////////////
.flat-object { @extend .panel; margin: 0.5em;}
.flat-object:hover {background-color: #f8f8f8;}
.flat-cell {border-radius: 0px; margin-left: 1em; margin-right: 1em; margin-top: 0.5em; margin-bottom: 0.5em;}
.flat-cell-name {max-width: 150px; min-width: 150px; font-weight: bold;}
.flat-cell-description {max-width: 250px; min-width: 250px;}
.flat-cell-type {max-width: 40px; font-size: 2em; text-align: center; margin: auto; margin-left: 0.5em;}
.flat-cell-tag {font-size: 11pt; margin: auto; min-height: 2.5em; padding-top: 0.4em; padding-left: 1em; opacity: 0.8;}
//////////////////////////////////////////////////////////////////////
// Login view
//////////////////////////////////////////////////////////////////////
.row-login {
@extend .panel;
background: #fff;
//padding: 20px 50px 20px 50px;
padding: 1em 5em 1em 5em;
max-width: 35em;
margin-left: auto;
margin-right: auto;
}
img.navbar-logo {
margin-top: -0.2em;
display: inline;
margin-right: 0.5em;
}
.navbar-brand {
font-variant: small-caps;
padding-top: 0.8em;
}
//////////////////////////////////////////////////////////////////////
// Settings
//////////////////////////////////////////////////////////////////////
.settings-menu {
position: fixed;
padding-top: 1em;
top: 51px;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #242628;
border-right: 1px solid #eee;
width: 212px;
}
.settings-menu li a {
font-size: 1em;
padding-: 1em;
padding-left: 2em;
display: block;
color: #f8f8f8;
&:hover, &:focus {
background-color: #2e3133;
}
}
.settings-content {
margin-left: 212px;
}
.fa-menu {
margin-right: 1.5em;
}

View File

@@ -1,227 +0,0 @@
/* Brand color */
.navbar-brand {color: #bf0000;}
input.form-control.inverse {background-color: #666; color: #f8f8f8; border: 0px;}
/* Search bar */
.popover { width: 200px; }
/* search button in main bar */
/* WARNING: hack because of strange Chrome behavior! Remove static height ASAP */
.btn-search {background-color: #3a87ad; color: #f8f8f8; height: 34px;}
.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;*/
}
body {
background-color: transparent;
}
.navbar-under { background-color: #f8f8f8;}
td.vcenter {
vertical-align: middle;
}
input[type="checkbox"]{
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;
}
.progress-bar-black {
color: black;
}
// FIXME: What is it?
//.i-progress { float: center; margin-right: 0em;}
.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;
}
.pool-name {font-size: 1.8em;}
.host-name {font-size: 20px;}
.big-caret {font-size: 1.4em;}
/* for dropdown menu not centered */
.dropdown-menu {background-color: white;}
.left {text-align: left;}
/* VM TABLE */
/* grab zone on VM table, for drag and drop */
td.grab {padding: 0 !important; margin: 0 !important; width: 6px !important; cursor: move;}
tr:hover .grab {background: url("../images/grip.png") no-repeat scroll 1px 50% transparent !important}
table { table-layout: fixed; }
table th, table td { overflow: hidden; }
td.vm-power-state {width: 20px; text-align: center;}
td.select-vm {width: 1.5em; height: 20px;}
td.vm-memory-stat {text-align: right;}
/* the main bar */
.navbar-inverse
{
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;
}
/* Black theme: inversed colors for drop down menus */
a.dropdown-toggle.inverse:hover {background-color:#2e3133 !important;}
a.dropdown-toggle.inverse:active {background-color:#2e3133 !important;}
a.dropdown-toggle.inverse:focus {background-color:#2e3133 !important;}
ul.inverse {background:#2e3133;}
ul.inverse a {color:#f8f8f8;}
ul.inverse a:hover {background-color:#242628; color: #f8f8f8 ;}
ul.inverse li.divider {background-color:#222;}
/* Inversed (black) buttons */
button.btn.navbar-btn.btn-default.inversed {background-color:#444; border-color: #222; color: #999999;}
a.btn.navbar-btn.btn-default.dropdown-toggle.inversed {background-color:#444; border-color: #222; color: #999999; margin-left: 0.5em;}
/* change caret color for inversed button */
.grey-caret {border-top-color: #999999 !important; border-bottom-color: #999999 !important;}
/* stats bar */
.overview { padding: 1em; color: #a6a6a6 ; max-width: 346px;}
.overview i {font-weight: normal;}
.small {font-size: 10pt; font-style: normal;}
.task-overview {
@extend .overview;
text-align: right;
}
.task-menu {
color: white;
}
/* stats pool and host */
.stats {text-align: center; margin-top: 1em; margin-bottom: 0.4em; padding-bottom: 0.4em; }
.stats a {text-decoration: none; color: #a6a6a6; margin-right: 0.6em;}
.stats a:hover {text-decoration: none; color: #333;}
.sr-name {font-size: 10pt;}
/* "actionbar" which is select and buttons when one or more VM selected */
.before-action-bar { margin-left: 1em;}
.action-bar {display: inline;}
/* SR table (in host and pool) */
.no-border {border-top: 0px !important;}
/* ip display */
.cpu {display: inline;}
.cpu a {text-decoration: none; color: #a6a6a6; margin-left: 0.9em;}
.cpu a:hover {text-decoration: none; color: #333;}
/* tooltip hack to avoid be hidden by other elements */
/*
.tooltip {
position: fixed;
}
*/
/* useful global class */
.center {text-align: center;}
.right {text-align: right;}
.small-caps {font-variant:small-caps;}
.big {font-size: 2em;}
.grid-cell {
flex: 1;
// border-radius: 3px 3px 3px 3px;
}
/* stats name in a grid cell */
.stat-name {text-align: center; font-variant: small-caps; margin-bottom: -0.3em;}
.grid-button {
margin-left: 1em;
margin-right: 1em;
margin-bottom: 0.5em;
padding: 0.5em;
background-color: #efefef;
}
.grid-button:hover {
background-color: #e25440;
}
.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;
}
legend.login {
border: medium none;
color: #111;
display: block;
font-size: 20px;
line-height: inherit;
margin-bottom: 15px;
padding: 0;
text-align: center;
width: 100%;
}
.btn-login {
padding: 10px;
border-radius: 0;
border: none;
font-size: 21px;
}
.status {
margin-top: 2em;
}

View File

@@ -1,16 +0,0 @@
{
"name": "xo-web",
"version": "0.0.0",
"dependencies": {
"angular-natural-sort": "https://bitbucket.org/OverZealous/angularjs-naturalsort/raw/f6e7ea30bf71324125549bd40a7cf9043abc2449/src/naturalSortVersionDates.js",
"angular-notify-toaster": "~0.4.8",
"angular-xeditable": "~0.1.8",
"ng-file-upload": "~1.6.12",
"angular-ui-select": "~0.9.9",
"Chart.js": "~1.0.2",
"angular-chart.js": "~0.5.3"
},
"resolutions": {
"angular": "~1.x"
}
}

View File

@@ -1,344 +1,291 @@
// Julien Fontanet gulpfile.js
//
// https://gist.github.com/julien-f/4af9f3865513efeff6ab
'use strict'
'use strict';
// ===================================================================
//====================================================================
var gulp = require('gulp');
// All plugins are loaded (on demand) by gulp-load-plugins.
var $ = require('gulp-load-plugins')();
//====================================================================
var DIST_DIR = __dirname +'/dist';
var SRC_DIR = __dirname +'/app';
// Bower directory is read from its configuration.
var BOWER_DIR = (function () {
var cfg;
try
{
cfg = JSON.parse(require('fs').readFileSync(__dirname +'/.bowerrc'));
}
catch (error)
{
cfg = {};
}
cfg.cwd || (cfg.cwd = __dirname);
cfg.directory || (cfg.directory = 'bower_components');
return cfg.cwd +'/'+ cfg.directory;
})();
var PRODUCTION = process.argv.indexOf('--production') !== -1;
var SRC_DIR = __dirname + '/src'
var DIST_DIR = __dirname + '/dist'
// Port to use for the livereload server.
//
// It must be available and if possible unique to not conflict with other projects.
// http://www.random.org/integers/?num=1&min=1024&max=65535&col=1&base=10&format=plain&rnd=new
var LIVERELOAD_PORT = 46417;
var LIVERELOAD_PORT = 26242
// Port to use for the embedded web server.
//
// Set to 0 to choose a random port at each run.
var SERVER_PORT = LIVERELOAD_PORT + 1;
var SERVER_PORT = LIVERELOAD_PORT + 1
// Address the server should bind to.
//
// - `'localhost'` to make it accessible from this host only
// - `null` to make it accessible for the whole network
var SERVER_ADDR = 'localhost';
var SERVER_ADDR = 'localhost'
//--------------------------------------------------------------------
var PRODUCTION = process.argv.indexOf('--production') !== -1
var DEVELOPMENT = !PRODUCTION
// Create a noop duplex stream.
var noop = function () {
var PassThrough = require('stream').PassThrough;
// ===================================================================
noop = function () {
return new PassThrough({
objectMode: true
});
};
var gulp = require('gulp')
return noop.apply(this, arguments);
};
// ===================================================================
// Browserify plugin for gulp.js which uses watchify in development
// mode.
function browserify(path, opts) {
opts || (opts = {});
var bundler = require('browserify')({
basedir: SRC_DIR,
debug: !PRODUCTION,
entries: [path],
extensions: opts.extensions,
standalone: opts.standalone,
// Required by Watchify.
cache: {},
packageCache: {},
fullPaths: !PRODUCTION,
});
if (!PRODUCTION) {
bundler = require('watchify')(bundler);
bundler.plugin('bundle-collapser/plugin');
function lazyFn (factory) {
var fn = function () {
fn = factory()
return fn.apply(this, arguments)
}
// Append the extension if necessary.
if (!/\.js$/.test(path)) {
path += '.js';
return function () {
return fn.apply(this, arguments)
}
// Absolute path.
path = require('path').resolve(SRC_DIR, path);
var proxy = noop();
var write;
function bundle() {
bundler.bundle(function onBundleComplete(err, buf) {
if (err) {
proxy.emit('error', err);
return;
}
write(new (require('vinyl'))({
base: SRC_DIR,
path: path,
contents: buf,
}));
});
}
if (PRODUCTION) {
write = proxy.end.bind(proxy);
} else {
proxy = $.plumber().pipe(proxy);
write = proxy.write.bind(proxy);
bundler.on('update', bundle);
}
bundle();
return proxy;
}
// Combine multiple streams together and can be handled as a single
// stream.
var combine = function () {
// `event-stream` is required only when necessary to maximize
// performance.
combine = require('event-stream').pipe;
return combine.apply(this, arguments);
};
// -------------------------------------------------------------------
// Merge multiple readable streams into a single one.
var merge = function () {
// `event-stream` is required only when necessary to maximize
// performance.
merge = require('event-stream').merge;
return merge.apply(this, arguments);
};
var livereload = lazyFn(function () {
var livereload = require('gulp-livereload')
livereload.listen(LIVERELOAD_PORT)
return livereload
})
var pipe = lazyFn(function () {
var pipe = require('nice-pipe')
return PRODUCTION
? pipe
: function () {
return require('gulp-plumber')().pipe(pipe.apply(this, arguments))
}
})
var resolvePath = lazyFn(function () {
return require('path').resolve
})
// -------------------------------------------------------------------
// Similar to `gulp.src()` but the pattern is relative to `SRC_DIR`
// and files are automatically watched when not in production mode.
var src = (function () {
var resolvePath = require('path').resolve;
function resolve(path) {
if (path) {
return resolvePath(SRC_DIR, path);
}
return SRC_DIR;
var src = lazyFn(function () {
function resolve (path) {
return path
? resolvePath(SRC_DIR, path)
: SRC_DIR
}
if (PRODUCTION)
{
return function src(pattern, base) {
base = resolve(base);
return PRODUCTION
? function src (pattern, base) {
base = resolve(base)
return gulp.src(pattern, {
base: base,
cwd: base,
});
};
}
sourcemaps: true
})
}
: function src (pattern, base) {
base = resolve(base)
// gulp-plumber prevents streams from disconnecting when errors.
// See: https://gist.github.com/floatdrop/8269868#file-thoughts-md
return function src(pattern, base) {
base = resolve(base);
return gulp.src(pattern, {
base: base,
cwd: base,
})
.pipe($.watch(pattern, {
base: base,
cwd: base,
}))
.pipe($.plumber())
;
};
})();
return pipe(
gulp.src(pattern, {
base: base,
cwd: base,
sourcemaps: true
}),
require('gulp-watch')(pattern, {
base: base,
cwd: base
}),
require('gulp-plumber')()
)
}
})
// Similar to `gulp.dest()` but the output directory is relative to
// `DIST_DIR` and default to `./`, and files are automatically live-
// reloaded when not in production mode.
var dest = (function () {
var resolvePath = require('path').resolve;
function resolve(path) {
if (path) {
return resolvePath(DIST_DIR, path);
var dest = lazyFn(function () {
function resolve (path) {
return path
? resolvePath(DIST_DIR, path)
: DIST_DIR
}
var opts = {
sourcemaps: {
path: '.'
}
return DIST_DIR;
}
if (PRODUCTION)
{
return function dest(path) {
return gulp.dest(resolve(path));
};
return PRODUCTION
? function dest (path) {
return gulp.dest(resolve(path), opts)
}
: function dest (path) {
return pipe(
gulp.dest(resolve(path), opts),
livereload()
)
}
})
// ===================================================================
function browserify (path, opts) {
if (opts == null) {
opts = {}
}
var livereload = function () {
$.livereload.listen(LIVERELOAD_PORT);
var bundler = require('browserify')(path, {
basedir: SRC_DIR,
debug: DEVELOPMENT,
extensions: opts.extensions,
fullPaths: DEVELOPMENT,
standalone: opts.standalone,
livereload = $.livereload;
return livereload();
};
return function dest(path) {
return combine(
gulp.dest(resolve(path)),
livereload()
);
};
})();
//====================================================================
gulp.task('buildPages', function buildPages() {
return src('[i]ndex.jade')
.pipe($.jade())
.pipe(PRODUCTION ? noop() : $.embedlr({ port: LIVERELOAD_PORT }))
.pipe(dest())
;
});
gulp.task('buildScripts', [
'installBowerComponents',
], function buildScripts() {
return browserify('./app', {
extensions: '.coffee .jade'.split(' '),
// Required by Watchify.
cache: {},
packageCache: {}
})
// Annotate the code before minification (for Angular.js)
.pipe($.ngAnnotate({
add: true,
'single_quotes': true,
}))
.pipe(PRODUCTION ? $.uglify() : noop())
.pipe(dest())
;
});
gulp.task('buildStyles', [
'installBowerComponents',
], function buildStyles() {
return src('styles/[m]ain.scss')
.pipe($.sass())
.pipe($.autoprefixer([
'last 1 version',
'> 1%',
]))
.pipe(PRODUCTION ? $.csso() : noop())
.pipe(dest())
;
});
gulp.task('copyAssets', [
'installBowerComponents',
], function copyAssets() {
var imgStream;
if (PRODUCTION) {
var imgFilter = $.filter('**/*.{gif,jpg,jpeg,png,svg}');
imgStream = combine(
imgFilter,
$.imagemin({
progressive: true,
}),
imgFilter.restore()
);
bundler.plugin('bundle-collapser/plugin')
} else {
imgStream = noop();
bundler = require('watchify')(bundler)
}
return merge(
src([
'[f]avicon.ico',
'images/**/*',
]).pipe(imgStream),
// Append the extension if necessary.
if (!/\.js$/.test(path)) {
path += '.js'
}
path = resolvePath(SRC_DIR, path)
var stream = new (require('readable-stream'))({
objectMode: true
})
var write
function bundle () {
bundler.bundle(function onBundle (error, buffer) {
if (error) {
stream.emit('error', error)
return
}
write(new (require('vinyl'))({
base: SRC_DIR,
contents: buffer,
path: path
}))
})
}
if (PRODUCTION) {
write = function (data) {
stream.push(data)
stream.push(null)
}
} else {
stream = require('gulp-plumber')().pipe(stream)
write = function (data) {
stream.push(data)
}
bundler.on('update', bundle)
}
stream._read = function () {
this._read = function () {}
bundle()
}
return stream
}
// ===================================================================
gulp.task(function buildPages () {
return pipe(
src('index.jade'),
require('gulp-jade')(),
DEVELOPMENT && require('gulp-embedlr')({
port: LIVERELOAD_PORT
}),
dest()
)
})
gulp.task(function buildScripts () {
return pipe(
browserify('./index.js'),
PRODUCTION && require('gulp-uglify')({
mangle: false // Avoid breaking Angular deps injector.
}),
dest()
)
})
gulp.task(function buildStyles () {
return pipe(
src('index.scss'),
require('gulp-sass')(),
require('gulp-autoprefixer')([
'last 1 version',
'> 1%'
]),
PRODUCTION && require('gulp-csso')(),
dest()
)
})
gulp.task(function copyAssets () {
return pipe(
src(['assets/**/*', 'favicon.*']),
src(
'fontawesome-webfont.*',
__dirname + '/node_modules/font-awesome/fonts/'
)
).pipe(dest());
});
__dirname + '/node_modules/font-awesome/fonts'
),
dest()
)
})
gulp.task('installBowerComponents', function installBowerComponents(done) {
require('bower').commands.install()
.on('error', done)
.on('end', function () {
done();
})
;
});
//--------------------------------------------------------------------
gulp.task('build', [
gulp.task('build', gulp.parallel(
'buildPages',
'buildScripts',
'buildStyles',
'copyAssets',
]);
'copyAssets'
))
gulp.task('clean', function clean(done) {
require('rimraf')(DIST_DIR, done);
});
// -------------------------------------------------------------------
gulp.task('distclean', ['clean'], function distclean(done) {
require('rimraf')(BOWER_DIR, done);
});
gulp.task(function clean (done) {
require('rimraf')(DIST_DIR, done)
})
gulp.task('server', function server(done) {
// -------------------------------------------------------------------
gulp.task(function server (done) {
require('connect')()
.use(require('serve-static')(DIST_DIR))
.listen(SERVER_PORT, SERVER_ADDR, function serverOnListen() {
var address = this.address();
.listen(SERVER_PORT, SERVER_ADDR, function onListen () {
var address = this.address()
var port = address.port;
address = address.address;
var port = address.port
address = address.address
// Correctly handle IPv6 addresses.
if (address.indexOf(':') !== -1) {
address = '['+ address +']';
address = '[' + address + ']'
}
console.log('Listening on http://'+ address +':'+ port);
/* jshint devel: true*/
console.log('Listening on http://' + address + ':' + port)
})
.on('close', function serverOnClose() {
done();
.on('error', done)
.on('close', function onClose () {
done()
})
;
});
//------------------------------------------------------------------------------
gulp.task('default', ['build']);
})

17
node_modules/angular-file-upload.js generated vendored
View File

@@ -1,17 +0,0 @@
'use strict';
//====================================================================
/* global window: false */
window.FileApi = {
jsPath: '/bower_components/ng-file-upload/',
staticPath: '/bower_components/ng-file-upload/',
};
// Must be loaded before Angular.
require('../dist/bower_components/ng-file-upload/angular-file-upload-shim');
require('angular');
require('../dist/bower_components/ng-file-upload/angular-file-upload');
module.exports = 'angularFileUpload';

View File

@@ -1,4 +0,0 @@
'use strict';
require('../dist/bower_components/angular-natural-sort/index');
module.exports = 'naturalSort';

View File

@@ -1,4 +0,0 @@
'use strict';
require('../dist/bower_components/angular-notify-toaster/toaster.js');
module.exports = 'toaster';

View File

@@ -1,7 +0,0 @@
'use strict';
//====================================================================
require('angular-bootstrap');
module.exports = 'ui.bootstrap';

View File

@@ -1,4 +0,0 @@
'use strict';
require('./angular-ui-utils/modules/indeterminate/indeterminate');
module.exports = 'ui.indeterminate';

5
node_modules/angular-ui-select.js generated vendored
View File

@@ -1,5 +0,0 @@
'use strict';
require('../dist/bower_components/angular-ui-select/dist/select');
module.exports = 'ui.select';

4
node_modules/angular-xeditable.js generated vendored
View File

@@ -1,4 +0,0 @@
'use strict';
require('../dist/bower_components/angular-xeditable/dist/js/xeditable');
module.exports = 'xeditable';

8
node_modules/angular.js generated vendored
View File

@@ -1,8 +0,0 @@
'use strict';
//====================================================================
// Force Angular to use jQuery.
require('jquery');
module.exports = require('./angular/index.js');

16
node_modules/has.js generated vendored
View File

@@ -1,16 +0,0 @@
'use strict';
//====================================================================
module.exports = (function (hasOwnProperty) {
/* jshint eqnull: true */
return hasOwnProperty ?
function has(obj, prop) {
return (obj != null) && hasOwnProperty.call(obj, prop);
} :
function has(obj, prop) {
return (obj != null) && obj[prop] !== undefined;
}
;
})(Object.prototype.hasOwnProperty);

21
node_modules/isempty.js generated vendored
View File

@@ -1,21 +0,0 @@
'use strict';
//====================================================================
var has = require('has');
//====================================================================
module.exports = function isEmpty(obj) {
if (has(obj, 'length')) {
return obj.length === 0;
}
var prop;
for (prop in obj) {
if (has(obj, prop)) {
return false;
}
}
return true;
};

10
node_modules/jquery.js generated vendored
View File

@@ -1,10 +0,0 @@
'use strict';
//====================================================================
var jQuery = require('./jquery/dist/jquery');
/* global window: false */
window.jQuery = window.$ = jQuery;
module.exports = jQuery;

View File

@@ -1,7 +1,8 @@
{
"private": true,
"name": "xo-web",
"version": "3.9.0",
"license": "AGPL3",
"version": "5.0.0",
"license": "AGPL-3.0",
"description": "Web interface client for Xen-Orchestra",
"keywords": [
"xen",
@@ -9,52 +10,8 @@
"xen-orchestra",
"web"
],
"devDependencies": {
"angular": "^1.3.15",
"angular-animate": "^1.3.15",
"angular-bootstrap": "^0.12.0",
"angular-cookies": "^1.3.15",
"angular-ui-router": "^0.2.13",
"angular-ui-utils": "^0.1.1",
"babelify": "^6.0.2",
"bluebird": "^2.9.14",
"bootstrap-sass": "^3.3.4",
"bower": "^1.3.12",
"browserify": "^9.0.3",
"browserify-plain-jade": "^0.2.2",
"bundle-collapser": "^1.1.4",
"coffeeify": "^1.0.0",
"event-stream": "^3.3.0",
"font-awesome": "^4.3.0",
"gulp": "^3.8.11",
"gulp-autoprefixer": "^2.1.0",
"gulp-coffee": "^2.3.1",
"gulp-csso": "^1.0.0",
"gulp-embedlr": "^0.5.2",
"gulp-filter": "^2.0.2",
"gulp-imagemin": "^2.2.1",
"gulp-jade": "^1.0.0",
"gulp-livereload": "^3.8.0",
"gulp-load-plugins": "^0.8.1",
"gulp-ng-annotate": "^0.5.2",
"gulp-plumber": "^1.0.0",
"gulp-sass": "^1.3.3",
"gulp-uglify": "^1.1.0",
"gulp-watch": "^4.2.0",
"in-publish": "^1.1.1",
"jquery": "^2.1.3",
"lodash.difference": "^3.0.1",
"lodash.filter": "^3.0.0",
"lodash.includes": "^3.1.1",
"lodash.indexof": "^3.0.2",
"lodash.sortby": "^3.1.0",
"lodash.throttle": "^3.0.1",
"novnc-node": "^0.5.1",
"rimraf": "^2.3.2",
"vinyl": "^0.4.6",
"watchify": "^3.1.1",
"xo-lib": "^0.6.3"
},
"homepage": "https://github.com/vatesfr/xo-web",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xo-web"
@@ -63,22 +20,92 @@
"name": "Julien Fontanet",
"email": "julien.fontanet@vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"engines": {
"node": ">=0.8.0"
"node": ">=0.12"
},
"devDependencies": {
"babel-eslint": "^5.0.0-beta6",
"babel-plugin-transform-runtime": "^6.4.3",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-0": "^6.3.13",
"babelify": "^7.2.0",
"bootstrap": "github:twbs/bootstrap#v4-dev",
"browserify": "^13.0.0",
"browserify-plain-jade": "^0.2.2",
"bundle-collapser": "^1.2.1",
"clarify": "^1.0.5",
"connect": "^3.4.0",
"dependency-check": "^2.5.1",
"font-awesome": "^4.5.0",
"gulp": "github:gulpjs/gulp#4.0",
"gulp-autoprefixer": "^3.1.0",
"gulp-csso": "^1.0.1",
"gulp-embedlr": "^0.5.2",
"gulp-jade": "^1.1.0",
"gulp-livereload": "^3.8.1",
"gulp-plumber": "^1.0.1",
"gulp-sass": "^2.1.1",
"gulp-uglify": "^1.5.1",
"gulp-watch": "^4.3.5",
"history": "^2.0.0-rc2",
"jsonrpc-websocket-client": "0.0.1-4",
"mocha": "^2.3.4",
"must": "^0.13.1",
"nice-pipe": "^0.3.4",
"nyc": "^5.3.0",
"react": "^0.14.6",
"react-dom": "^0.14.6",
"react-intl": "^1.2.2",
"react-redux": "^4.0.6",
"react-router": "^2.0.0-rc5",
"react-router-redux": "^2.1.0",
"readable-stream": "^2.0.5",
"redux": "^3.0.5",
"redux-devtools": "^3.0.1",
"redux-router": "^1.0.0-beta7",
"redux-thunk": "^1.0.3",
"serve-static": "^1.10.2",
"source-map-support": "^0.4.0",
"standard": "^5.4.1",
"trace": "^2.0.2",
"vinyl": "^1.1.1",
"watchify": "^3.7.0",
"xo-lib": "^0.8.0-1"
},
"scripts": {
"build": "gulp distclean && gulp build --production",
"dev": "gulp build",
"prepublish": "in-publish && npm run build || in-install"
"build": "gulp build --production",
"dev": "gulp build server",
"dev-test": "mocha --opts .mocha.opts --watch --reporter=min \"dist/**/*.spec.js\"",
"lint": "standard",
"depcheck": "dependency-check ./package.json",
"posttest": "npm run lint && npm run depcheck",
"prepublish": "npm run build",
"test": "nyc mocha --opts .mocha.opts \"dist/**/*.spec.js\""
},
"browser": {
"node_modules/ws/index.js": "./ws.js"
},
"files": [
"dist"
],
"browserify": {
"transform": [
"babelify",
"browserify-plain-jade",
"coffeeify"
"browserify-plain-jade"
]
},
"standard": {
"ignore": [
"dist"
],
"parser": "babel-eslint"
},
"dependencies": {
"babel-runtime": "^6.3.19",
"redux-promise": "^0.5.1"
}
}

50
src/index.jade Normal file
View File

@@ -0,0 +1,50 @@
//- HTML 5 Doctype
doctype html
//- The `no-js` class will be automatically removed if JavaScript is
//- available.
html.no-js(
dir = 'ltr'
lang = 'en'
)
head
meta(charset = 'utf-8')
//- Makes sure IE is using the last engine available.
meta(
http-equiv = 'x-ua-compatible'
content = 'ie=edge,chrome=1'
)
//- .visible-js to display content only when JavaScript is ENABLED.
//- .hidden-js to display content only when JavaScript is DISABLED.
script !function(d){d.className=d.className.replace(/\bno-js\b/,'js')}(document.documentElement)
style .no-js .visible-js,.js .hidden-js{display:none}
//- (TODO: 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'
)
title Xen Orchestra
link(
rel = 'stylesheet'
href = 'index.css'
)
body
xo-app
.container: .row: .col-xs-12
h1.hidden-js.text-danger JavaScript is required for Xen Orchestra!
h1.visible-js.text-muted
i.xo-icon-loading
| Xen Orchestra is loading…
script(src = 'index.js')

9
src/index.js Normal file
View File

@@ -0,0 +1,9 @@
import React from 'react'
import { render } from 'react-dom'
import XoApp from './xo-app'
render(
<XoApp/>,
document.getElementsByTagName('xo-app')[0]
)

23
src/index.scss Normal file
View File

@@ -0,0 +1,23 @@
// http://v4-alpha.getbootstrap.com/getting-started/flexbox/#how-it-works
$enable-flex: true;
@import "../node_modules/bootstrap/scss/bootstrap";
// -------------------------------------------------------------------
$fa-font-path: "./";
@import "../node_modules/font-awesome/scss/font-awesome";
// Replace Bootstrap's glyphicons by Font Awesome.
.glyphicon {
@extend .fa;
}
// ===================================================================
.xo-icon-loading {
@extend .fa;
@extend .fa-spinner;
@extend .fa-pulse;
}

88
src/store/actions.js Normal file
View File

@@ -0,0 +1,88 @@
/*import Xo from 'xo-lib'
import { createBackoff } from 'jsonrpc-websocket-client'*/
/* action type */
export const VM_CREATE = 'VM_CREATE' // create a VM in store
export const VM_EDIT = 'VM_EDIT' // edit a vm in store
export const VM_SAVE = 'VM_SAVE' // want to save a vm from store to server
export const VM_SAVED = 'VM_SAVED' // VM is saved on server
export const SIGN_IN = 'SIGN_IN' // ask to signin
export const SIGNED_IN = 'SIGNED_IN' // is signed in
export const SIGN_OUT = 'SIGN_OUT' // want to sign out
export const SIGNED_OUT = 'SIGNED_OUT' // signed out
export const SESSION_PATCH = 'SESSION_PATCH' // signed out
/* action creator
* they HAVE TO return an action with the mandatory field type, and an optiona payload
* they MAY dispatch ( emit ) other actions, async or not
* action will be used by reducers
*/
export function patchSession (patch) {
return {
type: SESSION_PATCH,
payload: patch
}
}
export function signIn () {
// using redux thunk https://github.com/gaearon/redux-thunk
// instead of returning one promise, it can dispatch multiple events
// that way it become trivial to inform user of progress
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(signedIn({userId: Math.floor(Math.random() * 1000)}))
}, 10)
dispatch({type: 'SIGN_IN'}) // immediatly inform the sore that we'll try to signin
}
}
// you can also directly return an action
export function signedIn (payload) {
return {
type: 'SIGNED_IN',
payload
}
}
export function VMCreate (payload) {
return {
type: VM_CREATE,
payload
}
}
export function VMEdit( payload){
// should check if there s a vm id ?
return {
type: VM_EDIT,
payload
}
}
export function VMSave (vmId) {
//should call xoApi and save to server
return dispatch => {
setTimeout(function(){
console.log('really save')
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(VMSaved({id: Math.floor(Math.random() * 1000)}))
}, 1000)
dispatch({
type: VM_SAVE,
payload: {id: vmId}
}) // immediatly inform the sore that we'll try to save
}
}
export function VMSaved (vm) {
//xoAPi is happy, let's tell everyone this vm is save
return {
type: VM_SAVED,
payload: vm
}
}

35
src/store/index.js Normal file
View File

@@ -0,0 +1,35 @@
import {
applyMiddleware,
combineReducers,
compose,
createStore
} from 'redux'
import reducer from './reducers'
import thunk from 'redux-thunk'
import initialState from './initialStoreState'
let createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(reducer)
export default store
// ===================================================================
/*
export default compose(
applyMiddleware(reduxPromise),
// applyMiddleware(reduxThunk),
reduxReactRouter({
createHistory: createHashHistory
})
)(createStore)(combineReducers({
...reducers,
router: routerStateReducer
}))
export * as actions from './actions'
*/

View File

@@ -0,0 +1,11 @@
export default {
xoApi: {
}, // somehting like xoApi.all
components: {}, // UI state for each widget /components
session: {
isLoggingIn: false,
isLoggingOut: false,
isLoggued: false
} // session specific information
}

110
src/store/reducers.js Normal file
View File

@@ -0,0 +1,110 @@
import * as actions from './actions'
import { combineReducers } from 'redux'
import initialState from './initialStoreState'
/* Reducers are synchronous and repeatable : it's not the place to put api call
*/
function sessionReducer (currentSession = initialState.session, action) {
switch (action.type) {
case actions.SESSION_PATCH :
let payload = action.payload
// don't change loggued state from this action
delete payload.isLoggingIn
delete payload.isLoggingOut
delete payload.isLoggued
return Object.assign(
{},
currentSession,
payload // login , password
)
case actions.SIGN_IN :
// user tried to sign in
return Object.assign(
{},
currentSession,
action.payload, // login , password
{isLoggingIn: true}
)
case actions.SIGN_OUT :
return Object.assign({}, currentSession, {isLoggingOut: true})
case actions.SIGNED_OUT:
// when user is signed out : reset session
return initialState.session
case actions.SIGNED_IN :
// when user is signed in : session informations are received from server
return Object.assign(
{},
currentSession,
action.payload,
{
isLoggued: true,
isLoggingIn: false,
isLoggingOut: false,
password: 'redacted , don t need it '
}
)
default :
return currentSession // reducer HAVE TO return a state, even when they did nothing
}
}
function xoApiReducer (state = initialState.xoApi, action) {
let payload = action.payload || {}
switch (action.type){
case actions.VM_EDIT:
payload = Object.assign({},
state[action.payload.id],
payload,
{isSaved: false}
)
return Object.assign(
{},
state,
{
[action.payload.id]: payload
}
)
case actions.VM_SAVE:
console.log(' SAVE ',action.payload)
return Object.assign(
{},
state,
{
[action.payload.id]: {isSaving:true}
}
)
return state
case actions.VM_SAVED:
// put the VM at its real id if it's a creation
payload = Object.assign({},
state[action.payload.id],
payload,
{isSaved: true, isSaving: false}
)
console.log('VM_SAVED',payload)
return Object.assign(
{},
state,
{
[action.payload.id]: payload
}
)
}
return state
}
function componentsReducer (state = initialState.components, action) {
return state
}
export default combineReducers({
session: sessionReducer,
components: componentsReducer,
xoApi: xoApiReducer
})

View File

@@ -0,0 +1,3 @@
import React from 'react'
export default () => <h1>About</h1>

3
src/xo-app/home/index.js Normal file
View File

@@ -0,0 +1,3 @@
import React from 'react'
export default () => <h1>Home</h1>

118
src/xo-app/index.js Normal file
View File

@@ -0,0 +1,118 @@
import React, { Component, PropTypes } from 'react'
import { connect, Provider } from 'react-redux'
import store from '../store'
import LoginForm from './login/loginForm.js'
import VMForm from './vm/form.js'
import { Router, Route, Link } from 'react-router'
// from https://github.com/rackt/react-router/blob/master/upgrade-guides/v2.0.0.md#no-default-history
import { hashHistory } from 'react-router'
const Dashboard = React.createClass({
render() {
return <div>Welcome to the app!</div>
}
})
const App = React.createClass({
render() {
return (
<div>
<ul>
<li><Link to="/vm/Lab1/create">createvm</Link></li>
<li><Link to="/inbox">Inbox</Link></li>
</ul>
{this.props.children}
</div>
)
}
})
const Inbox = React.createClass({
render() {
return (
<div>
<h2>Inbox</h2>
{this.props.children || "Welcome to your Inbox"}
</div>
)
}
})
const Message = React.createClass({
render() {
return <h3>Message {this.props.params.id}</h3>
}
})
class XoAppUnconnected extends Component {
render () {
const {isLoggued, login, password, userId} = this.props
return (
<div>
<h2> Xen Orchestra {login} {isLoggued ? 'connecté' : ' non connecté'}</h2>
{!isLoggued &&
<LoginForm />
}
{isLoggued &&
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="vm/:host/:vmId" component={ VMForm } host='lab1'/>
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</Route>
</Router>
}
</div>
)
}
}
const XoApp = connect(state => state.session)(XoAppUnconnected)
/*
* Provider allow the compontn directly bellow XoApp to have acces to the store
* and to the dispatch method
*/
export default () =>
<Provider store={ store }>
<XoApp/>
</Provider>
/*
import About from './about'
import Home from './home'
import CreateVm from './create-vm'
//import CreatVM from './create-vm'
class XoApp extends Component {
static propTypes = {
children: PropTypes.node
};
render () {
}
_do (action) {
return () => this.props.dispatch(action)
}
}
XoApp = connect(state => state)(XoApp)
export default () => <div>
<h1>Xen Orchestra</h1>
<ReduxRouter>
<Route path='/' component={ XoApp }>
<IndexRoute component={ Home } />
<Route path='/about' component={ About } />
<Route path='/create-vm' component={ CreateVm } />
</Route>
</ReduxRouter>
</div>*/

View File

@@ -0,0 +1,61 @@
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import {signIn, signedIn, patchSession } from '../../store/actions'
class LoginForm extends Component {
handleSigninIn (e) {
const { actions } = this.props
e.preventDefault()
actions.signIn()
}
updateLogin (event) {
this.props.actions.patchSession({login: event.target.value})
}
updatePassword (event) {
this.props.actions.patchSession({password: event.target.value})
}
render () {
/*remeber : this.propos.session is from the connect call*/
const { login, password, isLoggingIn } = this.props.session
return (
<div>
<form onSubmit={(e) => this.handleSigninIn(e)}>
<input type='text' name='login' value={login} onChange={(e) => this.updateLogin(e)/* autobinding is only in ES5*/}/>
<input type='password' name='password' value={password} onChange={(e) => this.updatePassword(e)}/>
{isLoggingIn &&
<p>signing in , waiting for server</p>
}
{!isLoggingIn &&
<input type='submit' value='Sign in'/>
}
</form>
</div>
)
}
}
/* Which part of the global app state this component can see ?
* make it as small as possible to reduce the rerender */
export default connect(
(state) => {
return {session: state.session}
},
/* Transmit action and actions creators
* It can be usefull to transmit only a few selected actions.
* Also bind them to dispatch , so the component can call action creator directly,
* without having to manually wrap each
*/
(dispatch) => {
return {
actions: bindActionCreators({
signIn, patchSession, signedIn
},
dispatch) }
}
)(LoginForm)

200
src/xo-app/vm/form.js Normal file
View File

@@ -0,0 +1,200 @@
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { VMEdit, VMSave } from '../../store/actions'
// should move to glbal style
const fieldsetStyle = {
'display': 'flex',
'flexDirection': 'row'
}
const inputContainerStyle = {
'flex': 1
}
const legendStyle = {
'width': '150px'
}
/* I don't use fieldset / legend here since they don't
* really like being styled with flex
*/
class VmForm extends Component {
static propType = {
routeParams: PropTypes.object,
vm: PropTypes.object,
actions: PropTypes.object
};
save (e){
e.preventDefault()
this.props.actions.VMSave(this.props.routeParams.vmId)
}
patch (field,value){
this.props.actions.VMEdit({
id:this.props.routeParams.vmId,
[field]:value
})
}
addSr ( e) {
e.preventDefault()
const vm = this.props.vm || {}
let srs = vm.srs || []
srs.push({})
this.props.actions.VMEdit({
id:this.props.routeParams.vmId,
srs:srs
})
}
editSr( index, field, value) {
const vm = this.props.vm || {}
let srs = vm.srs || []
srs[index].field = value
this.props.actions.VMEdit({
id:this.props.routeParams.vmId,
srs:srs
})
}
removeSr (e,index) {
e.preventDefault()
const vm = this.props.vm || {}
let srs = vm.srs || []
srs.splice(index,1)
this.props.actions.VMEdit({
id:this.props.routeParams.vmId,
srs:srs
})
}
render () {
const s = this.state
const {name,templateId, isSaving, isSaved, desc, vcpus, ram , ramUnit, srs } = this.props.vm || {}
const {host} = this.props.routeParams
console.log('VM is now ',this.props.vm)
return (
<form onSubmit={(e) => this.save(e)}>
<h2>Create VM on {host}</h2>
<div style={fieldsetStyle}>
<div style={legendStyle}>
Infos
</div>
<div style={inputContainerStyle}>
<label htmlFor='vm-edit-name'>Name : </label>
<input
id ='vm-edit-name'
type='text'
value={name}
onChange={(e) => this.patch('name', e.target.value)}/>
<label htmlFor='vm-template-id'>Template : </label>
<select
id='vm-template-id'
value={templateId}
onChange={(e) => this.patch('templateId', e.target.value)}>
<option></option>
<option value='0'>Template0</option>
<option value='1'>Template1</option>
</select>
<br/>
<label htmlFor='Description'>Description</label>
<input type='text'
value={desc}
placeholder='Optional description'
onChange={(e) => this.patch('desc', e.target.value)}/>
</div>
</div>
<div style={fieldsetStyle}>
<div style={legendStyle}>
Perf
</div>
<div style={inputContainerStyle}>
<label htmlFor='vm-edit-vcpus'>VCPUs : </label>
<input
id ='vm-edit-vcpus'
type='number'
value={vcpus}
onChange={(e) => this.patch('vcpus', e.target.value)}/>
<label htmlFor='vm-template-id'>Ram : </label>
<input type='number'
value={ram}
defaultValue='0'
onChange={(e) => this.patch('ram', e.target.value)}/>
<select
id='vm-edit-ramUnit'
value={ramUnit}
onChange={(e) => this.patch('ramUnit', e.target.value)}>
<option value='MB'>MB</option>
<option value='GB'>GB</option>
<option value='TB'>TB</option>
</select>
</div>
</div>
<div style={fieldsetStyle}>
<div style={legendStyle}>
Disks
</div>
<div style={inputContainerStyle}>
{(srs || []).map((sr, i)=>{
return <div key={'srs'+i} /*should be sr.id*/>
Name <input type='text' value={sr.name} onChange={(e) => this.editSr(i, 'name',e.target.value)}></input>
desc <input type='text' value={sr.mac} placeholder='optionnal desc' onChange={(e) => this.editSr(i, 'desc',e.target.value)}></input>
<button onClick={(e) => this.removeSr(e,i)}> X</button>
</div>
})}
<button onClick={(e) => this.addSr(e)}>+ addInterface</button>
</div>
</div>
<div style={fieldsetStyle}>
<div style={legendStyle}>
Summary
</div>
<div style={inputContainerStyle}>
{vcpus} x cpus {ram} {ramUnit || 'MB'}
</div>
</div>
{!isSaving && !isSaved &&
<button type='submit'>Save</button>
}
{isSaving &&
<p>Saving to server</p>
}
{!isSaving && isSaved &&
<p>Not modified</p>
}
</form>
)
}
}
/* Which part of the global app state this component can see ?
* make it as small as possible to reduce the rerender */
//ownprop is the prop given to the component
function mapStateToProps(state, ownProps) {
console.log(ownProps.routeParams.vmId);
return {
vm :state.xoApi[ownProps.routeParams.vmId]
}
}
export default connect(
mapStateToProps,
/* Transmit action and actions creators
* It can be usefull to transmit only a few selected actions.
* Also bind them to dispatch , so the component can call action creator directly,
* without having to manually wrap each
*/
(dispatch) => {
return {
actions: bindActionCreators({
VMEdit, VMSave
},
dispatch) }
}
)(VmForm)