Compare commits

...

1607 Commits

Author SHA1 Message Date
Julien Fontanet
563b4cb1ec 5.2.5 2016-10-07 15:45:28 +02:00
Olivier Lambert
45bad231cf feat(changelog): add 5.2.4 and 5.2.5 release 2016-10-07 15:44:47 +02:00
Pierre Donias
d76bd2484b fix(console): disable shortcuts when console is focused (#1637)
Fixes #1614
2016-10-07 15:26:20 +02:00
Pierre Donias
445b60bb63 fix(vm/console): initial scale value should be 1 (#1639) 2016-10-07 14:06:16 +02:00
Julien Fontanet
3214e0e41e fix: style & minor issues 2016-10-06 18:28:23 +02:00
Julien Fontanet
c61230e145 fix(intl/locales/fr): remove incorrect entry 2016-10-06 16:13:54 +02:00
fufroma
fac6a29226 feat(intl): new translatable messages (#1627) 2016-10-06 16:05:47 +02:00
Olivier Lambert
7a8f414748 feat(home/host): sparklines in expanded zone (#1619)
Fixes #1634
2016-10-06 15:14:35 +02:00
Julien Fontanet
9f450d282e chore(package): use index-modules 2016-10-06 14:41:46 +02:00
Pierre Donias
31787067e3 feat(new-vm): set dynamic and static memory bounds (#1618)
Fixes #1603
2016-10-05 17:27:59 +02:00
fufroma
1a769b23e2 feat(i18n): update French translation (#1600) 2016-10-05 10:38:12 +02:00
Olivier Lambert
ae002abafc feat(home/pool): bar for pool RAM usage (#1626)
Fixes #1625
2016-10-05 10:26:47 +02:00
Julien Fontanet
31a25d9c16 5.2.4 2016-10-04 15:35:11 +02:00
Julien Fontanet
356295c361 fix(package): add missing make-error 2016-10-04 15:33:24 +02:00
Julien Fontanet
d10681b6d1 fix(package): add missing even-to-promise 2016-10-04 15:33:24 +02:00
Julien Fontanet
0602410aa8 fix(package): update xo-acl-resolver to 0.2.2
Fixes vatesfr/xo-web#1621
2016-10-04 15:33:23 +02:00
Olivier Lambert
1112768adc feat(home/host): add memory bar (#1617)
Fixes #1616
2016-10-03 18:06:36 +02:00
Julien Fontanet
86b599df89 5.2.3 2016-10-03 09:39:59 +02:00
Olivier Lambert
88f7661172 feat(changelog): add info for 5.2.3 release 2016-10-03 09:21:45 +02:00
Julien Fontanet
29c96c0119 chore(gitignore): pnpm compat 2016-10-03 09:15:18 +02:00
Julien Fontanet
d8c6e54c68 fix(user): add VM template label 2016-10-03 09:13:42 +02:00
Julien Fontanet
df053eb016 fix(user): do not crash on missing type label 2016-10-03 09:13:26 +02:00
Julien Fontanet
d1715f7711 chore(intl): homeTypeTemplate → homeTypeVmTemplate 2016-10-03 09:09:40 +02:00
Julien Fontanet
240282c72d chore(intl): remove unused message 2016-10-03 09:09:03 +02:00
Olivier Lambert
9e8dd6ea21 fix(README): broken link to doc 2016-10-02 23:23:19 +02:00
Julien Fontanet
32806a20c9 fix(sr): goes to homepage if object disappear
Fixes #1611
2016-10-02 23:02:14 +02:00
Pierre Donias
34dcfbbf49 fix(home/item): prevent item from being displayed on 2 rows (#1608)
Fixes #1580
2016-09-30 13:33:05 +02:00
Pierre Donias
91fec43866 feat(pool/network): create a bonded network (#1605)
See #876
2016-09-30 11:23:44 +02:00
Greenkeeper
aa2d196a79 chore(package): update vinyl to version 2.0.0 (#1607)
https://greenkeeper.io/
2016-09-29 23:06:53 +02:00
Pierre Donias
180ca458ad feat(vm/network): allow VIF edition (#1596)
See #1446
2016-09-28 14:29:15 +02:00
Greenkeeper
aa881c60e7 chore(package): update babel-eslint to version 7.0.0 (#1597)
https://greenkeeper.io/
2016-09-27 23:39:03 +02:00
Greenkeeper
5b6966042d standard@8.2.0 breaks build 🚨 (#1595)
https://greenkeeper.io/
2016-09-27 01:11:46 +02:00
Julien Fontanet
dc859da0cd chore(build): remove embedded dev server 2016-09-26 16:45:02 +02:00
Pierre Donias
151eb6cbd6 feat(updates,users,servers): disable credentials autocomplete (#1592)
Fixes #1304
2016-09-26 16:00:04 +02:00
Olivier Lambert
16db591bbf feat(vm): add red icon if VM doesn't have tools. Fixes #1575 2016-09-26 12:24:58 +02:00
Pierre Donias
05a55e5eb2 fix(xoa-upgrade): more suitable message for non-admin users (#1591)
Fixes #1564
2016-09-26 10:31:23 +02:00
Pierre Donias
dcd84b2b8f feat(shortcuts): help modal and new home shortcuts (#1588)
Fixes #1578
2016-09-23 18:43:10 +02:00
Greenkeeper
4a89119f0a Update react-virtualized to version 8.0.8 🚀 (#1587)
https://greenkeeper.io/
2016-09-23 17:11:55 +02:00
Julien Fontanet
bc1c30a7bf chore(package): add __self prop, better React warnings 2016-09-23 16:02:19 +02:00
Julien Fontanet
33cffbf28b fix(Copiable): do not pass tagName prop to wrapper 2016-09-23 15:50:40 +02:00
Julien Fontanet
a18b68116c chore(package): improve React stack traces in dev build 2016-09-23 15:45:03 +02:00
Pierre Donias
d5acf15bca feat(vm/network): indicate when an IP is already used (#1584)
Fixes #1566
2016-09-23 12:13:48 +02:00
Pierre Donias
84f970af68 fix(shortcuts): prevent Shortcuts from stopping events propagation (#1583)
`Editable`s were broken (could not use *enter* to save).
2016-09-23 11:35:56 +02:00
Julien Fontanet
969f636bb7 fix(host/storage): name sorting 2016-09-23 10:54:36 +02:00
Pierre Donias
6939aee20a feat(home): keyboard shortcuts (#1400)
Fixes #1279
2016-09-22 19:01:05 +02:00
Pierre Donias
ab2a02a555 fix(vm/tab-network): lock icon conditions (#1576)
Fixes #1573
2016-09-22 16:52:37 +02:00
Pierre Donias
70038e0764 fix(new-vm): lodash/sum instead of lodash/sumBy (#1577) 2016-09-22 16:19:34 +02:00
Olivier Lambert
e730ef5e11 feat(host/sr): Sr link and better storage tab. (#1572)
Fixes #1567
2016-09-22 16:04:45 +02:00
Olivier Lambert
835ad5aaf1 feat(vm/host/pools/sr): add tooltips. Fixes #1568 2016-09-22 11:02:05 +02:00
Pierre Donias
ac645c8617 fix(home): types dropdown button title (#1570) 2016-09-22 10:47:48 +02:00
Julien Fontanet
b801fdbab2 5.2.2 2016-09-21 18:01:03 +02:00
Pierre Donias
bf495953e2 feat(new-vm): show resource set limits (#1563)
Fixes #1541
2016-09-21 11:58:07 -04:00
Pierre Donias
45b165deec fix(home): VM bulk restart (#1562)
Fixes #1561
2016-09-21 14:48:16 +02:00
Olivier Lambert
09169578e8 feat(changelog): add issue #1562 2016-09-21 14:47:52 +02:00
Olivier Lambert
43b2366927 feat(changelog): update changelog for 5.2.2 2016-09-21 12:22:37 +02:00
Julien Fontanet
f015a69eec feat(host/patches): display if needs to be restarted (#1559)
Fixes #1352
2016-09-21 10:10:44 +02:00
Olivier Lambert
99568508dd fix(charts): change color order to avoid confusions. Fixes #1265 2016-09-20 18:41:44 +02:00
Olivier Lambert
e8515344dd feat(home): display a message if a filter is empty. (#1560)
feat(home): display a message if a filter is empty. Fixes #1517
2016-09-20 12:34:07 -04:00
Julien Fontanet
edc873a570 fix(pool/storage): fix sort by usage 2016-09-20 15:53:45 +02:00
Julien Fontanet
1a03e96ab2 fix(pool/storage): fix passing pool to SortedTable 2016-09-20 15:53:25 +02:00
Olivier Lambert
89e0bb4f0a feat(home/templates): template management (#1533)
Fixes #1091
2016-09-20 13:46:58 +02:00
Olivier Lambert
7d0fd60908 feat(pool/storage): display default SR and add button to set it (#1557)
Fixes #1554
2016-09-20 13:19:47 +02:00
Pierre Donias
6b20523df4 fix(vm/tab-network): check for undefined network (#1556)
Fixes #1518
2016-09-20 11:52:17 +02:00
Julien Fontanet
e9a612647e fix(home): do not overwrite current filter (#1555)
Fixes #1513
2016-09-20 05:38:33 -04:00
Julien Fontanet
28404ef149 feat(SortedTable): default column can be set by a simple prop 2016-09-20 09:43:57 +02:00
Olivier Lambert
a5f8230def feat(self): hide some buttons and tabs for self service VMs (#1550) 2016-09-19 15:17:45 +02:00
Pierre Donias
39171de5de fix(job/new): forbid "_" character in job names (#1548)
Fixes #1414
2016-09-19 11:58:46 +02:00
Pierre Donias
5aa5a0acbc fix(home): set items per page in createPager (#1549) 2016-09-19 11:39:56 +02:00
Olivier Lambert
a4518e630a fix(log): sort logs by end date 2016-09-19 11:04:15 +02:00
Julien Fontanet
94975f5ea6 feat(settings/logs): group action buttons
Fixes #1547
2016-09-19 10:59:19 +02:00
Julien Fontanet
7e98838d96 feat(ActionButton): can have a tooltip 2016-09-19 10:59:19 +02:00
Pierre Donias
e8c9c196ff feat(vm/snapshot): "Snapshot before" checkbox in revert modal (#1543)
Fixes #1445
2016-09-17 00:11:51 +02:00
Julien Fontanet
db314a238f fix(xo/subcriptions): mark as non-running on error 2016-09-16 17:13:56 +02:00
Julien Fontanet
2c85a6d4ab fix(self/admin): do not fail on empty limits
Fixes #1537
2016-09-16 17:13:56 +02:00
Pierre Donias
b683e14e80 feat(self): merge dashboard and administration (#1542)
Fixes #1429
2016-09-16 16:47:20 +02:00
Olivier Lambert
ba45095fa8 fix(editable): limit input size for long text to 50ex. Fixes #1528 2016-09-15 16:45:19 +02:00
Olivier Lambert
b8e5ffa9f7 feat(backup): improved view. Fixes #1534 2016-09-15 16:26:42 +02:00
Pierre Donias
b4bff9e032 feat(vm/disks): "Long click to migrate" tooltip on SR (#1529)
* feat(vm/disks): "Long click to migrate" tooltip on SR

Fixes #1512

* feat(vm/disks): migrate VDI button, force cursor on editable

* fix bulk VDI migration

* Return Promise.all

* Ternary operator
2016-09-15 16:08:50 +02:00
Olivier Lambert
0c461bc4e2 feat(backuplogs): shorten the start/end date format. Fixes #1535 2016-09-15 15:55:01 +02:00
Olivier Lambert
a33b2a5294 fix(home/menu): adapt message depending of user perm (#1532)
Fixes #1519
2016-09-15 13:56:33 +02:00
Olivier Lambert
298e1c4471 fix(host console): removing useless ISO selector for host. Fixes #1527 2016-09-15 13:07:33 +02:00
Julien Fontanet
1c70cdc10b fix(render-xo-item): remove unused import 2016-09-15 13:07:04 +02:00
Julien Fontanet
160e4bb530 fix(SelectVmTemplates): use correct placeholder
Fixes #1530
2016-09-15 12:00:32 +02:00
fufroma
e69ba8dd96 Fix typo on Changelog (#1526) 2016-09-15 04:44:42 -04:00
Julien Fontanet
e55f4c3eb2 feat(settings/logs): show complete error object 2016-09-14 17:42:32 +02:00
Pierre Donias
1a3272b980 feat(new-vm): auto poweron setting (#1514)
Fixes #1444
2016-09-14 15:25:54 +02:00
Julien Fontanet
7bed5e025a fix(home): fix empty default filter (#1504)
Fixes #1354
2016-09-14 14:38:24 +02:00
Pierre Donias
29d22c0598 feat(backup/new): confirm modal when local remote is selected (#1507)
Fixes #1441
2016-09-14 13:20:51 +02:00
Greenkeeper
a38c7c34ac chore(package): update react-key-handler to version 0.3.0 (#1505)
https://greenkeeper.io/
2016-09-14 01:42:16 +02:00
Julien Fontanet
8d690ce4ff 5.2.1 2016-09-13 16:54:42 +02:00
Olivier Lambert
2569568a03 feat(changelog): patch version 5.2.1 changelog 2016-09-13 16:52:47 +02:00
Olivier Lambert
2c6ff6b5b8 feat(stopHost modal): explain consequences if halting a pool master. Fixes #1458 2016-09-13 15:28:30 +02:00
Pierre Donias
1257f01027 feat(new-vm): create tags during VM creation (#1501)
See #1431
2016-09-13 14:17:04 +02:00
Pierre Donias
fad6830863 fix(settings/remotes): SMB domain is required (#1502)
Fixes #1499
2016-09-13 11:28:07 +02:00
Olivier Lambert
66262bb20b fix(vm console): re-fix issue #1485 2016-09-13 11:01:18 +02:00
Julien Fontanet
4abb0754c7 fix(remotes): edition of URL parts (#1500)
Fixes #1498
2016-09-13 10:20:48 +02:00
Pierre Donias
78c53bf3ad fix(xo): better message for snapshot deletion (#1497)
Fixes #1433
2016-09-13 09:42:18 +02:00
Pierre Donias
810d666d84 feat(home): tooltips on bulk action buttons (#1496)
Fixes #1456
2016-09-12 17:04:02 +02:00
Pierre Donias
67699f0bb6 feat(pool/network): SortedTable and collapsable PIFs table (#1494)
Fixes #1461
2016-09-12 16:44:43 +02:00
Pierre Donias
46274948c0 fix(settings/logs): properly handle unknown user (#1495) 2016-09-12 16:42:52 +02:00
Olivier Lambert
28e3a842ef feat(vm): new containers tab (#1492)
Fixes #1442
2016-09-12 13:50:14 +02:00
Pierre Donias
6d90f1d45d fix(user): prevent SSH key overflow (#1491)
Fixes #1475
2016-09-12 12:57:11 +02:00
Pierre Donias
09642c347d feat(new-vm): Advanced section (#1486)
Fixes #1444
2016-09-12 12:27:11 +02:00
Pierre Donias
2d0e06f785 fix(backup/new): better month and week days layout (#1490)
Fixes #1488
2016-09-12 11:58:24 +02:00
Pierre Donias
a5bc8497cf fix(vm/tab-network): set default network and MTU for VIF creation (#1487)
Fixes #1472
2016-09-12 11:26:32 +02:00
Julien Fontanet
4bcb65c518 5.2.0 2016-09-09 16:31:24 +02:00
Olivier Lambert
25361fa7eb feat(changelog): add IP management feature in changelog 2016-09-09 16:30:13 +02:00
Pierre Donias
889a265000 feat(settings/ips): new page to manage IP pools (#1418)
Fixes #1350, fixes #988 and fixes #240.
2016-09-09 16:19:26 +02:00
Olivier Lambert
3122f6dcd5 feat(changelog): update changelog for 5.2.0 2016-09-09 14:21:54 +02:00
Olivier Lambert
16aa2e8085 bug(vmConsole): re-display header if VM goes from running to halted state. Fixes #1485 2016-09-08 16:29:56 +02:00
Julien Fontanet
074d51a670 fix(xo/deleteVms): delete disks as well
Fixes #1484
2016-09-08 15:06:42 +02:00
Greenkeeper
2122a79132 chore(package): update chartist-plugin-legend to version 0.5.0 (#1482)
https://greenkeeper.io/
2016-09-08 11:01:50 +02:00
Ronan Abhamon
26dbc585ba feat(vm-import): supports ova import (#709) (#1361)
Fixes #709
2016-09-08 10:15:44 +02:00
Greenkeeper
4b3cfbd424 chore(package): update modular-css to version 0.27.1 (#1478)
https://greenkeeper.io/
2016-09-08 09:55:38 +02:00
Julien Fontanet
035191a2cc feat(xo/installAllPatchesOnPool): better pool-wide patching (#1476)
Fixes #1392
2016-09-07 17:55:02 +02:00
Olivier Lambert
06a40180a1 fix(vm): do not display VDI or VIF disconnect but when VM is not running. Fixes #1470 and fixes #1468 2016-09-05 16:59:21 +02:00
Julien Fontanet
aaf4c5dff7 chore(Vm): move isRunning into utils/isVmRunning 2016-09-05 16:40:13 +02:00
Olivier Lambert
0c83bc2b0e feat(logview): standardize and improve the settings/log view. Fixes #1467 2016-09-05 16:10:12 +02:00
Julien Fontanet
2d412fd8db fix(scheduling): month and day display
There were issues with some timezones.

Fixes #1404 & fixes #1438.
2016-09-01 11:47:50 -03:00
Julien Fontanet
443e2bec25 chore(NewVm#_getIsoPredicate): memoise selector 2016-09-01 10:57:13 -03:00
Olivier Lambert
d5e1323d82 feat(newVif): select management network by default when adding a vif. Fixes #1425 2016-09-01 15:34:49 +02:00
Julien Fontanet
7f0b77cc89 chore(package): update chartist-plugin-legend to version 0.4.0 (#1450)
https://greenkeeper.io/
2016-08-31 10:32:50 +02:00
greenkeeperio-bot
0169cff66c chore(package): update chartist-plugin-legend to version 0.4.0
https://greenkeeper.io/
2016-08-31 10:17:48 +02:00
Olivier Lambert
0fd1424a41 fix(newVm): check pool object for ISO selector when creating a VM from selfservice. Fixes #1448 2016-08-30 21:26:59 +02:00
Julien Fontanet
6280d56f32 chore(xo): use resolveId() (only) where it makes sense 2016-08-26 16:18:03 -04:00
Julien Fontanet
9f2a77872f fix(xo/deleteUser): dont attempt to display error when cancelled
Fixes vatesfr/xo-web#1439
2016-08-26 14:38:02 -04:00
Pierre Donias
b571c18e9a feat(host): indicate pool master in multiple places (#1423)
Fixes #1407
2016-08-25 12:33:55 -04:00
Greenkeeper
49863d6e4d Update standard to version 8.0.0 🚀 (#1435)
https://greenkeeper.io/
2016-08-24 13:00:00 -04:00
Julien Fontanet
48cc7bb647 5.1.9 2016-08-22 14:02:40 -04:00
Pierre Donias
442d42d8dc fix(settings/logs): show params in a modal (#1424) 2016-08-19 16:09:20 +02:00
Olivier Lambert
9501ebacfc feat(menu): add warning icon when disconnected 2016-08-19 13:52:31 +02:00
Pierre Donias
23f9fa46f8 fix(home/host-item): do not show pool name if not enough permissions (#1421) 2016-08-19 13:40:55 +02:00
Julien Fontanet
1bd0f37fd4 feat(Menu): display when disconnected
Fixes vatesfr/xo-web#1417
2016-08-19 12:27:48 +02:00
Pierre Donias
ed74ded923 feat(settings/logs): display parameters (#1420) 2016-08-19 10:06:45 +02:00
Olivier Lambert
b732410b74 feat(vm and home): add tooltip to OS icon. Fixes #1416 2016-08-18 14:12:46 +02:00
Olivier Lambert
a51f2b7fcf fix(newvm): check if ssh keys object exists 2016-08-18 13:59:08 +02:00
Olivier Lambert
fe12bbb60d fix(sr): container var check if not defined 2016-08-18 13:51:37 +02:00
Olivier Lambert
8882df7939 5.1.8 2016-08-17 11:07:30 +02:00
Olivier Lambert
185a554cd9 fix(newVm): fix wrong ISO SR predicate. Fixes #1415 2016-08-17 11:06:35 +02:00
Olivier Lambert
230e0dc2a5 5.1.7 2016-08-16 15:38:59 +02:00
Pierre Donias
f5b69fdfdc feat(vm/console): hide header and resize console (#1410)
Fix #1268
2016-08-16 14:49:44 +02:00
Greenkeeper
01dc0d8f1e chore(package): update modular-css to version 0.26.0 (#1385)
https://greenkeeper.io/
2016-08-16 12:45:29 +02:00
Greenkeeper
8035886a3c chore(package): update promise-toolbox to version 0.5.0 (#1409)
https://greenkeeper.io/
2016-08-16 12:22:45 +02:00
Olivier Lambert
0ab5f4b13f fix(host): wrong storage link. Fixes #1408 2016-08-16 11:16:56 +02:00
Pierre Donias
a1bc98def8 feat(host): redirect to home when host disappears (#1406) 2016-08-16 09:50:29 +02:00
Olivier Lambert
868cf6140b feat(settings): more tooltips for server connect/disconnect 2016-08-15 18:04:01 +02:00
Olivier Lambert
4b3473f480 feat(logstackmodal): use pre tag for stack trace 2016-08-15 17:52:04 +02:00
Olivier Lambert
7bc782cc62 feat(copiable): add tooltip on copiable component 2016-08-15 17:33:59 +02:00
Olivier Lambert
e625a53e4a fix(vm migration): allow target network without IPs. Fixes #1403 2016-08-15 15:20:59 +02:00
Olivier Lambert
b31185d96d fix(newVm): typo spotted by @Danp2 2016-08-15 14:07:12 +02:00
Olivier Lambert
09d75e972f feat(newVm): add missing tooltips. Fixes #1402 2016-08-15 11:44:36 +02:00
Olivier Lambert
f33568951b 5.1.6 2016-08-12 17:28:49 +02:00
Pierre Donias
8d8c442be5 feat(settings/logs): new view to display API logs (#1401)
Fix #1344
2016-08-12 17:27:50 +02:00
Olivier Lambert
f890b8ea7a feat(modal text): warns users about consequences of host eject 2016-08-11 21:13:32 +02:00
Pierre Donias
1b80b3929c feat(host): detach host from its pool (#1399)
Fixes #1395
2016-08-11 17:49:25 +02:00
Pierre Donias
4f946293f6 feat(pool): add host (#1398)
Fixes #1374
2016-08-11 17:05:41 +02:00
Olivier Lambert
36788cde2b feat(vm disk): add VBD connect for a running VM. Fixes #1397 2016-08-11 16:52:52 +02:00
Pierre Donias
1547c99e5a feat(new-vm): use saved SSH key in cloud config(#1394)
* feat(new-vm): use saved SSH key in cloud config. Fixes #1319
2016-08-11 13:32:54 +02:00
Olivier Lambert
5c9606dad8 feat(pool): improve pool view. Fixes #1393 2016-08-11 10:34:03 +02:00
Olivier Lambert
fdcb1dccf5 feat(pool): start to work on adding an existing host to a pool 2016-08-11 09:47:52 +02:00
Olivier Lambert
12812b8c23 5.1.5 2016-08-10 18:06:19 +02:00
Olivier Lambert
0098497255 fix(select): select color modified due to an update. Fixes #1391 2016-08-10 16:02:48 +02:00
Olivier Lambert
6562d2de7f feat(sr select): display space left on SR. Fixes #1358 2016-08-10 15:58:36 +02:00
Olivier Lambert
1f0e88cdb0 feat(backup): better tooltips. Fixes #1363 2016-08-10 14:17:27 +02:00
Olivier Lambert
197da91ef3 feat(vdi remove): add modal when removing a VDI. Fixes #1388 2016-08-10 13:39:13 +02:00
Olivier Lambert
cbd59789e2 fix(vm disks): _isFreeForWriting missing case. Fixes #1386 2016-08-10 13:13:17 +02:00
Olivier Lambert
190ecf3d74 fix(pool): pool name and description edition. Fixes #1390 2016-08-10 12:42:46 +02:00
Olivier Lambert
15b8f6bca2 feat(meter tooltips): add tooltips for meter object. Fixes #1387 2016-08-10 12:31:28 +02:00
Pierre Donias
5b406d731b fix(vm): select destination SR when at least one VDI is local (#1382)
* Fixes #1357 
* fix(vm): select destination SR when at least one VDI is local
* fix(vm): do not send map when not necessary
2016-08-09 17:03:08 +02:00
Olivier Lambert
4be9e67ac4 fix(metercss): remove useless and conflicting CSS styles 2016-08-09 10:31:03 +02:00
Olivier Lambert
d047421685 feat(updates): enhance update view. Also fixes #1341 2016-08-08 16:46:47 +02:00
Olivier Lambert
f6f415a421 fix(network): name instead of description 2016-08-08 14:55:15 +02:00
Pierre Donias
edfaaebac0 feat(dashboard/health): Storage table: BlockLink (SR) and Link (SR's pool)
Fixes #1381
2016-08-08 14:15:49 +02:00
Olivier Lambert
67df22a1bf feat(vmsnapshot): add snapshot export and copy. Fixes #1353 and #1336 2016-08-08 14:05:27 +02:00
Pierre Donias
7dc59a00f6 feat(pool): action button to create an SR (#1380)
Fixes #1372
2016-08-08 12:45:12 +02:00
Pierre Donias
6214fe4c2e feat(pool): action button to create a VM (#1379)
Fix #1373
2016-08-08 11:35:24 +02:00
Greenkeeper
21610c3e0a chore(package): update ava to version 0.16.0 (#1377)
https://greenkeeper.io/
2016-08-08 09:57:36 +02:00
Olivier Lambert
87550b0189 5.1.4 2016-08-07 19:35:37 +02:00
Ronan Abhamon
b7c42d0a08 fix(scheduling): range not working
Fixes #1376
2016-08-07 19:35:05 +02:00
Olivier Lambert
c15ad299ac fix(sparklines): smaller sparklines and removing useless dots 2016-08-05 14:39:07 +02:00
Olivier Lambert
48c56cd602 5.1.3 2016-08-05 12:42:01 +02:00
Ronan Abhamon
7957f621ef fix(backups-edit): display correctly old backup jobs
Fixes #1366
2016-08-05 12:12:20 +02:00
Olivier Lambert
38ddbfdc9c fix(dashboard): inverted value for SR total/used. Fixes #1370 2016-08-04 14:42:50 +02:00
Olivier Lambert
3d2aae81da 5.1.2 2016-08-03 17:22:49 +02:00
Olivier Lambert
2227b9d061 feat(new vm): hide URL install for HVM templates. Fixes #1362 2016-08-03 16:55:43 +02:00
Olivier Lambert
12aab5fa8c feat(snapshots): add tooltips and confirm modal for snapshot oprations. Fixes #1349 2016-08-03 16:34:31 +02:00
Olivier Lambert
7323e6e117 fix(tooltip): remove tooltip if button changes. Fixes #1360 2016-08-03 15:53:38 +02:00
Greenkeeper
6f36869609 chore(package): update gulp-uglify to version 2.0.0 (#1355)
https://greenkeeper.io/
2016-08-02 14:47:18 +02:00
Ronan Abhamon
4a12419162 feat(backups): supports smart backup (close #837) (#1335)
Fixes #837
2016-07-31 19:03:41 +02:00
ABHAMON Ronan
bf91938aa6 fix(form/Toggle): refresh when set is used (#1347)
Fixes #1339
2016-07-29 11:35:58 +02:00
ABHAMON Ronan
bd70bd2b45 fix(scheduling): fix month selection highlighting (#1345)
Fixes #1338
2016-07-29 10:31:42 +02:00
Greenkeeper
bb26c8e449 chore(package): update modular-css to version 0.25.0 (#1331)
https://greenkeeper.io/
2016-07-28 00:16:06 +02:00
Julien Fontanet
93c7a01e62 5.1.1 2016-07-27 15:31:00 +02:00
Julien Fontanet
9c2359e8ee fix(Tooltip): better PropTypes 2016-07-27 15:29:05 +02:00
Julien Fontanet
5b9000012e fix(Tooltip): behave if children is missing or a string 2016-07-27 15:17:02 +02:00
Julien Fontanet
bf00b4e8e3 fix(Tooltip): better PropTypes 2016-07-27 15:08:52 +02:00
ABHAMON Ronan
ee7787f4ae fix(heatmap): related to last Tooltip changes (#1327)
Fixes #1326
2016-07-27 10:10:55 +02:00
Olivier Lambert
0b88e743c9 feat(changelog): update changelog 2016-07-26 17:07:23 +02:00
Julien Fontanet
f07a947580 5.1.0 2016-07-26 16:54:35 +02:00
Julien Fontanet
0b8a9eedbc feat(tooltip): float → solid, do not follow cursor 2016-07-26 16:50:46 +02:00
ABHAMON Ronan
8d24e596ac fix(tooltip): use position.top instead of position.right (#1322) 2016-07-26 14:49:07 +02:00
ABHAMON Ronan
c2378a44cd fix(tooltip): do not inject an intermediary element (#1321)
Fixes #1150
2016-07-26 14:28:11 +02:00
ABHAMON Ronan
023f7fdef1 feat(home): custom filters & configure default filters (#1308)
Fixes #1235
2016-07-25 15:20:39 +02:00
ABHAMON Ronan
5d7a64bc28 fix(scheduling): timezone support (#1318) 2016-07-25 14:57:38 +02:00
ABHAMON Ronan
8661957a97 feat(timezone-picker): xo-server timezone in the select (#1316)
Fixes #1314
2016-07-25 13:21:37 +02:00
ABHAMON Ronan
7a15d265b7 fix(new/sr): fix IQNs, LUNs selection (#1317)
Fixes #1281
2016-07-25 13:04:05 +02:00
Olivier Lambert
2736881975 fix(new sr): cast port number. See issue #1281 2016-07-23 16:42:58 +02:00
Greenkeeper
44a85f4e0c chore(package): update globby to version 6.0.0 (#1313)
https://greenkeeper.io/
2016-07-23 16:41:41 +02:00
Julien Fontanet
52a6e42e7e fix(pool/storage): display read-only SRs 2016-07-23 16:26:41 +02:00
Julien Fontanet
3dbe058d4e feat(home): add link to VMs console 2016-07-23 15:58:12 +02:00
Pierre Donias
620139efc1 feat(settings/acls): (un)select all objects of a specific type (#1310)
Fixes #1296
2016-07-22 17:45:38 +02:00
Pierre Donias
71464ac2e3 feat(menu): add types as Home sub-menus (#1309)
Fixes #1306
2016-07-22 16:18:16 +02:00
Pierre Donias
4a65489d39 fix(xo): polyfill Intl for Safari (#1307)
Fixes #1120
2016-07-22 15:51:32 +02:00
Pierre Donias
65d7eac590 feat(user): SSH keys management (#1302)
Fix #1299
2016-07-21 12:21:27 +02:00
ABHAMON Ronan
02bbc01dc4 feat(scheduling): improve utilisability (#1300)
Fixes #1295
2016-07-21 10:25:57 +02:00
Pierre Donias
3066237c86 feat(self/admin): recompute resource sets limits (#1298)
Fixes #1287
2016-07-20 11:36:49 +02:00
Pierre Donias
53f3c0bef1 fix(new-vm): fix CPU weight and add CPU cap (#1297)
Fixes #1286
2016-07-20 10:41:50 +02:00
ABHAMON Ronan
823c91b457 feat(plugins): supports predefined configurations (#1294)
Fixes #1289
2016-07-20 09:46:30 +02:00
ABHAMON Ronan
3bd7e20411 feat(backups): jobs support timezones (#1290)
Fixes #1258
2016-07-20 09:45:35 +02:00
Pierre Donias
24d4610b04 feat(vm/tab-advanced): editable CPU weight and cap (#1293)
Fixes #1283
2016-07-20 09:44:24 +02:00
ABHAMON Ronan
b16097767a feat(json-schema-input): use only schema.defaults in combobox options (#1292)
Fix #1288
2016-07-19 15:06:33 +02:00
ABHAMON Ronan
2ff74ffd39 feat(line-chart): many fixes on graphs legends (#1291)
Fixes #1247
2016-07-19 13:39:53 +02:00
Julien Fontanet
f0bb464136 fix(intl/locales/zh): fix moment import 2016-07-19 10:51:56 +02:00
Julien Fontanet
4767830386 feat(i18n): skeleton for Chinese 2016-07-19 10:02:33 +02:00
Julien Fontanet
ce23d4f164 feat(editable): change cursor to make it easier to see 2016-07-19 09:40:29 +02:00
Pierre Donias
c1380d1256 feat(home): focus search input after changing type (#1285)
Fixes #1228
2016-07-18 17:51:47 +02:00
Pierre Donias
ed9a848858 feat(new-vm): create mutiple VMs with a name pattern (#1271)
Implements parts of #949: initial sequence number.
2016-07-18 14:42:18 +02:00
ABHAMON Ronan
5e4e15fc12 fix(self/overview): display correctly resources set (#1284)
Fixes #1282
2016-07-18 09:36:46 +02:00
Greenkeeper
0dea952a2a chore(package): update modular-css to version 0.23.2 (#1239)
https://greenkeeper.io/
2016-07-15 12:19:47 +02:00
Olivier Lambert
a1818dd525 5.0.9 2016-07-14 14:49:18 +02:00
Pierre Donias
659e336f66 fix(migrate-vms-modal): fix conditions for SR and Network selectors 2016-07-14 14:43:29 +02:00
Pierre Donias
058f7ecd9f feat(Usage): new component to display a usage progress bar
Fixes #1151
2016-07-13 12:50:29 +02:00
Olivier Lambert
831d9cb49f feat(i18n): Portuguese translation
* Translation PT-BR Reviewed and Updated
2016-07-13 11:57:06 +02:00
Olivier Lambert
a5d059b0b1 fix(vm): protect JS crash if arrays[0] is empty 2016-07-13 11:43:52 +02:00
Pierre Donias
4c3b959869 fix(home): add key prop to sort options 2016-07-13 09:59:59 +02:00
Pierre Donias
d81a169a39 fix(form/SizeInput): parseSize value cannot be undefined 2016-07-13 09:59:44 +02:00
Pierre Donias
0d47332526 feat(new-vm): self service with resource sets
Fixes #1155
2016-07-13 09:59:11 +02:00
Pierre Donias
539d136936 fix(page/title): check if the container exists before displaying its name in the title 2016-07-13 09:30:58 +02:00
Pierre Donias
4c28b5775d feat(page/title): page titles for each view
Fixed #1185
2016-07-11 17:33:00 +02:00
Pierre Donias
fe6f351f84 feat(new/sr): page header
Fixes #1129
2016-07-11 17:10:27 +02:00
Olivier Lambert
5dbeccf92f 5.0.8 2016-07-08 19:29:54 +02:00
Olivier Lambert
56bba1d84b fix(home): typo on host memoryTotal 2016-07-08 16:22:11 +02:00
ABHAMON Ronan
af05d362b4 fix(stats): avoid calculations on null stats objects
Fix #969
2016-07-08 13:53:25 +02:00
ABHAMON Ronan
268ccf9a36 feat(settings/plugins): presets are supported
Fix #1222
2016-07-08 12:50:45 +02:00
ABHAMON Ronan
e77d4fafaa feat(patches): fix all patches related issues
Fixes #1244, #1245 and #1246
2016-07-08 12:30:32 +02:00
Pierre Donias
b88b99e342 fix(new-vm): display fast clone option only if there are template disks
Fix #1172
2016-07-08 12:20:28 +02:00
Olivier Lambert
f862d0df5b fix(host,vm): use stacked value text in a tooltip for stats 2016-07-08 10:40:15 +02:00
Fabrice Marsaud
dac954155c feat(menu): better contrast and size for update icons 2016-07-07 17:23:12 +02:00
Fabrice Marsaud
cf9deceb15 fix(logs): displays job log message if available 2016-07-07 15:22:43 +02:00
Fabrice Marsaud
72aed98088 Filter errors and successes in job logs (#1242)
* Meta data for job log state

* Preset filters for sorted tables

* Work around a babel issue

Fixes #1232
2016-07-07 14:28:09 +02:00
Fabrice Marsaud
ec92eddde8 display log errors properly (#1248)
Fixes #1134
2016-07-07 14:25:14 +02:00
ABHAMON Ronan
e30b5ab6c3 fix(xo-app): use correct sortOrder on sortedTable instances (#1243) 2016-07-07 14:20:54 +02:00
Olivier Lambert
0a5d26b001 5.0.7 2016-07-07 12:14:37 +02:00
ABHAMON Ronan
7e4b881041 feat(srs/uuid): UI improvements (fix #1142) (#1230) 2016-07-07 11:23:37 +02:00
Fabrice Marsaud
27a6af414f fix(host): broken patch filter
Fixed #1236
2016-07-07 11:20:43 +02:00
Olivier Lambert
ba6204f811 feat(user): add Spanish in the language selector 2016-07-06 17:09:16 +02:00
Olivier Lambert
d17b1050ad feat(es): add i18n file for Spanish 2016-07-06 17:05:45 +02:00
Olivier Lambert
b70bc86f71 feat(plugins): compact plugin view. Fixes #1130 2016-07-06 15:14:13 +02:00
Olivier Lambert
42b08633e9 fix(host): correctly display XS version. Fixes #1225 2016-07-06 12:45:20 +02:00
ABHAMON Ronan
bc898e1afd fix(pools): use sorted table & links to srs/hosts (fix #1141) (#1219)
* fix(pools): use sorted table & links to srs/hosts (fix #1141)
2016-07-06 10:45:48 +02:00
Pierre Donias
48d5f34ae6 fix(new-vm): wrong conditions for creation disabling (#1224) 2016-07-06 10:07:07 +02:00
Olivier Lambert
67b8b15cd8 5.0.6 2016-07-05 19:14:36 +02:00
Pierre Donias
09d80afa69 fix(form/SizeInput): controlled & uncontrolled modes (#1220) 2016-07-05 19:06:28 +02:00
ABHAMON Ronan
c0d95304f6 fix(home): actions depend on the selected type (#1218)
Fix #1153
2016-07-05 18:14:37 +02:00
Olivier Lambert
5a0d67a9f6 feat(vm): allow halted VM migration (#1221)
Fixes #1216
2016-07-05 17:55:49 +02:00
Julien Fontanet
08305b4b93 feat(SortedTable): support rowLink prop 2016-07-05 17:04:55 +02:00
Olivier Lambert
04d5612946 chore(xo): more resolveId (#1217) 2016-07-05 14:41:10 +02:00
Olivier Lambert
3dcb6f1f61 fix(host patch): wrong version check for patching hosts 2016-07-05 13:26:59 +02:00
Olivier Lambert
4e7684e38b feat(actions): add confirms for critical actions (#1215)
Fixes #1211
2016-07-05 13:24:04 +02:00
Fabrice Marsaud
a692b7571f feat(logs): sortable (#1214) 2016-07-05 12:30:03 +02:00
ABHAMON Ronan
a098618efa feat(dashboard/overview): add links to related pages (#1212)
Fixes #1139
2016-07-05 11:10:58 +02:00
Pierre Donias
71381e75f1 fix(migration): intra-pool migration should not send maps (#1213) 2016-07-05 11:10:15 +02:00
Pierre Donias
05b345db4a feat(home): bulk VM copy (#1205)
Fixes #1154.
2016-07-05 10:33:30 +02:00
Julien Fontanet
f85f6eab9e 5.0.5 2016-07-04 19:04:36 +02:00
Julien Fontanet
b6dc8b507d fix(SortedTable): total number of items when collection is a map 2016-07-04 19:03:31 +02:00
ABHAMON Ronan
831308ee05 feat(pool/patches): can patches hosts (#1203)
Fixes #1149
2016-07-04 18:52:39 +02:00
Fabrice Marsaud
eb5bcb759f feat(home): can display pools (#1202)
Fixes #1140
2016-07-04 16:28:41 +02:00
ABHAMON Ronan
8286570811 feat(xo-line-charts): supports sum of stats series (#1197)
Fixes #1158
2016-07-04 16:27:26 +02:00
Pierre Donias
10b511f0ed fix(form/SizeInput): props.defaultValue instead of props.value (#1206) 2016-07-04 15:56:57 +02:00
Julien Fontanet
751e335bc0 5.0.4 2016-07-01 17:53:47 +02:00
Pierre Donias
cb107521f2 feat(migration): smart VIFs-networks mapping (#1195) 2016-07-01 17:10:57 +02:00
Olivier Lambert
e56af57b74 fix(card): minor style change 2016-07-01 17:09:10 +02:00
ABHAMON Ronan
a2a1cbab6e feat(sorted-table): display selected items number (#1200)
Fixes #1133
2016-07-01 17:07:57 +02:00
Pierre Donias
306a021a8d fix(new-vm): make radio buttons controlled (#1199)
Fixes #1198
2016-07-01 16:11:19 +02:00
Julien Fontanet
d8c414af2f fix(getEventValue): returns value prop for radios 2016-07-01 15:47:46 +02:00
Julien Fontanet
ec4c76b2e0 feat(selectors): filter() can be chained after pick() 2016-07-01 15:47:46 +02:00
ABHAMON Ronan
e23b8a6891 feat(dashboard/overview): display missing patches (#1191)
Fixes #1148
2016-07-01 15:28:07 +02:00
Olivier Lambert
34006bcbf6 fix(health): forget to check edition 2016-07-01 14:47:43 +02:00
ABHAMON Ronan
ed9aeabf6a chore(dashboard/health): use Card (#1194) 2016-07-01 11:20:32 +02:00
Fabrice Marsaud
799fc5089f feat(settings/remotes): add button to test a remote (#1192)
Fixes #1075.
2016-07-01 09:25:57 +02:00
Julien Fontanet
683d510aa6 5.0.3 2016-06-30 15:12:26 +02:00
Pierre Donias
ebd7e58f61 feat(home): bulk VM migration (#1187)
Fixes #1146
2016-06-30 15:04:21 +02:00
Fabrice Marsaud
9a498b54ac fix(menu): only display one icon for updates when collapsed (#1190)
Fixes #1188
2016-06-30 15:03:26 +02:00
ABHAMON Ronan
2687f45e6e fix(settings/plugins): set config value to undefined if value is null (#1189) 2016-06-30 14:31:43 +02:00
ABHAMON Ronan
f79a17fcec feat(json-schema-input): generate uiSchema JSON schema (#1182) 2016-06-30 13:52:20 +02:00
ABHAMON Ronan
8fd377d1e2 feat(dashboard/dataviz): parallel coordinates graph (#1174)
Fixes #1157
2016-06-30 11:36:29 +02:00
Olivier Lambert
fda06fbd29 feat(VM/network): VIFs management (#1186)
Fixes #1176
2016-06-30 11:28:25 +02:00
Fabrice Marsaud
cee4378e6d feat(xoa-updates): reload after upgrading (#1183)
Fixes #1131.
2016-06-30 11:26:03 +02:00
Fabrice Marsaud
ab6d342886 fix(VM/network): fix broken propTypes import (#1184) 2016-06-29 17:59:39 +02:00
Fabrice Marsaud
9954c08993 fix(xoa-updates): fix env test (#1181) 2016-06-29 12:16:25 +02:00
Julien Fontanet
3ae80aeab3 feat(link): expose Link and BlockLink components 2016-06-29 11:57:42 +02:00
Julien Fontanet
2a3534f659 chore(utils): do not re-export propTypes 2016-06-29 11:57:42 +02:00
Julien Fontanet
fc39de0d5a chore(sign-in): remove because unused 2016-06-29 11:57:41 +02:00
Julien Fontanet
64e4b79d41 chore(utils/createSimpleMatcher): remove because not used 2016-06-29 11:57:41 +02:00
Fabrice Marsaud
53887da3da feat(VM/network): VIF creation (#1173)
Fixes #1138.
2016-06-28 17:47:44 +02:00
ABHAMON Ronan
7c60d68f56 fix(xo-line-chart): set precision on LoadLineChart (#1175)
Fixes #1167
2016-06-28 17:17:46 +02:00
Julien Fontanet
2ac1b991b1 feat(BaseComponent#_linkedState): only allocate when necessary 2016-06-28 15:56:53 +02:00
Julien Fontanet
8257714cdb feat(get-event-value): works with checkbox/radio/select 2016-06-28 15:56:53 +02:00
Julien Fontanet
1b8bacbf5a chore(utils/autobind): remove in favor of ES7 class properties syntax 2016-06-28 15:56:52 +02:00
Julien Fontanet
1d5b84389d chore(utils): do not re-export invoke 2016-06-28 15:56:51 +02:00
Julien Fontanet
f7dcf52977 chore(utils/If): remove because does not work 2016-06-28 15:03:34 +02:00
Julien Fontanet
e26dd5147a feat(BaseComponent#linkState): creates a callback associated to a state entry 2016-06-28 14:56:51 +02:00
Julien Fontanet
bb8f96c2e2 5.0.2 2016-06-28 14:38:13 +02:00
Julien Fontanet
95d4cc9055 chore(README): master → stable 2016-06-28 14:37:14 +02:00
Julien Fontanet
cb84a85f8b chore(package): make package publishable 2016-06-28 14:36:32 +02:00
Pierre Donias
0a8aa2ecf5 feat(user): better UI and password edition (#1165)
Fixes #1127
2016-06-28 14:32:02 +02:00
Julien Fontanet
5941321e84 fix(intl/locales): Spanish is es, not sp 2016-06-28 14:15:11 +02:00
Julien Fontanet
8cf62280f4 feat(intl/locales/sp): initial file 2016-06-28 14:13:21 +02:00
Olivier Lambert
4cea142b57 fix(tasks): improve the task view (#1166)
Fixes #1147
2016-06-28 12:31:25 +02:00
Fabrice Marsaud
64d9245bc4 fix(settings/users): correctly set default permission value (#1170)
Fixes #1159
2016-06-28 11:49:04 +02:00
Fabrice Marsaud
2d78c0c4c3 fix(backup/restore): ignore incorrectly formatted files (#1163)
Fixes #1164
2016-06-28 11:19:54 +02:00
Pierre Donias
aa585e2d25 fix(home): always use advanced migration modal (#1137) 2016-06-27 16:15:52 +02:00
Julien Fontanet
325ab17dcc chore(xo): prefix local function call with _ 2016-06-27 16:04:54 +02:00
Pierre Donias
443ea44bcd fix(new/vm): default custom cloud config (#1125) 2016-06-27 15:21:20 +02:00
Pierre Donias
07958d8efa fix(vms/new): gracefully handle missing objects (#1124) 2016-06-27 15:20:05 +02:00
Olivier Lambert
f19affe599 fix: better SR predicate (#1122) 2016-06-27 15:15:46 +02:00
Julien Fontanet
f7b7c27b6c fix(host/storage): use long clicks for SR name edition 2016-06-25 09:10:22 +02:00
Julien Fontanet
c7af5b384c fix(xo/editSr): use camel case param
Fixes #1116
2016-06-25 09:09:12 +02:00
Olivier Lambert
436a9dfc14 5.0.1 2016-06-25 07:09:43 +02:00
Olivier Lambert
1d6d8ccb28 fix(ACLs): are available from Enterprise (#1117) 2016-06-25 01:09:06 +02:00
Julien Fontanet
7d0862ecfd fix(disclaimer): only from sources (#1119) 2016-06-25 01:08:18 +02:00
Julien Fontanet
7de059919b Merge branch 'v5.x' into next-release 2016-06-24 18:22:54 +02:00
Julien Fontanet
dfd1fb86cb chore(tab-button): inline props 2016-06-24 14:05:15 +02:00
ABHAMON Ronan
847a92433f fix(host/storage): use TabButtonLink instead of Link (#1113) 2016-06-24 14:04:02 +02:00
Pierre Donias
53af4df47b feat(vms/new): fixes and multiple VMs creation (#1098) 2016-06-24 14:02:24 +02:00
Olivier Lambert
09db7c999e fix(he i18n): fix missing space 2016-06-24 14:01:50 +02:00
Denis Kalitviansky
1b4c958aba feat(i18n): Hebrew (#1112) 2016-06-24 13:44:05 +02:00
Fabrice Marsaud
9368d5df01 fix(backup/edit) (#1110) 2016-06-24 13:42:46 +02:00
Olivier Lambert
f3b5026190 fix(ACLs): update views to behave with missing objects (#1111) 2016-06-24 12:53:18 +02:00
ricardovilarinho
19dcd81639 feat(i18n): more Portugese (#1106) 2016-06-24 12:46:37 +02:00
Julien Fontanet
d38c171151 fix(home): add missing key prop 2016-06-24 11:53:32 +02:00
Julien Fontanet
af3049925f fix(SingleLineRow): behave with falsy children 2016-06-24 11:46:39 +02:00
Julien Fontanet
a79825d18c feat(menu): add some entries to non-admins 2016-06-24 11:20:22 +02:00
Olivier Lambert
c4b456b470 fix(home): broken OpenSource modal due to bad intl import 2016-06-24 11:15:09 +02:00
Julien Fontanet
ccdf28767a fix(build): various issues
- missing assets with npm run build
- some files had source maps even though it did not make sense
2016-06-24 11:05:14 +02:00
ABHAMON Ronan
2561f7d793 fix(home/storage): "add a storage" link style & label (#1109) 2016-06-24 10:52:36 +02:00
Fabrice Marsaud
57bd8c1a49 fix(settings/users): permission when creating user (#1108) 2016-06-24 09:58:38 +02:00
Julien Fontanet
8387e4ae04 chore(intl): remove unused messages 2016-06-23 20:03:41 +02:00
Julien Fontanet
5c02935017 fix(user): correctly select current lang 2016-06-23 20:01:56 +02:00
Julien Fontanet
726ffb9b1b feat(store): save lang in cookie 2016-06-23 19:59:18 +02:00
Olivier Lambert
5dcc3f4076 fix(i18n): various mistakes 2016-06-23 19:27:35 +02:00
Fabrice Marsaud
4639d7872f feat(backup/restore): translation (#1105) 2016-06-23 17:52:26 +02:00
Pierre Donias
71cb6af8c4 fix(intl): confirmOK, confirmCancel and alertOk (#1104) 2016-06-23 17:46:28 +02:00
Fabrice Marsaud
52060301bd feat(logs): use SortedTable (#1099) 2016-06-23 17:43:14 +02:00
Olivier Lambert
dfa3e6d8e4 feat(changelog): adding changelog file and changes for 5.0.0 2016-06-23 17:41:42 +02:00
ABHAMON Ronan
f38f3fe5c9 fix(self): message if no resource sets (#1100) 2016-06-23 17:13:47 +02:00
Fabrice Marsaud
24bf031270 feat(menu): add updater status icon (#1103) 2016-06-23 17:11:25 +02:00
ABHAMON Ronan
eeadd72e1f feat(host/storage): add button redirect to new/sr page (#1097) 2016-06-23 16:45:36 +02:00
ricardovilarinho
e4139bab04 Initial work on portuguese translation (#1102) 2016-06-23 16:34:50 +02:00
Fabrice Marsaud
7c7205849b feat(backup/restore): display number of backups per VM (#1101) 2016-06-23 16:24:36 +02:00
ABHAMON Ronan
03b2b13f14 feat(dashboard/stats): add weekly charts (#1093) 2016-06-23 15:53:44 +02:00
Olivier Lambert
8caf9f7fde feat(locales): add i18n PT and HE files 2016-06-23 15:50:19 +02:00
Julien Fontanet
5b8a5ac6b6 fix(create-locale): remove trailing comma 2016-06-23 15:43:34 +02:00
Julien Fontanet
4429bed1cf fix(intl/messages): xenToolsStatusValue description 2016-06-23 15:25:12 +02:00
Julien Fontanet
b9beda3484 fix(intl): export messages 2016-06-23 15:21:31 +02:00
Julien Fontanet
354c9bc927 feat(create-locale): tool to scaffold a new locale 2016-06-23 15:06:28 +02:00
Julien Fontanet
a2d88f7fbf chore(intl): split messages data 2016-06-23 15:05:53 +02:00
Olivier Lambert
83cad000e7 feat(job new): i18n 2016-06-23 12:30:29 +02:00
Fabrice Marsaud
1b78791aa9 feat(groups,users): UI improvements (#1094) 2016-06-23 12:20:17 +02:00
Olivier Lambert
2b05fbf6a0 feat(pool network): i18n 2016-06-23 12:17:59 +02:00
Olivier Lambert
7b677cddaf feat(jobs): xoa plans for jobs feature 2016-06-23 11:03:03 +02:00
Olivier Lambert
18a8fcaa70 feat(newSr,pool,updates): i18n 2016-06-23 10:46:27 +02:00
ABHAMON Ronan
dd1bc757d5 fix(SortedTable): better pagination/filter alignment (#1095) 2016-06-23 10:44:35 +02:00
Olivier Lambert
27ca0fdfcc fix(home): broken link for new VM button 2016-06-22 17:46:30 +02:00
Olivier Lambert
578de05a40 feat(logs): i18n 2016-06-22 17:38:31 +02:00
Olivier Lambert
cd3e1d6bd4 feat(job): i18n 2016-06-22 17:23:37 +02:00
Julien Fontanet
de160bb51b feat(xo): make all calls wait for authentication 2016-06-22 17:12:56 +02:00
ABHAMON Ronan
14417e14c0 fix(vm/snapshots): use ButtonGroup with action buttons (#1096) 2016-06-22 17:02:32 +02:00
Olivier Lambert
f2d8b4e444 feat(dashboard): i18n 2016-06-22 16:55:47 +02:00
Olivier Lambert
b7c41fee28 feat(restore backup): i18n 2016-06-22 16:35:15 +02:00
Olivier Lambert
4f0678d6a2 fix(new backup): forgotten i18n for reset word 2016-06-22 15:41:25 +02:00
Olivier Lambert
880c624935 feat(xoa-upgrade): i18n 2016-06-22 15:22:17 +02:00
Olivier Lambert
0fa0902262 feat(about): i18n 2016-06-22 15:12:07 +02:00
Fabrice Marsaud
a2ab3ccaee feat(jobs/scheduling) (#1032) 2016-06-22 14:51:37 +02:00
Fabrice Marsaud
77a0d1c2ff feat(settings/acls): use SortedTable (#1090) 2016-06-22 14:26:30 +02:00
Olivier Lambert
7fdb022819 feat(backup restore): more explicit restore operation 2016-06-22 12:02:04 +02:00
Fabrice Marsaud
878a630b69 feat(backup/restore): better UI (#1081) 2016-06-22 11:41:50 +02:00
Pierre Donias
fbcfc69983 feat(host,pool): network management (#1084) 2016-06-22 10:01:51 +02:00
Olivier Lambert
a1bd327524 feat(about): hide xo version numbers for non admin users. Fix #877 2016-06-21 17:42:38 +02:00
Olivier Lambert
e62829debd fix(new-vm): using 'state' and not 'this' for SSH cloudConfig 2016-06-21 16:51:00 +02:00
Julien Fontanet
d9d669964f fix(xo/attachDiskToVm): camel casing and params order 2016-06-21 15:36:02 +02:00
Julien Fontanet
ced17b632a fix(editable/Text): do not add all props to the input element 2016-06-21 14:55:16 +02:00
Olivier Lambert
0aada62a5a feat(host,vm): use SortedTable for VM and host logs 2016-06-21 14:01:49 +02:00
Julien Fontanet
2fece7a8fe perf(selectors/createGetObjectMessages): better input dependency 2016-06-21 13:52:07 +02:00
Julien Fontanet
6680373c76 fix(VM/Logs): use correct selector creator 2016-06-21 13:52:07 +02:00
Olivier Lambert
68ae43fd72 feat(vm): add ISO disk drive selector in VM disk view 2016-06-21 13:40:47 +02:00
Julien Fontanet
6b6f452d06 feat(VM/Advanced): can edit number of current CPUs 2016-06-21 12:13:20 +02:00
Greenkeeper
7153ff17e8 chore(package): update modular-css to version 0.22.1 (#1087)
https://greenkeeper.io/
2016-06-21 09:16:12 +02:00
Pierre Donias
0fde5a1b3d fix(new-vm): network instead of $network (#1085) 2016-06-20 17:17:08 +02:00
Julien Fontanet
b17fbdd19b fix(SortedTable): correctly pass a selector to createFilter. 2016-06-20 17:05:23 +02:00
Julien Fontanet
61ae522486 chore(xo): remove test function plop 2016-06-20 17:05:23 +02:00
Olivier Lambert
bd414ae9f2 fix(new-vm): send VDI id for ISO, stringify device for VDI 2016-06-20 15:36:59 +02:00
Olivier Lambert
7579db5876 feat(host): use SortedTable for patches (#1083) 2016-06-20 14:21:20 +02:00
Julien Fontanet
994ce8dab2 fix(log-error): add missing file 2016-06-20 13:12:46 +02:00
Julien Fontanet
e8a84dce7d chore(xo/purgePluginConfiguration): remove unused try/catch. 2016-06-20 13:11:24 +02:00
Julien Fontanet
fdca9eda90 fix(xo): display next connection attempt as warning 2016-06-20 13:11:24 +02:00
Julien Fontanet
e007009a00 feat(xo): logs all call errors 2016-06-20 13:11:24 +02:00
Julien Fontanet
1d5cc209dd feat(log-error): properly display an error in the console 2016-06-20 13:11:24 +02:00
Pierre Donias
09b18e1563 fix(vms/new): Display VDIs instead of SRs in ISO/DVD selector (#1082) 2016-06-20 09:53:00 +02:00
Fabrice Marsaud
363db0edea Create or attach disk and a VM, and change boot order with Drag&Drop (#1067)
* feat(vm): add & attach disks, boot order

* chore(vm): fix issues and use correct selectors predicates for SR/disks
2016-06-17 18:53:06 +02:00
Olivier Lambert
e500240a35 feat(visualizations): add a coming soon message 2016-06-17 17:48:46 +02:00
Pierre Donias
6694977b87 fix(form/Select): set a minimum width (#1080) 2016-06-17 15:23:45 +02:00
Pierre Donias
b173dc1f28 fix(Page): fix height (#1079) 2016-06-17 14:56:05 +02:00
Pierre Donias
aa91f5649f fix(vms/new): text field to set the cloud configuration (#1077) 2016-06-17 11:59:06 +02:00
ABHAMON Ronan
74efd563ab feat(xo-week-heatmap) (#1064) 2016-06-17 11:27:15 +02:00
Pierre Donias
b0b389fb4d fix(dashboard/overview): lint (#1078) 2016-06-17 11:26:32 +02:00
Pierre Donias
e2d9131a07 feat(react-novnc): support clipboard (#1076) 2016-06-16 17:43:23 +02:00
Olivier Lambert
3b1c8216b9 feat(home): more info density on large screens 2016-06-16 17:00:03 +02:00
Julien Fontanet
7f259a43cf fix(home): handle disappearing filter during life 2016-06-16 15:28:32 +02:00
Julien Fontanet
874a504df3 feat(home): can display hosts 2016-06-16 15:09:08 +02:00
Julien Fontanet
6a4c6318e3 fix(form/Toggle): controlled/uncontrolled issues 2016-06-16 11:06:14 +02:00
Julien Fontanet
09bf2b87dc feat(form/Toggle): move into its own module 2016-06-16 11:05:36 +02:00
Olivier Lambert
9c5c9838ae chose(vm,host): use value for toggle 2016-06-16 10:47:15 +02:00
Olivier Lambert
3924033d9a feat(settings): read-only for server connection (#1074) 2016-06-15 18:00:31 +02:00
ABHAMON Ronan
d81e45e456 feat(IsoDevice) (#1071) 2016-06-15 17:42:15 +02:00
Pierre Donias
631a762b56 feat(host/tab-storage): each row of the table is a link to the storage (#1073) 2016-06-15 17:10:35 +02:00
Julien Fontanet
a9cf79942f feat(form/Toggle): can be used as a controlled component 2016-06-15 17:03:06 +02:00
Julien Fontanet
bd31476933 fix(Tooltip): fix import 2016-06-15 17:00:51 +02:00
Julien Fontanet
9b9e4c2ffa fix(select-objects): remove unused import 2016-06-15 16:44:48 +02:00
Julien Fontanet
ec93daac7e feat(Tooltip): accept a className prop 2016-06-15 16:08:03 +02:00
Julien Fontanet
3431b2dfb1 fix(Tooltip): stricter propTypes for children prop 2016-06-15 16:05:45 +02:00
Julien Fontanet
4270abaf1c feat(Tooltip): children prop is optional 2016-06-15 16:05:45 +02:00
Julien Fontanet
0bd288afbd feat(Tooltip): support a tagName prop 2016-06-15 16:05:45 +02:00
Julien Fontanet
b72d5d50a1 feat(select-objects/SelectTag): accept an objects prop 2016-06-15 16:05:45 +02:00
Pierre Donias
d51889c233 feat(utils/BlockLink): Ctrl-click or middle mouse click to open in new tab (#1070) 2016-06-15 14:29:29 +02:00
Olivier Lambert
332b093ee9 feat(copyVM): include Edition check 2016-06-15 13:10:53 +02:00
Pierre Donias
8be332208f feat(xo): copyVm() (#1069) 2016-06-15 11:51:35 +02:00
ABHAMON Ronan
85d1188628 feat(react-novnc): can send Ctrl+Alt+Del (#1068) 2016-06-15 11:28:44 +02:00
Julien Fontanet
56896996c3 feat(selectors/createGetObjectsOfType): type can be a selector 2016-06-15 10:32:21 +02:00
Julien Fontanet
896374e069 feat(selectors/createSortForType): type can be a selector 2016-06-15 10:32:21 +02:00
Julien Fontanet
ac36505fb2 fix(home): remove extranous space in filter 2016-06-15 10:32:21 +02:00
Julien Fontanet
d36df1a8ae chore(home): move VmItem in its own module 2016-06-15 10:32:21 +02:00
ABHAMON Ronan
edd939c069 feat(react-novnc): auto reconnect (#1065) 2016-06-15 10:04:41 +02:00
Olivier Lambert
f80225ba54 feat(updates): current version in updater view 2016-06-14 18:48:39 +02:00
Olivier Lambert
3ccd87b369 fix(self): use formatSize for human readable disk and RAM available 2016-06-14 18:08:32 +02:00
ABHAMON Ronan
59d3dd9255 feat(vm): VM export (#1066) 2016-06-14 17:27:57 +02:00
Pierre Donias
392f08059d feat(vms/new): VM creation page (#1058) 2016-06-14 17:05:10 +02:00
Julien Fontanet
0d5c9a2bba feat(home): connect pools/hosts/tags selects 2016-06-14 16:39:10 +02:00
Julien Fontanet
f27de8015b feat(complex-matcher): -addPropertyClause, +getPropertyClausesStrings, +setPropertyClause 2016-06-14 16:39:10 +02:00
Julien Fontanet
3b7bdee814 fix(complex-matcher/parse): do not fail on empty input 2016-06-14 16:39:10 +02:00
Julien Fontanet
397ed9d581 feat(complex-matcher): $ char is allowed in raw strings 2016-06-14 16:39:10 +02:00
Julien Fontanet
a098880669 fix(selectors/createGetTags): make it work for real :) 2016-06-14 16:39:10 +02:00
Julien Fontanet
047d4cb650 fix(select-objects/SelectTags): fix label 2016-06-14 16:39:10 +02:00
Julien Fontanet
736904c579 feat(select-objects): can be used as controlled inputs 2016-06-14 16:39:10 +02:00
Olivier Lambert
e883c668b5 feat(XOA): VM import / backups (#1063) 2016-06-14 15:29:47 +02:00
Julien Fontanet
14181aa8a7 fix(selectors): fix pool ordering 2016-06-14 11:53:08 +02:00
Olivier Lambert
0b1ba99afa feat(XOA): Free edition(#1062) 2016-06-14 10:44:44 +02:00
Olivier Lambert
88a6215939 feat(health): use SortedTable (#1057) 2016-06-13 12:08:58 +02:00
Greenkeeper
d5ebd33038 chore(package): update globby to version 5.0.0 (#1056)
https://greenkeeper.io/
2016-06-13 09:47:34 +02:00
ABHAMON Ronan
b2ac214c0f feat(form/Select): advanced virtualized select implementation (#1052) 2016-06-13 09:44:38 +02:00
Olivier Lambert
3dee41a511 feat(tasks): better task view. (#1055)
* feat(tasks): better task view
2016-06-10 17:26:30 +02:00
ABHAMON Ronan
934818c07d feat(backup/new): disaster recovery accept an SR (#1054)
Fixes #955
2016-06-10 15:49:21 +02:00
Pierre Donias
4dc614a58e fix(ActionButton): oneOfType expects an array (#1053) 2016-06-10 15:42:02 +02:00
Greenkeeper
35ea095b75 chore(package): update chartist-plugin-legend to version 0.3.1 (#1051)
https://greenkeeper.io/
2016-06-10 12:59:01 +02:00
Olivier Lambert
ae1a4c73b3 fix(issue template): use the word "current" instead of "actual" 2016-06-09 22:21:50 +02:00
ABHAMON Ronan
b58dbe89be feat(vms/import) (#1045) 2016-06-09 17:58:12 +02:00
ABHAMON Ronan
1b4551b622 fix(SortedTable): select first page when changing filter (#1050) 2016-06-09 16:57:30 +02:00
Fabrice Marsaud
72a8f819d3 fix(backup/overview): fix display pending jobs (#1049) 2016-06-09 15:01:27 +02:00
ABHAMON Ronan
df8e16379c fix(select-objects): remove option margin when no containers (#1047) 2016-06-09 14:15:06 +02:00
Julien Fontanet
7dbbc7e25c fix(invoke): fix optim when called without args 2016-06-09 13:36:07 +02:00
Julien Fontanet
1eaae70adb feat(editable/Text): support validation related props 2016-06-09 13:36:07 +02:00
Julien Fontanet
d4a61782c4 chore(menu): menu prefix for icons is optional 2016-06-09 13:35:12 +02:00
Fabrice Marsaud
0e39c6f895 fix(SelectRemote) (#1046) 2016-06-09 12:26:31 +02:00
Fabrice Marsaud
fcc3ede485 fix(settings/remotes): use file type instead of local (#1044) 2016-06-09 10:45:18 +02:00
Fabrice Marsaud
f001e7e713 xo-remote-parser 0.2 2016-06-09 10:28:13 +02:00
Olivier Lambert
3fb8fae821 feat(selectHost): display hostname directly. Continue to group per pools 2016-06-09 10:13:37 +02:00
Fabrice Marsaud
349f3185c5 feat(settings/remotes): edition (#1040) 2016-06-08 17:24:41 +02:00
Olivier Lambert
b457b8409f feat: most of the views now have a header (#1042) 2016-06-08 17:07:43 +02:00
Olivier Lambert
c61e5e1ac8 feat(health): use sorted Table for alarms (#1041) 2016-06-08 15:42:39 +02:00
ABHAMON Ronan
9e60f9d9fd feat(SortedTable): add filter (#1039) 2016-06-08 14:48:34 +02:00
ABHAMON Ronan
a95d40078f feat(SortedTable): pagination can be injected in a container (#1038) 2016-06-08 10:11:18 +02:00
Julien Fontanet
515798bd9f feat(selectors): filter objects by permissions 2016-06-07 16:45:32 +02:00
Julien Fontanet
20b28135a3 chore(store): do not connect to XOA updaters in Sources plan 2016-06-07 15:57:46 +02:00
Julien Fontanet
33d2b8bbeb chore(dev-tools): move into store 2016-06-07 15:57:46 +02:00
Julien Fontanet
7c2059af2b fix(xoa-updater/blockXoaAccess): typo 2016-06-07 15:57:46 +02:00
ABHAMON Ronan
1e0f57bd1a feat(SortedTable): support pagination (#1036) 2016-06-07 14:07:29 +02:00
Olivier Lambert
9484f1dbe6 fix(new sr): wrong URL for srs view 2016-06-07 10:29:06 +02:00
Julien Fontanet
658766c9e4 fix(home): fix variable name 2016-06-07 10:22:25 +02:00
Julien Fontanet
9e47d9acf1 chore(migrate-vm-modal): move into xo 2016-06-06 15:37:04 +02:00
Julien Fontanet
5a1247c021 chore(selectors): getAreObjectsFecthed() → areObjectsFetched() 2016-06-06 15:30:50 +02:00
Julien Fontanet
a8b7972f3c chore: use Container instead of .container-fluid 2016-06-06 14:31:26 +02:00
Fabrice Marsaud
836c2127f7 chore(backup/restore): refactor using render-xo-item (#1023) 2016-06-06 14:28:24 +02:00
Olivier Lambert
6cef200aed feat(menu,about): set content depending on XOA plan (#1033) 2016-06-06 14:21:02 +02:00
Julien Fontanet
c9c80b1d62 fix(xoa-updater): fix import lodash/forEach 2016-06-06 13:45:55 +02:00
Julien Fontanet
04328bc2d1 fix(new/sr): fix import lodash/trim 2016-06-06 13:45:39 +02:00
Julien Fontanet
d909d0eeeb fix(package): update xo-remote-parser to 0.2.1 2016-06-06 13:39:22 +02:00
Julien Fontanet
80f5e913ec chore(users) 2016-06-06 11:28:35 +02:00
Julien Fontanet
57eca31a9c feat(utils/addSubscriptions): decorator to inject subscriptions 2016-06-06 11:28:35 +02:00
Julien Fontanet
c645fc7ad1 feat(utils/connectStore): can handle an object of selectors 2016-06-06 11:28:35 +02:00
Julien Fontanet
78b524b2e8 chore: always user selector to access state 2016-06-06 11:28:35 +02:00
Fabrice Marsaud
1ff1b6931b feat(xoa-updater): initial integration (#952) 2016-06-06 11:23:57 +02:00
Olivier Lambert
29ac883616 feat(xo-app,home): add nice loading icon 2016-06-03 22:22:56 +02:00
Julien Fontanet
467a147603 fix(grid/Row): remove flex which broke columns collapse 2016-06-03 20:36:13 +02:00
Julien Fontanet
7b49b6304c fix(xo-app): fix body scrolling 2016-06-03 20:35:00 +02:00
Olivier Lambert
25991027b9 fix(home): links to import and restore 2016-06-03 19:48:45 +02:00
Olivier Lambert
fce83dfa66 chore(home): text outside links 2016-06-03 19:27:36 +02:00
Olivier Lambert
8603d5d468 feat(home): using card component 2016-06-03 19:15:52 +02:00
Julien Fontanet
bd17f85140 chore(home): do not use btn class on links 2016-06-03 17:56:00 +02:00
Julien Fontanet
037dddb945 fix(messages): fix a typo 2016-06-03 17:52:15 +02:00
Julien Fontanet
dd2151e611 fix(grid/Col): should always have a class 2016-06-03 17:51:56 +02:00
Julien Fontanet
a1e0cdadd6 fix(xo-app): do not set flex on the body container 2016-06-03 17:51:34 +02:00
Julien Fontanet
8d36efa66c chore(Col): do not set the size when full width 2016-06-03 17:35:41 +02:00
Julien Fontanet
b9a12a6dcc feat(Debug): can display promises 2016-06-03 17:24:56 +02:00
Olivier Lambert
08e4fe9990 feat(about): add about info (#1031) 2016-06-03 17:24:07 +02:00
ABHAMON Ronan
2333fec181 feat(SortedTable) (#1030) 2016-06-03 17:22:22 +02:00
Olivier Lambert
05676a78e3 feat(home): handle loading, no servers or no VMs (#1028) 2016-06-03 16:35:55 +02:00
Julien Fontanet
02aaae240c feat(CenterPanel): to use when no data for instance 2016-06-03 11:30:48 +02:00
Julien Fontanet
158924fe3c chore(xo-app): remove unnecessary style 2016-06-03 10:17:36 +02:00
Julien Fontanet
0341b926b9 chore(xo-app/page): move styles to CSS module 2016-06-03 10:12:03 +02:00
Julien Fontanet
69d1f93ea4 feat(xo-app): remove body padding 2016-06-03 10:11:03 +02:00
Julien Fontanet
423fb56ae0 fix(reducers): add missing change 2016-06-02 18:25:48 +02:00
Julien Fontanet
c5fc8d437f fix(selectors/getAreObjectsFetched): previous test was not good enough 2016-06-02 18:24:44 +02:00
Julien Fontanet
0811addf9c fix(selectors/getAreObjectsFetched): use the correct test 2016-06-02 18:18:31 +02:00
Julien Fontanet
e6f8108dc0 feat(selectors/getAreObjectsFetched) 2016-06-02 18:06:20 +02:00
Julien Fontanet
4aa4a8c75d feat(Servers): add spaces in creation form 2016-06-02 18:03:27 +02:00
Julien Fontanet
bbe0467d16 feat(Server): use editable/Password 2016-06-02 18:03:27 +02:00
Julien Fontanet
88ca69138b feat(Server): use ActionRowButton 2016-06-02 18:03:27 +02:00
Julien Fontanet
6a0d9c8805 feat(Users): use ActionButton as submit 2016-06-02 18:01:01 +02:00
Julien Fontanet
1a57f9f134 feat(Users): use ActionRowButton 2016-06-02 18:01:01 +02:00
Julien Fontanet
109aedd3ae feat(ActionButton): redirectOnSuccess can be a function 2016-06-02 18:01:01 +02:00
ABHAMON Ronan
bd9f9344e5 fix(backup jobs): edition (#1026) 2016-06-02 15:23:26 +02:00
ABHAMON Ronan
5190873e99 fix(json-schema-input): correctly handle optional array/object (#1027)
Fixes #1000.
2016-06-02 15:17:51 +02:00
Julien Fontanet
c5fe7eb0dd chore(Groups): remove unused import 2016-06-02 13:51:05 +02:00
Fabrice Marsaud
fef1b14d69 fix a xo fn 2016-06-02 13:48:05 +02:00
Julien Fontanet
472fc02533 feat(Groups): use editable/Text for name 2016-06-02 13:47:39 +02:00
Julien Fontanet
ed29524cf3 fix(xo): resolveIds handles non objects 2016-06-02 13:46:50 +02:00
Julien Fontanet
69f35436c2 feat(form/Password): enableGenerator defaults to false 2016-06-02 13:46:19 +02:00
Fabrice Marsaud
a0ca1cddb5 feat(ACLs) (#1011) 2016-06-02 13:19:35 +02:00
Greenkeeper
be4ffd8308 chore(package): update notifyjs to version 2.0.1 (#1025)
https://greenkeeper.io/
2016-06-02 12:04:35 +02:00
Julien Fontanet
8e246f08ee fix(xo subscriptions): fix running condition 2016-06-02 11:45:24 +02:00
Julien Fontanet
73eda65300 fix(xo subscriptions): wait for previous call to finish 2016-06-02 10:32:50 +02:00
Julien Fontanet
be4df02844 fix(README): fix XOA_PLAN example 2016-06-02 10:27:47 +02:00
Julien Fontanet
7de461319f feat(XOA_PLAN): environment var for different builds 2016-06-02 10:25:58 +02:00
Olivier Lambert
970fc16aab feat(vm): working VDI live migration 2016-06-01 18:35:09 +02:00
Olivier Lambert
5db2c5804d fix(backup): typo for button size 2016-06-01 17:41:05 +02:00
Olivier Lambert
6c2924a08a fix(backup): typo in button style 2016-06-01 17:41:05 +02:00
Pierre Donias
32511fe6a0 feat(editable/XoSelect) (#1020) 2016-06-01 17:22:39 +02:00
Olivier Lambert
94d5b0f083 chore(backup): use the appropriate components 2016-06-01 17:22:25 +02:00
Julien Fontanet
0e957b9566 fix(renderXoItemFromId): handle missing object 2016-06-01 17:01:53 +02:00
Julien Fontanet
ec93f21f0a fix(renderXoItemFromId): fix incorrect var name 2016-06-01 17:01:41 +02:00
Julien Fontanet
bbc4f3beb4 chore(xo): move subscription refreshes in related methods 2016-06-01 16:54:21 +02:00
Julien Fontanet
c271a25a51 feat(selectors/createFilter): if predicate is false, empty collection is returned 2016-06-01 16:35:09 +02:00
Julien Fontanet
c986bf0c46 chore(common): group multiple-files modules in dirs 2016-06-01 16:35:09 +02:00
ABHAMON Ronan
6f994b75e5 feat(backups): deletion & redirect after creation (#1019) 2016-06-01 16:30:40 +02:00
Olivier Lambert
a227039260 feat(vm): allow VDI remove, forget and disconnect 2016-06-01 14:37:00 +02:00
Pierre Donias
ee38c07a3f feat(form/SizeInput): new component for size input (#1017) 2016-06-01 11:15:05 +02:00
ABHAMON Ronan
9678ebd71e chore(select-objects): major refactoring (#1001) 2016-06-01 10:43:38 +02:00
Pierre Donias
82ce0d3461 feat(vm): redirect to home page when VM no longer exists (#1018)
feat(vm): redirect to home page when VM no longer exists
2016-05-31 18:39:03 +02:00
Olivier Lambert
8315c79ef7 feat(host): display the date for license expiry value 2016-05-31 17:25:35 +02:00
Olivier Lambert
69cb6d30b5 feat(VM): VDIs edition (#1015) 2016-05-31 12:27:16 +02:00
Julien Fontanet
f4beef514e fix(xo subscriptions): do not notify if no results yet 2016-05-31 09:16:52 +02:00
Olivier Lambert
f002677134 feat(vm,host): use copiable 2016-05-30 18:48:54 +02:00
Olivier Lambert
6270d2d3af feat(sr,host): add actions on PBDs (#1010) 2016-05-30 18:35:14 +02:00
Julien Fontanet
83625e4ba7 fix(editable/Number): ensure onChange gets a number 2016-05-30 18:21:42 +02:00
Julien Fontanet
d039112b5b feat(xo subscription): notify ASAP when data available 2016-05-30 17:14:24 +02:00
Julien Fontanet
d8481af288 feat(xo subscription): only notify on changes 2016-05-30 17:14:24 +02:00
Julien Fontanet
ea902c1073 fix(xo subscriptions): do not refresh if no subscribers 2016-05-30 17:14:24 +02:00
Julien Fontanet
db62c18a39 fix(xo subscriptions): avoid potential race condition
The subscription could still be active if all the subscriber
unsubscribed at the event reception.
2016-05-30 17:14:24 +02:00
Julien Fontanet
d004e2f759 feat(Copiable) 2016-05-30 17:14:24 +02:00
Julien Fontanet
1f7e457c64 fix(editable/Number): fix size when starting edition 2016-05-30 15:04:52 +02:00
Julien Fontanet
4eae9398d8 fix(editable): fix undo button 2016-05-30 14:58:11 +02:00
Olivier Lambert
4766121570 chore(migrate modal): remove useless message and i18n 2016-05-30 14:57:02 +02:00
Olivier Lambert
3180641e33 feat(sr): add usage and free space 2016-05-30 14:22:53 +02:00
Julien Fontanet
9273002905 ùchore(Tasks): remove unused import 2016-05-30 12:47:59 +02:00
Julien Fontanet
3fc9c5ec90 feat(Tasks): basic tasks list 2016-05-30 12:42:22 +02:00
Julien Fontanet
3266cea1d6 chore(package): update react-router to version 3.0.0-alpha.1 2016-05-30 12:41:52 +02:00
Julien Fontanet
97839c06dc chore(Menu): remove incorrect log 2016-05-30 11:58:32 +02:00
Olivier Lambert
304f290e42 feat(Menu): add tasks link (#1005) 2016-05-30 11:40:35 +02:00
Julien Fontanet
52a241f300 chore(ActionToggle): use btnStyle prop 2016-05-30 10:41:32 +02:00
Julien Fontanet
1c1ea0dcc4 chore(Menu): clean up resize handler code a bit 2016-05-30 10:41:32 +02:00
Julien Fontanet
d998b384e8 chore(ActionToggle): rewritten as stateless component 2016-05-30 10:41:32 +02:00
Olivier Lambert
9184afa6de feat(sr): add various actions (#1002)
* feat(sr,xojs): add SR actions

* naming fix

* typo

* remove SR

* working

* fix

* fix

* noop fixes
2016-05-27 19:07:44 +02:00
Pierre Donias
3e1b4d724f feat(xo/migrateVm): advanced dialog to select host (#993) 2016-05-27 18:21:42 +02:00
Fabrice Marsaud
5b6f50b25b feat(backups): restoration (#996) 2016-05-27 17:56:30 +02:00
Fabrice Marsaud
b757025359 feat(sr addition) (#971) 2016-05-27 17:06:08 +02:00
Olivier Lambert
52e97edbd5 feat(vm,home): add color for OS icons 2016-05-27 15:15:16 +02:00
Olivier Lambert
def88db128 feat(backup overview): i18n 2016-05-27 15:06:58 +02:00
Fabrice Marsaud
d04702e5d4 feat(backups/overview): add logs (#995) 2016-05-27 14:18:23 +02:00
Julien Fontanet
f6407771b5 feat(Icon): accepts className prop 2016-05-27 13:28:58 +02:00
Julien Fontanet
f6a6e125b6 fix(editable/Select): correctly set default value 2016-05-27 13:24:09 +02:00
Julien Fontanet
2303b8a89f feat(loading): center message 2016-05-27 13:16:10 +02:00
Julien Fontanet
93f286b6ac chore(package): remove unused react-router-redux 2016-05-27 13:16:10 +02:00
Julien Fontanet
75e5f931eb chore(store): clarify Xo connection 2016-05-27 13:16:09 +02:00
Olivier Lambert
b215e89572 fix(vm): use Number component instead of Text for vCPU max number edition 2016-05-27 10:18:18 +02:00
Julien Fontanet
07a7e8cf0a feat(selectors/createPager): n can be a selector as well 2016-05-27 10:04:17 +02:00
Olivier Lambert
52000edd7d feat(vm): handle correctly suspended VMs 2016-05-27 09:58:53 +02:00
ABHAMON Ronan
3e4c07c86f feat(self-service): dashboard and management (#992) 2016-05-26 18:20:09 +02:00
Olivier Lambert
92ce69c603 feat(meter): style (#994) 2016-05-26 17:06:01 +02:00
Olivier Lambert
a338e0a3f1 fix(tags): clashing component names tags/xo-tags/label 2016-05-26 13:50:56 +02:00
Fabrice Marsaud
143e09b65f feat(settings): remote management (#975) 2016-05-26 11:46:22 +02:00
Olivier Lambert
e5cc5abdc9 feat(theme) (#978) 2016-05-26 10:31:49 +02:00
Fabrice Marsaud
dfc96ebb99 feat(xo-app): Open Source disclaimer (#972) 2016-05-26 10:24:28 +02:00
Julien Fontanet
9397d0121d perf(select-objects/SelectVm): improve connectStore 2016-05-25 18:33:11 +02:00
Julien Fontanet
d8a1f3c73a feat(home): auto open pools/hosts selects 2016-05-25 13:07:15 +02:00
Julien Fontanet
a07cb425a4 fix(form/Range): uncontrolled therefore value → defaultValue 2016-05-25 13:07:15 +02:00
Julien Fontanet
a89b33dfdf chore(host): _isRunning is a property 2016-05-25 13:07:15 +02:00
Olivier Lambert
486d33448b feat(host): toggle and other improvements in advanced tab 2016-05-25 12:06:33 +02:00
Julien Fontanet
2299d397cb feat(messages): possibility to add props to the FormattedMessage 2016-05-25 11:04:53 +02:00
Julien Fontanet
0173c4709f fix(settings/server): password edition 2016-05-25 11:04:53 +02:00
Olivier Lambert
42fdf8b61f feat(form): boolean toggle (#985) 2016-05-25 11:04:21 +02:00
Greenkeeper
0253723652 chore(package): update ava to version 0.15.0 (#987)
https://greenkeeper.io/
2016-05-25 09:58:59 +02:00
Olivier Lambert
5ca51d3510 feat(vm): edition of number of CPUs (#984) 2016-05-24 18:00:06 +02:00
Julien Fontanet
466dc0127d fix(xo): subscriptions wait for sign in 2016-05-24 17:52:04 +02:00
Julien Fontanet
32f610485c fix(editable): anything can be used as children 2016-05-24 17:42:20 +02:00
Olivier Lambert
429e1b54ee feat(vm): edition in advanced tab (#983) 2016-05-24 16:51:50 +02:00
ABHAMON Ronan
268c037487 feat(select-objects): connected to store and accept an optional predicate (#981) 2016-05-24 15:45:28 +02:00
Julien Fontanet
c146f3105e fix(host/stats): remove unused import 2016-05-24 15:09:21 +02:00
Julien Fontanet
81e0c04722 feat(messages): a render function can be passed 2016-05-24 14:44:57 +02:00
Olivier Lambert
5d156695d2 feat(vm): memory limits (#980) 2016-05-24 12:55:35 +02:00
Julien Fontanet
f71438347c feat(selectors/createGetObjectsOfType): add groupBy() method 2016-05-24 12:41:17 +02:00
Pierre Donias
a3081d607f fix(Editable/Text): use value instead of children since children is not required (#982) 2016-05-24 12:06:38 +02:00
Julien Fontanet
ca81f445b9 fix(selectors/createTags): more complete stub 2016-05-24 11:56:24 +02:00
Olivier Lambert
1c22ce6d76 feat(home): add bold for select sort option (#979) 2016-05-24 11:07:05 +02:00
Pierre Donias
a0d482ba88 feat(Editable): Size component (#966) 2016-05-24 10:13:51 +02:00
Pierre Donias
0c050cc053 feat(SingleLineRow): columns are centered vertically (#973) 2016-05-24 10:12:17 +02:00
Olivier Lambert
9645d624f2 fix(messages): typo on number 2016-05-23 19:14:56 +02:00
Julien Fontanet
f29cb94d9f fix(selectors/createPicker): typo 2016-05-23 17:37:21 +02:00
Julien Fontanet
e239206626 feat(selectors): hide all objects to non admins 2016-05-23 17:25:28 +02:00
Julien Fontanet
35d1065eaf feat(menu): Sign out 2016-05-23 17:25:08 +02:00
Julien Fontanet
8384d6f9d7 fix(messages): vars must be explicitely marked as number 2016-05-23 17:20:25 +02:00
Julien Fontanet
5b3282ba51 perf: major rework of reducers and selectors (#976) 2016-05-23 16:29:23 +02:00
Olivier Lambert
bd1043f034 feat(home): add more bulk actions (#974) 2016-05-23 13:47:12 +02:00
Julien Fontanet
c847dcec15 fix(vm/general): remove test icon 2016-05-21 13:53:15 +02:00
Julien Fontanet
f66994f0b5 fix({host,vm}/console): better display when no stats available 2016-05-21 13:45:01 +02:00
Julien Fontanet
eba27f1823 fix(backup/new): use standard icon on save button 2016-05-21 13:45:01 +02:00
Julien Fontanet
ad1bbb2a00 fix(utils/osFamily): behaves if osName is undefined 2016-05-21 13:45:01 +02:00
Julien Fontanet
42506ab37d chore: rationalize whitespace usage 2016-05-21 13:45:01 +02:00
Julien Fontanet
6bae33826d chore(icons): always use Icon 2016-05-21 13:44:56 +02:00
Julien Fontanet
914c2b89c5 feat(icons): keep using Linux icon for CoreOS 2016-05-21 13:42:47 +02:00
Olivier Lambert
e79926cf29 fix(vm): check if vm.addresses exists 2016-05-21 13:09:40 +02:00
Olivier Lambert
1f15d2c736 fix(vm): i18n shorter button names 2016-05-21 13:03:37 +02:00
Olivier Lambert
fadd27fd23 fix(vm): better display when tools not present (#968) 2016-05-21 13:02:44 +02:00
Julien Fontanet
d5aeb8db55 feat(icons): add CoreOS 2016-05-21 12:02:23 +02:00
Pierre Donias
d5dbdd9986 fix(home): fix false icon attribute in Icon (#967) 2016-05-20 18:19:49 +02:00
Pierre Donias
352c977dc7 feat(home): add tick next to current sort criteria in dropdown (#965) 2016-05-20 16:55:04 +02:00
Julien Fontanet
bf008eba99 style(home): remove double line breaks 2016-05-20 16:29:54 +02:00
Pierre Donias
76b7777fff feat(Menu): collapses when window width is small (#963) 2016-05-20 16:28:12 +02:00
Julien Fontanet
9292d990da fix(home): behaves if filter is null 2016-05-20 15:15:47 +02:00
Julien Fontanet
87fe715823 fix(icons): add generic Linux icon 2016-05-20 12:31:11 +02:00
Julien Fontanet
25e32e0600 chore(icons): remove fixed width 2016-05-20 12:31:10 +02:00
Julien Fontanet
41c901a05c fix(complex-matcher/addPropertyClause): fix when enclause in a and 2016-05-20 11:44:04 +02:00
Julien Fontanet
fdaba2faf4 chore(home): pagination handling more standard 2016-05-20 11:44:04 +02:00
Julien Fontanet
0c73ad4f46 fix(complex-matcher): execute() requires node as context 2016-05-20 09:30:02 +02:00
Julien Fontanet
36c44bc3d4 feat(complex-matcher): addPropertyClause() 2016-05-19 18:13:51 +02:00
Julien Fontanet
d612598bd0 feat(complex-matcher): execute() and toString() expects current node as context 2016-05-19 18:13:51 +02:00
Julien Fontanet
2d75b6086f chore(complex-matcher/parse): refactor 2016-05-19 18:13:51 +02:00
Julien Fontanet
3345674604 chore(complex-matcher): expose node creators 2016-05-19 18:13:51 +02:00
Julien Fontanet
1eeaeeeca5 feat(benchmarks): complex matcher parsing 2016-05-19 18:13:51 +02:00
Olivier Lambert
b0bea8b3ba feat(vm/host): add links to host and pools 2016-05-19 18:10:00 +02:00
Olivier Lambert
0e3e5edd17 fix(host): wrong function name for log deletion 2016-05-19 16:47:57 +02:00
Olivier Lambert
ec1287a2f4 fix(multiple views): missing rows 2016-05-19 16:47:27 +02:00
Pierre Donias
9d2c857c59 feat(Text): placeholder prop (#961) 2016-05-19 14:34:25 +02:00
Pierre Donias
077f4f201c fix(grid): vertically center Col inside Row (#962) 2016-05-19 14:33:59 +02:00
Pierre Donias
9a2154a2ce feat(editable): editable Select (#959) 2016-05-19 12:58:43 +02:00
Olivier Lambert
f4c111c1c2 fix(home): a bit better responsive things 2016-05-18 17:18:53 +02:00
Pierre Donias
9483a06e8a feat(Text): Long click to edit with prop useLongClick (#957) 2016-05-18 13:31:15 +02:00
Olivier Lambert
df2a90dc1d fix(xo): wrong parameter name 2016-05-18 12:44:43 +02:00
Pierre Donias
246c190ccd fix(home): collapse-all button should not be hidden by action buttons. (#956) 2016-05-18 11:25:07 +02:00
Pierre Donias
4640817a14 feat(home): VMs migration (#953) 2016-05-18 10:59:24 +02:00
Pierre Donias
9c7690d39b fix(home): remove useless ref on VmItem (#954) 2016-05-17 17:46:29 +02:00
Pierre Donias
160805af05 feat(home): bulk actions (#948) 2016-05-17 16:16:03 +02:00
ABHAMON Ronan
39e85730f0 feat(plugins): new page to configure plugins (#946) 2016-05-17 15:42:10 +02:00
Olivier Lambert
da692e1a92 feat(home): sort by (#950) 2016-05-17 15:33:03 +02:00
Julien Fontanet
23bc60f1ac feat(selectors/createSort): any input can be a selector 2016-05-17 12:21:32 +02:00
Julien Fontanet
ce0f759509 chore(store/actions/createAction): do not add payload if undefined 2016-05-17 10:53:27 +02:00
Julien Fontanet
1793e5943a chore(store/actions/createAction): remove unused promises handling 2016-05-17 10:52:59 +02:00
Julien Fontanet
201b5db155 chore(BaseComponent): inline env test because it was not prune from the build 2016-05-17 10:12:45 +02:00
Olivier Lambert
c588ac6777 fix(backup): unknown schedule typo in translation 2016-05-16 18:23:35 +02:00
Julien Fontanet
28c01fd4e1 feat(messages): throw an error when a message is undefined 2016-05-16 18:19:27 +02:00
Julien Fontanet
0715e7a31f feat(ISSUE_TEMPLATE): copied from next-release 2016-05-16 16:33:20 +02:00
Julien Fontanet
b497c38e34 feat(ActionButton): prints handler errors 2016-05-16 15:55:55 +02:00
Julien Fontanet
8a08dce405 chore(sr/TabAdvanced): remove unused imports 2016-05-16 15:55:15 +02:00
Julien Fontanet
a620c348bf fix(Menu): always update to avoid issues with router and intl 2016-05-16 15:55:15 +02:00
Julien Fontanet
fe750b7270 chore(*/TabLogs): avoid creating function in render() 2016-05-16 15:55:15 +02:00
Julien Fontanet
4aa9d56dfc fix(home/TabGeneral): use key prop on correct component 2016-05-16 15:55:15 +02:00
Julien Fontanet
4b2ebf2a3a chore: use handlerParam prop everywhere 2016-05-16 15:55:15 +02:00
Julien Fontanet
6290446ea5 chore(dashboard/health): remove unused forEach import 2016-05-16 15:55:15 +02:00
Julien Fontanet
42f5d06960 chore(*/ActionBar): use param prop 2016-05-16 15:55:14 +02:00
Julien Fontanet
85e8006137 chore(dashboard/health): use ActionRowButton handlerParam prop 2016-05-16 15:55:14 +02:00
Julien Fontanet
7c29d4c644 fix(dashboard/health): adapt to modal/confirm changes 2016-05-16 15:55:14 +02:00
Julien Fontanet
7378bc852d feat(xo): deleteSr() 2016-05-16 15:55:14 +02:00
Julien Fontanet
67ed137cfa feat(xo): add confirm to convertVmToTemplate and deleteVm 2016-05-16 15:55:14 +02:00
Julien Fontanet
02e08e54a2 feat(TabButton): forward all props to ActionButton 2016-05-16 15:55:14 +02:00
Julien Fontanet
6b2dd24334 feat(modal/confirm): now expects an object param 2016-05-16 15:55:14 +02:00
Julien Fontanet
131d5becad feat(ActionBar): add param prop to inject as handlerParam 2016-05-16 15:55:14 +02:00
Julien Fontanet
157e0a83b1 feat(ActionButton): accept an handlerParam prop
It makes it easier to use without having to create new functions
everywhere (which can cause perf issues).
2016-05-16 15:55:14 +02:00
Julien Fontanet
d0d3abce3e chore(*/ActionBar): remove incorrect handlers prop 2016-05-16 15:55:14 +02:00
Julien Fontanet
b965c41a45 fix(ActionBar): do not keep recreating style prop 2016-05-16 15:55:14 +02:00
Julien Fontanet
42f824e034 fix(vm): do not show 0 snapshots 2016-05-16 15:55:14 +02:00
Olivier Lambert
2768d9d49d fix(pool patch): incorrect key for translation 2016-05-16 15:52:12 +02:00
Olivier Lambert
c9a86dcae3 feat(host): link to SRs 2016-05-16 15:40:57 +02:00
ABHAMON Ronan
e1d307ea2c feat(home): implement objects selection for filtering (#943) 2016-05-16 12:59:34 +02:00
Olivier Lambert
98ece12ae8 fix(home): number of VMs per page to 20 2016-05-16 10:40:34 +02:00
Olivier Lambert
4cf3db7c2a fix(vm disks): check if VBD has VDIs 2016-05-16 10:37:55 +02:00
Julien Fontanet
7c2f79d980 chore(Vm): move some logic in the tabs 2016-05-16 01:18:54 +02:00
Julien Fontanet
fe064f8b6a feat(utils/@checkPropsState): create an optimized shouldComponentUpdate() 2016-05-16 00:44:15 +02:00
Julien Fontanet
2bad2f6b80 perf(BaseComponent): do not use splat and spread params in constructor 2016-05-16 00:42:20 +02:00
Julien Fontanet
2ba9c5193f chore(Home): use VMS_PER_PAGE constant 2016-05-16 00:16:33 +02:00
Julien Fontanet
331695c10a fix(Tooltip): put tooltip above other components 2016-05-16 00:04:31 +02:00
Julien Fontanet
f299193f05 chore(Menu): cleanup and minor optimization 2016-05-16 00:03:55 +02:00
Julien Fontanet
de8130abc2 feat(selectors/_id): use id prop when no routeParams 2016-05-15 23:55:50 +02:00
Julien Fontanet
1cbde7f2e1 chore(XoApp): inherits from BaseComponent 2016-05-15 23:51:15 +02:00
Julien Fontanet
d1c796d9a7 chore(XoApp): unnecessary to validate children prop 2016-05-15 23:50:33 +02:00
Julien Fontanet
af43061353 chore(XoApp): remove unused connectStore() 2016-05-15 23:50:07 +02:00
Olivier Lambert
1b2ca8e69e 4.16.1 2016-05-14 11:18:16 +02:00
Olivier Lambert
a9e6679b08 fix(vm view): filter perms on all SR. Fix #945 2016-05-14 11:17:43 +02:00
Olivier Lambert
9408760122 chore(host,sr): style fixes 2016-05-14 11:02:26 +02:00
Olivier Lambert
c25e804d61 fix(home): use link for quick actions 2016-05-14 10:34:59 +02:00
Olivier Lambert
b18b2262eb vdi map 2016-05-14 00:12:56 +02:00
Olivier Lambert
570440dc7d host memory bar 2016-05-13 23:12:04 +02:00
Olivier Lambert
aef660fb2f feat(pool): sr view (#944) 2016-05-13 19:50:02 +02:00
Julien Fontanet
17671c7282 feat(selectors/createGetObjects): ignore missing objects 2016-05-13 17:38:38 +02:00
Julien Fontanet
8216ab44b4 feat(selectors): expose createSelector as its usually the name we want 2016-05-13 17:38:34 +02:00
Julien Fontanet
5902d43a94 fix(xo): fix internal createSubscription() 2016-05-13 17:20:12 +02:00
Pierre Donias
a6eb04d3f9 feat(home): VMs pagination (#940) 2016-05-13 17:15:45 +02:00
Julien Fontanet
a71780e860 chore(xo/subscribe): split into independant functions 2016-05-13 17:14:30 +02:00
Julien Fontanet
c2d815ef66 perf(selectors/objects): avoid creating empty objects 2016-05-13 17:11:34 +02:00
Julien Fontanet
bf4679aa9b perf(modal): do not create functions in render 2016-05-13 17:10:16 +02:00
Julien Fontanet
4dd74bbb16 perf(react-novnc): do not create functions in render 2016-05-13 17:10:13 +02:00
Julien Fontanet
10530146ca chore(selectors): remove unused vmContainers 2016-05-13 16:59:30 +02:00
Julien Fontanet
75d49da3d4 fix(home): correctly set filter in input field 2016-05-13 16:43:32 +02:00
Julien Fontanet
af026b0c52 perf(home): let VmItem fetch its container 2016-05-13 16:43:32 +02:00
Julien Fontanet
bcd4f70d0e perf(selectors/createGetObject): simplify 2016-05-13 16:43:32 +02:00
Olivier Lambert
54cc31d1a0 feat(pool): pool view (#933) 2016-05-13 15:26:26 +02:00
ABHAMON Ronan
3f0553861a feat(select-objects): split out XO select logic in low level components (#939) 2016-05-13 14:33:22 +02:00
Julien Fontanet
e1b3c51d2c fix(home): fix expand all button 2016-05-13 14:27:13 +02:00
Julien Fontanet
58a0e3fad6 fix(home): correctly set default filter 2016-05-13 14:26:54 +02:00
Julien Fontanet
6bb235650a chore(home): extract _saveFilter() 2016-05-13 12:07:50 +02:00
Julien Fontanet
0bf0bc4c33 chore(scheduling): remove an incorrect FIXME 2016-05-13 09:43:12 +02:00
Julien Fontanet
3085749e92 chore(Notification): minor optimizations 2016-05-12 21:12:03 +02:00
Julien Fontanet
de3abbf6b8 chore(host): minor optimizations 2016-05-12 21:08:12 +02:00
Julien Fontanet
925469689f chore(scheduling): implement components on top of BaseComponent 2016-05-12 20:50:06 +02:00
Julien Fontanet
631e58a585 chore(form): implement components on top of BaseComponents 2016-05-12 20:42:25 +02:00
Julien Fontanet
63571d06bf chore(editable/Text): implement on top of BaseComponent 2016-05-12 20:38:04 +02:00
Julien Fontanet
1979758fab chore(ActionButton): implement on top of BaseComponent 2016-05-12 20:35:51 +02:00
Julien Fontanet
6ef5a23000 chore(vm): minor optimizations 2016-05-12 20:35:48 +02:00
Julien Fontanet
ae4aa23d27 chore(Tags): clean & optimize 2016-05-12 20:29:46 +02:00
Julien Fontanet
710d1f13cd feat(BaseComponent): React component with reasonable defaults & debugging traces 2016-05-12 20:29:43 +02:00
Julien Fontanet
57a4d366d7 chore(shallow-equal): split out of selectors 2016-05-12 17:52:30 +02:00
Julien Fontanet
8060c66c08 feat(home): put the filter in the URL 2016-05-12 17:23:50 +02:00
Julien Fontanet
d5a58fbec2 perf(home): do not create functions in render 2016-05-12 16:15:53 +02:00
Julien Fontanet
059256de3e chore(home): simplify by treating no VMs case first 2016-05-12 15:50:54 +02:00
Julien Fontanet
2c52d4c867 feat(utils/firstDefined) 2016-05-12 15:50:54 +02:00
Julien Fontanet
d9bfde2e47 chore(selectors): use props.routeParams instead of props.params 2016-05-12 15:50:54 +02:00
ABHAMON Ronan
757acd8d92 fix(backups): remoteId vs remote param name(#938) 2016-05-12 14:52:31 +02:00
Pierre Donias
3e92252e2e fix(BlockLink): correctly behaves with links/buttons/inputs (#934) 2016-05-12 13:21:00 +02:00
ABHAMON Ronan
ce7aeb1a27 chore(Scheduler): use react-intl for month names translation (#935) 2016-05-12 13:13:43 +02:00
ABHAMON Ronan
236d2ad39a feat: backups overview (#932) 2016-05-12 13:04:15 +02:00
Julien Fontanet
63b37714b1 chore(page): use number for plain numeric styles 2016-05-12 11:53:06 +02:00
Julien Fontanet
dc81fd0622 chore(style): remove unused .xo-icon-action-row styles 2016-05-12 11:52:45 +02:00
Julien Fontanet
ccec2bf7ee chore(ActionRow): renamed to ActionRowButton 2nd pass 2016-05-12 11:51:48 +02:00
Julien Fontanet
c93b93331e chore(ActionRow): renamed to ActionRowButton 2016-05-12 11:31:43 +02:00
Julien Fontanet
1f194f1680 chore(ActionRow): reimplemented on top of ActionButton 2016-05-12 11:29:54 +02:00
Julien Fontanet
2251123c1d perf(invoke): minor optim when no param 2016-05-12 11:17:23 +02:00
Julien Fontanet
1bfe1c3370 chore(complex-matcher/parse): rename pattern to input 2016-05-12 11:17:23 +02:00
Olivier Lambert
0044eeb6d1 chore(health): use components and remove useless arrays 2016-05-12 09:52:55 +02:00
Olivier Lambert
669302d46b regain focus on the search field 2016-05-11 18:54:25 +02:00
Olivier Lambert
fce2f44197 feat(home): pre-existing filters (#931) 2016-05-11 18:06:45 +02:00
Pierre Donias
64db1df248 feat(user): new user page (#930) 2016-05-11 17:14:49 +02:00
Olivier Lambert
9109d55019 new VM button working on home view 2016-05-11 16:31:09 +02:00
Olivier Lambert
412e13ccd5 better header content style 2016-05-11 16:25:08 +02:00
Olivier Lambert
0c7e0528b6 better patches refresh when installing a patch 2016-05-11 15:49:13 +02:00
Pierre Donias
ccb22a2f40 chore(Page): remove unnecessary component Header (#928) 2016-05-11 15:48:40 +02:00
Pierre Donias
2f3e463aca feat: move tabs in header for host and VM views (#927)
Fixes #926
2016-05-11 15:13:12 +02:00
Olivier Lambert
c548e08aea react component usage for action buttons in rows 2016-05-11 15:02:19 +02:00
ABHAMON Ronan
150e0171f0 feat: initial VM backups view (#924) 2016-05-11 14:27:58 +02:00
Olivier Lambert
bfcaca7bc0 tab button for VM view 2016-05-11 12:17:30 +02:00
Olivier Lambert
fcb0482193 use TabButton component in host view 2016-05-11 12:02:50 +02:00
Olivier Lambert
714ea7c236 a bit better home responsive view 2016-05-11 11:29:15 +02:00
Pierre Donias
cc30799f0d feat: browser notifications (#921)
* Browser notifications.

* Browser notifications: XO logo in notification.

* Browser notifications: multiple enhancements.
2016-05-11 11:13:08 +02:00
Pierre Donias
df71259a10 style: removed incorrect JSX whitespaces (#925) 2016-05-11 10:36:55 +02:00
Olivier Lambert
9a0ae5d4b9 quick buttons for home view 2016-05-10 16:26:30 +02:00
Olivier Lambert
eea4648ada add docker icon for VM with docker XS plugin installed 2016-05-10 14:27:11 +02:00
Olivier Lambert
2883398c2a add modal for VM convert and delete 2016-05-10 14:17:47 +02:00
Olivier Lambert
84f6e14b89 display the number of snapshot in the expanded home VM view 2016-05-10 13:53:29 +02:00
Pierre Donias
30fb9ed65a feat(modal): alert() and confirm() methods (#918) 2016-05-10 12:00:48 +02:00
Olivier Lambert
a809f2d1f2 re add install all patches in host patch tab 2016-05-10 10:53:35 +02:00
Olivier Lambert
6b5a19983d missing translation in server view 2016-05-09 19:41:36 +02:00
Olivier Lambert
2471f447b3 add current status info for VMs 2016-05-09 17:18:44 +02:00
Julien Fontanet
413e944d7a fix(home): fix imports 2016-05-09 13:46:20 +02:00
Julien Fontanet
3b952819d6 feat(home): remember the last used filter 2016-05-09 13:41:06 +02:00
Greenkeeper
2445c10c1c chore(package): update modular-css to version 0.21.0 (#920)
https://greenkeeper.io/
2016-05-09 11:46:00 +02:00
Julien Fontanet
7e26593d04 feat(complex-matcher): quoted strings 2016-05-08 17:53:34 +02:00
Greenkeeper
73595c683b chore(package): update modular-css to version 0.20.0 (#919)
https://greenkeeper.io/
2016-05-08 10:02:02 +02:00
Julien Fontanet
570f56a4cc chore(complex-matcher): test parse() and toString() 2016-05-08 00:25:16 +02:00
Julien Fontanet
f9e940871e feat(home): add group/or syntax 2016-05-08 00:24:32 +02:00
Julien Fontanet
c2b724a54a chore(test): add AVA test runner 2016-05-08 00:21:58 +02:00
Olivier Lambert
f52db472ed clear search button 2016-05-07 17:52:17 +02:00
Julien Fontanet
324fe98a5b chore(complex-matcher): minor parser simplification 2016-05-07 17:32:25 +02:00
Julien Fontanet
2fd9833580 chore(complex-matcher): move into its own module 2016-05-07 17:25:44 +02:00
Olivier Lambert
0ab4827d6f default search value with a space and search autofocus 2016-05-07 17:19:42 +02:00
Julien Fontanet
f0bd7d7eee perf(home): debounce filter by 250ms 2016-05-07 17:02:55 +02:00
Olivier Lambert
8cd1209602 display OS icon if possible, even when we do not detect xen tools 2016-05-07 16:57:04 +02:00
Julien Fontanet
e2781adc81 feat(home): allow search on nested properties 2016-05-07 16:49:45 +02:00
Julien Fontanet
b91ac2fe89 feat(home): initial complex filter 2016-05-07 16:16:22 +02:00
Julien Fontanet
683a7a1851 perf(selectors/tags): sort the tags and wraps the selector 2016-05-07 13:11:28 +02:00
Olivier Lambert
8b05aa7b59 remove useless array 2016-05-07 13:00:41 +02:00
Olivier Lambert
883f839bfd action for servers 2016-05-07 12:55:22 +02:00
Julien Fontanet
90dc00ac6b perf: throttle object updates by 500ms 2016-05-07 12:51:41 +02:00
Julien Fontanet
9c1cecbb7d chore(dev-tools): disable for now
It's not used at the moment and it has an major perf impact.
2016-05-07 12:05:19 +02:00
Julien Fontanet
1fce11bfba chore(store): do not add useless enhancers 2016-05-07 12:05:19 +02:00
Olivier Lambert
fba3ebdf49 basic filtering in home view 2016-05-07 11:34:05 +02:00
Olivier Lambert
e9585e08a4 only count pending tasks 2016-05-07 00:50:37 +02:00
Olivier Lambert
24a89985fb remove useless icon in vm general tab 2016-05-06 22:20:18 +02:00
Olivier Lambert
33026e8281 more logical order display for the home view 2016-05-06 21:23:54 +02:00
Olivier Lambert
50d9b832a9 better tag component 2016-05-06 21:14:54 +02:00
Olivier Lambert
1df82c3380 do not display a filter if there isn't any object associated with it (ACLs or nothing to display) 2016-05-06 18:59:57 +02:00
Olivier Lambert
0a8db4ebbf object number for filters 2016-05-06 18:39:44 +02:00
Julien Fontanet
928b19aef4 chore(TabButton): move into its own module 2016-05-06 17:48:53 +02:00
Julien Fontanet
a7ec98cef6 chore(icons): move into its own stylesheet 2016-05-06 17:46:13 +02:00
Julien Fontanet
67326a1859 chore(button): remove unused module 2016-05-06 17:33:30 +02:00
Julien Fontanet
631a8a5edf fix(ActionButton): fixed width icon 2016-05-06 17:32:40 +02:00
Julien Fontanet
7e5e463ef2 feat(ActionButton): better feedback on async actions 2016-05-06 17:20:53 +02:00
Julien Fontanet
add65e41da chore(actions): move business code in xo 2016-05-06 17:20:48 +02:00
Julien Fontanet
351b4571cd chore(store): move into common 2016-05-06 17:18:56 +02:00
Julien Fontanet
793258a91f chore(dev-tools): move into common 2016-05-06 17:18:56 +02:00
Julien Fontanet
ed3e1933c3 fix(xo): names of VM clones and snapshots 2016-05-06 17:18:56 +02:00
Olivier Lambert
2f63b26458 use react boostrap button component 2016-05-06 16:56:52 +02:00
Olivier Lambert
f1d14da3dd UI improvements on home view 2016-05-06 16:03:49 +02:00
Pierre Donias
4a3d90bdf3 feat(notification): new module (#914)
* Notifications

* Notification: global notification fired by `notify(...)`

* Notification: Bootstrap colors.

* Notification: Simplified version. Usage example.
2016-05-06 14:27:59 +02:00
Olivier Lambert
f8f24fbc37 feat(home view): major rework 2016-05-06 14:26:30 +02:00
Pierre Donias
8f6c53e111 feat(Modal): new component (#916) 2016-05-06 14:04:38 +02:00
Pierre Donias
c23f55b1d4 feat(Wizard): new component (#896) 2016-05-06 13:41:05 +02:00
Olivier Lambert
88f94f5d6f improved XO title in menu collapse 2016-05-04 21:07:58 +02:00
Olivier Lambert
3e99a179b7 add tool tip for action bar 2016-05-04 11:56:13 +02:00
Olivier Lambert
c7271f94a5 remove xo call for vdi and sr set 2016-05-04 10:46:43 +02:00
Olivier Lambert
5c0ced942c fix tags 2016-05-03 18:41:55 +02:00
Julien Fontanet
d9b07e76f9 chore(package): update standard to version 7.0.0 2016-05-03 18:31:15 +02:00
Julien Fontanet
c75793df20 chore(package): use commit-msg hook instead of commit-msg
It avoids running the tests when there is nothing in the stage.
2016-05-03 18:28:08 +02:00
Julien Fontanet
1bee5121f0 chore(package): remove unused Babel config 2016-05-03 18:28:08 +02:00
Julien Fontanet
932e7eb374 chore(README): remove dependencies status 2016-05-03 18:28:08 +02:00
Julien Fontanet
abdbcfe42b chore(README): add Travis status in title 2016-05-03 18:28:08 +02:00
Julien Fontanet
a62e888732 chore(gitignore): remove unused config.json entry 2016-05-03 18:28:08 +02:00
Olivier Lambert
16b982b953 fix patches issues 2016-05-03 18:25:33 +02:00
Olivier Lambert
e92b87095b container for header 2016-05-03 17:37:42 +02:00
Olivier Lambert
e5f1aa689b flex shrink fix 2016-05-03 17:35:59 +02:00
Olivier Lambert
f39a05cd8d smaller header for VM and host view 2016-05-03 16:42:32 +02:00
Olivier Lambert
c5e22b785a replace xo.call 2016-05-03 15:24:39 +02:00
Olivier Lambert
11f93a125c add header for host view 2016-05-03 15:22:25 +02:00
Pierre Donias
38a9cb002d Menu and sticky header (#891)
Fixes #705
2016-05-03 14:13:11 +02:00
ABHAMON Ronan
cf1a38a004 Recursive forms implementation. (#894) 2016-05-03 12:09:22 +02:00
Olivier Lambert
d6e823d19d fix missingpatches call 2016-05-03 10:24:30 +02:00
Olivier Lambert
763a23d9d0 add recoveryStartVm method 2016-05-01 11:19:58 +02:00
Olivier Lambert
f266577f2f fix cloneVM method 2016-05-01 11:00:49 +02:00
Olivier Lambert
1bb5e73668 less choices in VM action bar, advanced actions are in advanced tab 2016-05-01 10:39:26 +02:00
Olivier Lambert
b07bc755f6 add label on tab for number of patches missing 2016-05-01 09:57:05 +02:00
Olivier Lambert
db4b39c54b upgrade on poolpatches 2016-04-29 18:53:43 +02:00
Julien Fontanet
ffd95261c3 4.16.0 2016-04-29 12:23:59 +02:00
Olivier Lambert
82f38040c1 changelog 2016-04-29 12:22:14 +02:00
Olivier Lambert
7bb4f9f8e3 update host 2016-04-28 22:33:59 +02:00
Olivier Lambert
c2345df275 Merge pull request #905 from vatesfr/v5-toolbar-improved
V5 toolbar improved, missing patches and xo call removal
2016-04-28 19:15:46 +02:00
Olivier Lambert
b0c341da3f minor fixes 2016-04-28 19:11:56 +02:00
Olivier Lambert
b1ccc16da7 add missing patches 2016-04-28 19:11:44 +02:00
Julien Fontanet
16856a5911 feat(utils/routes): support plain route def 2016-04-28 17:32:06 +02:00
Olivier Lambert
d4ee364349 add host action bar 2016-04-28 17:03:11 +02:00
Olivier Lambert
db62ca7b4b proper col/row for vm tabs 2016-04-28 16:20:36 +02:00
Olivier Lambert
d0b99b854d remove more xo direct call 2016-04-28 15:57:17 +02:00
Olivier Lambert
b3b13b3e01 less xo calls 2016-04-28 15:55:46 +02:00
Julien Fontanet
5cb738b82b chore: reduce xo.call() direct use 2016-04-28 15:22:40 +02:00
Olivier Lambert
cfe3b15cbe Toolbar actions 2016-04-28 15:06:28 +02:00
Julien Fontanet
a1bde80925 chore: move IntlProvider inside XoApp. 2016-04-28 12:37:49 +02:00
Julien Fontanet
4c958dd584 chore(selectors): remove unused code 2016-04-28 12:05:37 +02:00
Olivier Lambert
a05d4d3d18 Merge pull request #892 from vatesfr/pierre-v5-better-action-bar-ui
Better action bar UI.
2016-04-28 10:28:05 +02:00
Pierre
503b6dc914 Dropdown on hover. 2016-04-28 09:24:29 +02:00
Pierre
141cbcd1c0 [WIP] Dropdown on hover. 2016-04-28 09:24:29 +02:00
Olivier Lambert
e340d2d3f5 remove useless import 2016-04-27 18:27:57 +02:00
Olivier Lambert
436d5a3a66 remove dup message keys 2016-04-27 18:27:57 +02:00
Julien Fontanet
3e04fd4790 feat: initial ACLs handling 2016-04-27 16:44:01 +02:00
Julien Fontanet
3f6d149f9d feat(reducer/combineActionHandlers): perf for n=1 & common errors
Warnings when:

- no handlers defined
- there is an handler for the action type `type` (likely an error)

There is now an optimized implementation when there is only one handler.
2016-04-27 16:44:01 +02:00
Olivier Lambert
977fc7832a initial network tab added for host 2016-04-27 15:23:39 +02:00
Olivier Lambert
2e2f0e2e3d handle basic log removal 2016-04-27 14:59:40 +02:00
Julien Fontanet
5628beee72 fix(selectors/createCollectionWrapper): handle removed props 2016-04-27 14:14:53 +02:00
Olivier Lambert
a88fea560b fix indentation 2016-04-26 18:14:37 +02:00
Olivier Lambert
5832345b96 Merge pull request #902 from vatesfr/v5-hosts
V5 hosts view
2016-04-26 15:57:32 +02:00
Olivier Lambert
d3d2daa12f use createFinder 2016-04-26 15:39:58 +02:00
Julien Fontanet
f6f90982f4 feat(selectors/createPager): selector creator to return a page of items 2016-04-26 15:36:21 +02:00
Olivier Lambert
98323b08f0 add host console 2016-04-26 15:31:13 +02:00
Olivier Lambert
8f3112a5e2 advanced tab completed 2016-04-26 15:31:12 +02:00
Olivier Lambert
3408bd41ad additionnal host info 2016-04-26 15:31:12 +02:00
Olivier Lambert
68e12e86c1 add start time in host view 2016-04-26 15:31:12 +02:00
Olivier Lambert
890e0b4906 add control domain VM 2016-04-26 15:31:12 +02:00
Olivier Lambert
3aca7c7ae5 more working host tabs 2016-04-26 15:31:11 +02:00
Olivier Lambert
e8077ddbc5 initial work on host view 2016-04-26 15:31:11 +02:00
Julien Fontanet
6e04907357 feat(selectors/createFinder): selector creator to find an item in a collection 2016-04-26 15:26:28 +02:00
Greenkeeper
3d8c9a99fe chore(package): update modular-css to version 0.18.0 (#895)
https://greenkeeper.io/
2016-04-20 08:05:43 +01:00
Greenkeeper
730768705b chore(package): update modular-css to version 0.17.1 (#890)
http://greenkeeper.io/
2016-04-19 10:04:12 +01:00
Julien Fontanet
13f75a37ab CSS: remove 60em min-width on .xo-content. 2016-04-18 15:58:43 +01:00
Julien Fontanet
f74c69ea6f Disable Bootstrap flex for now as it is broken in responsive mode. 2016-04-18 15:57:56 +01:00
Greenkeeper
fe7be0f518 chore(package): update react-key-handler to version 0.2.0 (#886)
http://greenkeeper.io/
2016-04-18 09:33:30 +01:00
Olivier Lambert
2f0e656c45 menu: move remote in settings 2016-04-15 14:44:53 +02:00
Julien Fontanet
de489b799b Fix npm run build. 2016-04-14 19:12:25 +02:00
Julien Fontanet
6250ef49b6 Minor change in VM/Snapshots. 2016-04-14 19:12:25 +02:00
Julien Fontanet
afdab8dcde Fix About page. 2016-04-14 19:12:25 +02:00
Olivier Lambert
8e1d39f37f navbar style 2016-04-14 18:45:18 +02:00
Julien Fontanet
2a8c346a65 Add basic Page not found. 2016-04-14 18:06:56 +02:00
Olivier Lambert
71e431e744 better dashboard 2016-04-14 17:45:00 +02:00
Julien Fontanet
265cb75d70 Dashboard/Overview: 10 top SRs by size. 2016-04-14 16:51:29 +02:00
Olivier Lambert
4d0470838a move storage list to health 2016-04-14 16:41:05 +02:00
Olivier Lambert
d4ed3aeac0 better dashboard 2016-04-14 16:14:35 +02:00
Julien Fontanet
89fa89fe98 utils/@routes(): TODO add support for function childRoutes. 2016-04-14 16:02:05 +02:00
Julien Fontanet
2d663f0ac5 Remove unused import. 2016-04-14 15:58:38 +02:00
Julien Fontanet
bc9b3f1c5c utils/@routes(): accepts a plain object for child routes. 2016-04-14 15:48:38 +02:00
Julien Fontanet
2f0a46a46d utils/@routes(): use it AMAP. 2016-04-14 15:45:27 +02:00
Julien Fontanet
9559604d1e menu: Remove home special case. 2016-04-14 15:45:27 +02:00
Julien Fontanet
b779ab9bc5 utils/@routes(): accepts a subpath as index route. 2016-04-14 15:45:26 +02:00
Julien Fontanet
481943051c Tooltip component. 2016-04-14 15:45:26 +02:00
Olivier Lambert
cb49b7a906 fix styles 2016-04-14 11:55:14 +02:00
Olivier Lambert
090e4b3117 working health view 2016-04-14 10:35:58 +02:00
Olivier Lambert
9a40d5f784 style fixes 2016-04-13 19:49:59 +02:00
Olivier Lambert
1485637c6d more dashboard work 2016-04-13 17:52:54 +02:00
Olivier Lambert
b0ec8e26e8 sr table on dashboard 2016-04-13 13:53:01 +02:00
Olivier Lambert
e4c12e08cb sr panel on dashboard view 2016-04-13 12:55:24 +02:00
Olivier Lambert
a1675745b5 first charts on Dashboard view 2016-04-13 12:23:38 +02:00
Olivier Lambert
40beb5b104 dashboard work 2016-04-13 11:26:07 +02:00
Olivier Lambert
e49e2f51c2 more work on dashboard 2016-04-12 22:57:45 +02:00
Olivier Lambert
99669f2678 add missing files 2016-04-12 19:03:40 +02:00
Olivier Lambert
9aa88b9dad initial work on dashboard 2016-04-12 19:02:53 +02:00
Julien Fontanet
10de29795a settings/servers: implement edition. 2016-04-12 18:49:25 +02:00
Julien Fontanet
c4767e74f4 Initial server view. 2016-04-12 18:16:07 +02:00
Julien Fontanet
19f8666e1e Use Babel transforms to optimize React in prod. 2016-04-12 18:16:07 +02:00
Julien Fontanet
2e2bbdf0d7 Update gulp-csso to v2. 2016-04-12 18:16:07 +02:00
Olivier Lambert
973bee0ffa fix links to submenus 2016-04-12 18:05:20 +02:00
Olivier Lambert
9470cdf774 better wording 2016-04-12 18:00:11 +02:00
Olivier Lambert
ab198ea60b add import entry 2016-04-12 17:56:59 +02:00
Olivier Lambert
c78f4bb6d2 add dashboard submenu 2016-04-12 17:47:38 +02:00
Olivier Lambert
ff054ca47f Add menu 2016-04-12 17:28:24 +02:00
Olivier Lambert
30cc804022 backup submenu 2016-04-12 17:13:35 +02:00
Olivier Lambert
890d733bf8 snapshot name edition 2016-04-12 15:19:05 +02:00
Olivier Lambert
a554a9c4a1 add editable vdi name and description 2016-04-12 15:16:40 +02:00
Olivier Lambert
9ef212937d log view style 2016-04-12 14:05:30 +02:00
Pierre Donias
82d9c53f3e Left side menu and navbar. (#869)
Merge PR #869 from @pdonias.
2016-04-08 18:34:20 +02:00
Julien Fontanet
d9d91c4953 Update react/react-dom to v15 and react-intl to v2. 2016-04-08 15:16:55 +02:00
Julien Fontanet
dd5f5282e0 Remove special uglify setting for Angular. 2016-04-07 17:44:53 +02:00
Julien Fontanet
cc116defc6 Remove unused browser override ws.js. 2016-04-07 17:44:00 +02:00
Julien Fontanet
90c755e120 Remove unused browserify-plain-jade. 2016-04-07 17:43:40 +02:00
Julien Fontanet
85d1c80581 Move common modules out of src/node_modules. 2016-04-07 15:03:14 +02:00
Julien Fontanet
5bb04d3857 reducers: Fix REMOVE_OBJECTS handling. 2016-04-07 15:03:13 +02:00
Olivier Lambert
dde8404242 fix average computing on sparkline graphs 2016-04-07 14:00:10 +02:00
Julien Fontanet
7940bd2dcc Merge pull request #858 from vatesfr/abhamonr-vm-stats-only-when-vm-running
Get stats only when vm running.
2016-04-07 13:45:30 +02:00
Julien Fontanet
da48d117a0 Merge pull request #872 from vatesfr/pierre-v5-vm-state-icon
VM: state icon in front of VM's name.
2016-04-07 10:16:23 +02:00
Pierre
ffc74967fc VM: State icon in front of VM's name 2016-04-07 10:12:46 +02:00
wescoeur
c7388d5836 Get stats only when vm running. 2016-04-06 16:24:31 +02:00
Julien Fontanet
ee4b8fc66f Merge pull request #868 from vatesfr/pierre-v5-add-multiple-tags
Tags: Adding a tag does not remove the input field
2016-04-06 15:41:55 +02:00
Julien Fontanet
c73f22ca45 home: refactor filtering on top of selectors/createFilter(). 2016-04-06 15:35:45 +02:00
Julien Fontanet
bafa053fd1 vm/logs: initial listing. 2016-04-06 14:55:44 +02:00
Pierre
a00406d2b3 Tags: Adding a tag should keep the input field on 2016-04-06 11:59:10 +02:00
Julien Fontanet
df91e17dc6 vm/disks: use more variables. 2016-04-06 11:01:05 +02:00
Julien Fontanet
7c6eeababc vm/disks: add SR name. 2016-04-06 10:52:34 +02:00
Julien Fontanet
911a5067f9 Factorize selectors. 2016-04-06 10:47:35 +02:00
Julien Fontanet
bfa0fe9c51 editable/Text: initial implementation. 2016-04-05 15:40:16 +02:00
Julien Fontanet
7dafe31d51 react-novnc: Ungrab keyboard/mouse on mouse leave. 2016-04-04 15:44:26 +02:00
Olivier Lambert
1271ecedb4 Merge pull request #859 from vatesfr/pierre-v5-icon-fixed-width
Icon: `fixedWidth` attribute
2016-04-01 19:17:50 +02:00
Pierre
80be18068f Icon: lodash/isInteger instead of Number.isInteger 2016-04-01 18:31:16 +02:00
Julien Fontanet
ecbf2c0958 utils/BlockLink: To use for a block link :) 2016-04-01 18:16:56 +02:00
Pierre
cfb84b677f Icon: fixedWidth attribute 2016-04-01 18:12:33 +02:00
Olivier Lambert
dfbd7a5d76 Merge pull request #857 from vatesfr/abhamonr-fix-select-vm-stats
Fix select vm stats.
2016-04-01 17:30:27 +02:00
wescoeur
8e157f8ff7 Fix select vm stats. 2016-04-01 17:16:04 +02:00
Olivier Lambert
c75580e852 style fixes 2016-04-01 17:03:21 +02:00
Olivier Lambert
e21934fd55 more work on VM advanced tab 2016-04-01 17:03:21 +02:00
Julien Fontanet
844f1609c8 utils/@autobind: bind a method at first call. 2016-04-01 17:00:03 +02:00
Julien Fontanet
6f7de28672 Vm/Console: disable for non-running VMs. 2016-04-01 16:42:27 +02:00
Olivier Lambert
247212d768 update advanced view 2016-04-01 16:31:11 +02:00
Olivier Lambert
6592b880ea reorder stuff 2016-04-01 16:31:11 +02:00
Julien Fontanet
e27a4bf119 Merge pull request #855 from vatesfr/abhamonr-vms-stats-stateful
Stateful component vms stats.
2016-04-01 16:29:23 +02:00
wescoeur
dce5a83093 Stateful component vms stats.
Delay of stats interval is used to get stats.
2016-04-01 16:26:01 +02:00
Julien Fontanet
78c70f35a1 Merge pull request #856 from vatesfr/pierre-v5-col-offset
Added offset management in `Col` component.
2016-04-01 16:25:26 +02:00
Pierre
f4b84a0902 Added offset management in Col component 2016-04-01 16:15:46 +02:00
Julien Fontanet
b16237b514 Vm/Stats: Fix translation of select. 2016-04-01 15:37:26 +02:00
Olivier Lambert
b0077539fa Merge pull request #854 from vatesfr/abhamonr-vms-charts-tooltips
Tooltips on vms charts.
2016-04-01 15:21:14 +02:00
Olivier Lambert
63602216eb using icon component 2016-04-01 14:38:58 +02:00
Olivier Lambert
2f93935009 more links 2016-04-01 14:30:21 +02:00
Olivier Lambert
c0a0b653c1 add links and fix icons 2016-04-01 14:25:41 +02:00
wescoeur
24d6354467 Tooltips on vms charts. 2016-04-01 13:03:02 +02:00
Julien Fontanet
54a07469fd Fix granularity on stats tab. 2016-04-01 12:49:55 +02:00
Olivier Lambert
12f37bead1 translate inside option 2016-04-01 12:42:55 +02:00
Olivier Lambert
8d76dc2511 tab styles 2016-04-01 12:01:52 +02:00
Olivier Lambert
8ef7d6defc more i18n 2016-04-01 11:49:35 +02:00
Olivier Lambert
2f2cbbe3f0 various improvements 2016-04-01 11:05:27 +02:00
Julien Fontanet
2662ac719b Fix tabs display. 2016-04-01 10:50:38 +02:00
Olivier Lambert
1b0fe6e847 center console 2016-04-01 10:29:04 +02:00
Olivier Lambert
9fa9e26324 disk tab 2016-04-01 10:25:56 +02:00
Julien Fontanet
cb434df099 Move prod deps to dev deps. 2016-04-01 10:21:04 +02:00
Julien Fontanet
e3cb3002fe Replace react-tabs with Bootstrap styles + router. 2016-04-01 10:21:00 +02:00
Olivier Lambert
8b2a09b522 better tag placement 2016-03-31 18:44:33 +02:00
Olivier Lambert
03265d2545 stats select translation 2016-03-31 18:10:16 +02:00
Olivier Lambert
9b25e07b5e sort tags and fix snapshot icon 2016-03-31 17:57:56 +02:00
Julien Fontanet
60a21ca58d Merge pull request #853 from vatesfr/abhamonr-vms-charts-time-labels
Intl labels on vms charts.
2016-03-31 17:52:05 +02:00
wescoeur
05172492ef Intl labels on vms charts. 2016-03-31 17:44:42 +02:00
Julien Fontanet
a9c089a994 Merge pull request #851 from vatesfr/pierre-v5-action-bar
VM action bar (without handlers) with react-bootstrap-4
2016-03-31 17:37:18 +02:00
Julien Fontanet
988d018c8e Merge pull request #852 from vatesfr/pierre-v5-fix-sparklines-proptype
Fixed Sparklines propType: array --> object
2016-03-31 17:35:53 +02:00
Pierre
853c611fde Fixed Sparklines propType: array --> object 2016-03-31 17:30:43 +02:00
Pierre
0886f5335f DropdownButton instead of SplitButton (custom width). 2016-03-31 17:22:00 +02:00
Pierre
024d481a4d Using react-bootstrap-4. VM action bar done (no handlers). Minor fixes. 2016-03-31 17:22:00 +02:00
Pierre
b80de1af95 ERR action-bar: DropdownButton incompatible with React 15 2016-03-31 17:21:59 +02:00
Julien Fontanet
da44268f0d ReactNoVnc: remove unnecessary logs. 2016-03-31 17:16:15 +02:00
Julien Fontanet
b921a3ed8e Split the VM view. 2016-03-31 17:15:23 +02:00
Julien Fontanet
d2c9c824f9 Declare routes directly on component. 2016-03-31 16:41:13 +02:00
Olivier Lambert
21c255051c better console panel style 2016-03-31 16:39:30 +02:00
Olivier Lambert
dccf09c5dd charts improvements 2016-03-31 16:12:57 +02:00
Julien Fontanet
0772a17e4c grid/Col: use xs for smallSize. 2016-03-31 15:21:26 +02:00
Olivier Lambert
644ee782d2 fix style issues 2016-03-31 15:11:40 +02:00
Olivier Lambert
ea180cc415 fix col sizes 2016-03-31 15:11:10 +02:00
Olivier Lambert
68e3c4e6ed change stats titles 2016-03-31 15:07:59 +02:00
Julien Fontanet
47b1c2d680 grid/Col: now expects smallSize, mediumSize or largeSize. 2016-03-31 15:07:32 +02:00
Olivier Lambert
2e513043b7 Merge pull request #850 from vatesfr/abhamonr-vms-graphs-titles
Abhamonr vms graphs titles
2016-03-31 14:16:40 +02:00
wescoeur
ea942635f7 VMs graphs titles. 2016-03-31 13:12:41 +02:00
wescoeur
8a5f5cc673 Display spinner icon when stats granularity is selected. 2016-03-31 12:51:29 +02:00
Julien Fontanet
6cc673035d Work around babel-eslint bug. 2016-03-31 11:36:42 +02:00
Olivier Lambert
02d717e5a8 better graph style 2016-03-31 11:35:12 +02:00
Julien Fontanet
77d83b06bd Merge pull request #848 from vatesfr/abhamonr-vm-stats-various-time-steps
Possibility to select time interval on vm stats.
2016-03-31 11:19:57 +02:00
wescoeur
e3d8eabb05 Possibility to select time interval on vm stats. 2016-03-31 11:16:49 +02:00
Julien Fontanet
0596b0106f Handle removeObjects action. 2016-03-30 18:07:40 +02:00
Julien Fontanet
765bbd90fc Install ghooks. 2016-03-30 18:01:51 +02:00
Julien Fontanet
3799902a8a More efficient sort of snapshots. 2016-03-30 17:43:42 +02:00
Julien Fontanet
ca1dbb4556 Initial VM console. 2016-03-30 17:37:37 +02:00
Julien Fontanet
df551d457c Merge pull request #847 from vatesfr/abhamonr-chartist-graphs
VM's graphs. (Chartist)
2016-03-30 16:57:17 +02:00
wescoeur
278d518d8f VM's graphs. (Chartist) 2016-03-30 16:53:20 +02:00
Olivier Lambert
b06fa191f7 prepare limit space in vm advanced tab 2016-03-30 16:02:34 +02:00
Olivier Lambert
13d73f6f27 add translations 2016-03-30 15:54:49 +02:00
Olivier Lambert
d59af117c0 start work of Advanced vm tab 2016-03-30 15:51:23 +02:00
Olivier Lambert
69efd85ad6 check if VBD is a cd drive 2016-03-30 12:33:54 +02:00
Olivier Lambert
ead51787a8 Merge pull request #846 from vatesfr/pierre-v5-tags
Add and remove tag actions. Tag UI improvements.
2016-03-30 12:23:09 +02:00
Olivier Lambert
4bf0fee20d protection again counting twice or more a VDI size 2016-03-30 12:09:04 +02:00
Pierre
209693ee3f Add and remove tag actions. Tag UI improvements. Icon handles lg size. 2016-03-30 11:48:01 +02:00
Olivier Lambert
796d4f5b08 not pill in snapshots tab when no snapshots 2016-03-30 11:37:55 +02:00
Olivier Lambert
96914c9901 compute total vm disk space 2016-03-30 11:29:37 +02:00
Olivier Lambert
9e6073bf56 improve translation 2016-03-30 11:00:16 +02:00
Olivier Lambert
f8ad58159c minor i18n fix 2016-03-29 23:30:34 +02:00
Olivier Lambert
ede4a02315 various UI improvements 2016-03-29 22:43:06 +02:00
Olivier Lambert
e25faba990 basic VM disk tab 2016-03-29 22:35:27 +02:00
Olivier Lambert
4ccf272e53 add snapshot pill in tab name and more i18n 2016-03-29 21:08:58 +02:00
Olivier Lambert
5ad0951db3 improve snapshot view 2016-03-29 20:56:18 +02:00
Olivier Lambert
f72fcb76e3 start to work on snapshot view 2016-03-29 18:58:36 +02:00
Olivier Lambert
8ca00c81b2 display network name_label in vm network view 2016-03-29 18:02:55 +02:00
Olivier Lambert
5c7f87b8ae case when not VM network interface at all 2016-03-29 16:38:16 +02:00
Julien Fontanet
9f4f7ec88c utils/If: experimental component. 2016-03-29 16:14:54 +02:00
Olivier Lambert
ba7676f778 using keys for array 2016-03-29 16:01:46 +02:00
Julien Fontanet
ac248c32bb Fix handling of empty VM stats. 2016-03-29 15:44:46 +02:00
Julien Fontanet
dd6a3e8535 Update babel-eslint to 6.0.0. 2016-03-29 15:32:20 +02:00
Julien Fontanet
69b538cfd6 Remove unused redux-promise. 2016-03-29 15:32:20 +02:00
Julien Fontanet
e248c22f4b Remove unused redux-router. 2016-03-29 15:32:20 +02:00
Olivier Lambert
eff3c43483 better network page 2016-03-29 15:31:56 +02:00
Olivier Lambert
1cbfc3ccd4 various updates and fixes 2016-03-29 14:58:23 +02:00
Olivier Lambert
2fa72838f9 add sparklines to console view 2016-03-28 21:48:56 +02:00
Olivier Lambert
a559ec1fda better vm network translations 2016-03-28 21:42:13 +02:00
Olivier Lambert
8ddbc8b1fb minor network improvements 2016-03-28 21:21:59 +02:00
Olivier Lambert
c650a43d38 add translations 2016-03-28 21:12:30 +02:00
Olivier Lambert
ab4cc20c8c better network tab 2016-03-28 21:09:21 +02:00
Olivier Lambert
30d613ff04 minor vm action reordering 2016-03-28 20:09:41 +02:00
Olivier Lambert
b93da5a281 tag minor style modification 2016-03-28 18:06:57 +02:00
Olivier Lambert
9e2cf67a93 mockup layout for console view 2016-03-28 12:52:00 +02:00
Olivier Lambert
e87720ffdd minor style modification 2016-03-25 23:22:43 +01:00
Olivier Lambert
5108ed7da5 Merge pull request #842 from vatesfr/pierre-v5-tags
`Tags` component, basic version
2016-03-25 23:21:05 +01:00
Olivier Lambert
77936d86f2 position tags in VM view 2016-03-25 23:10:30 +01:00
Olivier Lambert
cbe8927d73 move tag style in main css file + PR comments 2016-03-25 22:57:47 +01:00
Pierre
47a7e98da8 Tags component 2016-03-25 22:56:36 +01:00
Olivier Lambert
d4f27cd2e0 default large buttons 2016-03-25 22:39:35 +01:00
Olivier Lambert
c355ad7a86 css fix 2016-03-25 19:16:48 +01:00
Julien Fontanet
c8c9ec081d Remove incorrect divs in VM page. 2016-03-25 19:12:46 +01:00
Julien Fontanet
1289e46401 Fix xo-sparlines/CpuSparkLines proptypes. 2016-03-25 19:12:46 +01:00
Julien Fontanet
0b54292130 VIFs in VM view. 2016-03-25 19:04:54 +01:00
Olivier Lambert
d1e2b91116 comment ationbar button to merge 2016-03-25 18:49:09 +01:00
Julien Fontanet
cd96b3e8f6 Merge pull request #844 from vatesfr/olivierlambert-clipboard
5.x clipboard
2016-03-25 18:19:27 +01:00
Olivier Lambert
9119f4d06f style fix 2016-03-25 18:16:40 +01:00
Olivier Lambert
4e6ccf2c81 reverse order on general tab 2016-03-25 18:08:18 +01:00
Olivier Lambert
6294e43762 various improvements 2016-03-25 18:08:18 +01:00
Olivier Lambert
feca78e55d reorder stuff and add clipboard support for UUID 2016-03-25 18:08:17 +01:00
Olivier Lambert
0c86526ad2 better sparklines styles 2016-03-25 18:07:33 +01:00
Olivier Lambert
9fd1d26067 minor graph improvements 2016-03-25 17:44:55 +01:00
Julien Fontanet
5ec02078d1 Merge pull request #843 from vatesfr/abhamonr-vm-charts
Vm Sparklines (Cpu, ram, vif, xvd)
2016-03-25 16:02:26 +01:00
wescoeur
012c6f3d41 Vm Sparklines (Cpu, ram, vif, xvd) 2016-03-25 15:41:44 +01:00
Olivier Lambert
8e89d492fc fix style 2016-03-25 14:55:46 +01:00
Olivier Lambert
69fe2f0443 date stuff 2016-03-25 14:52:14 +01:00
Julien Fontanet
ebd7f4ae1b Ignore Redux dev tools in production. 2016-03-25 12:40:46 +01:00
Julien Fontanet
f9ca4f339e Name components for better error messages. 2016-03-25 12:40:46 +01:00
Olivier Lambert
5919020e1c add cpu weight 2016-03-25 12:26:27 +01:00
Olivier Lambert
9d09c2356d add VM style 2016-03-25 12:08:24 +01:00
Julien Fontanet
3cfb597fc6 Properly forward props arg to super(). 2016-03-25 11:39:24 +01:00
Julien Fontanet
03f2da19e5 utils#Debug: component printing an object in JSON. 2016-03-25 11:39:24 +01:00
Julien Fontanet
951e62d984 Do not declare global variable osToFamily in utils. 2016-03-25 11:39:23 +01:00
Olivier Lambert
146039c4c5 translate UUID and use definitions for Advanced tab 2016-03-25 11:37:13 +01:00
Olivier Lambert
98a216fdb9 better translations 2016-03-25 11:00:45 +01:00
Olivier Lambert
28ea09a0c4 More translations 2016-03-24 23:32:59 +01:00
Olivier Lambert
a98a772360 remove trailing comma 2016-03-24 23:11:43 +01:00
Olivier Lambert
4f1da8a24b more translations 2016-03-24 23:11:03 +01:00
Olivier Lambert
cb83e71f2b better VM view 2016-03-24 19:29:49 +01:00
Olivier Lambert
646d174616 VM view stuff 2016-03-24 19:17:59 +01:00
Julien Fontanet
34cf78fd33 Require npm >=3. 2016-03-24 18:55:34 +01:00
Olivier Lambert
457e1bee2f fix osfamily 2016-03-24 18:45:02 +01:00
Olivier Lambert
6b95c63c1e style and some fixes 2016-03-24 18:25:46 +01:00
Julien Fontanet
e965f222db Merge pull request #841 from vatesfr/pierre-v5-action-bar
Action bar fixes
2016-03-24 18:17:50 +01:00
Olivier Lambert
d1591bc01c fix style 2016-03-24 17:36:12 +01:00
Olivier Lambert
a72846be7a uncomment icons 2016-03-24 17:36:12 +01:00
Julien Fontanet
5072661369 utils#{format,parse}Size() 2016-03-24 17:30:04 +01:00
Olivier Lambert
5417a83662 Add icon distro display 2016-03-24 17:09:28 +01:00
Pierre
23be006932 Removed console button in action bar 2016-03-24 16:54:33 +01:00
Pierre
e28553767e Label and icon are required for an ActionBar 2016-03-24 16:53:19 +01:00
Julien Fontanet
c1f64c043d Merge pull request #836 from vatesfr/pierre-v5-action-bar
v5 action bar
2016-03-24 16:50:02 +01:00
Pierre
9e6d0183d4 Button component. 2016-03-24 16:44:57 +01:00
Pierre
8dc7f3fb9e Better propTypes for ActionBar 2016-03-24 16:44:56 +01:00
Pierre
ac7b3b9b67 Icons CSS. propTypes on ActionBar and Icon. ActionBar in separate file. 2016-03-24 16:44:56 +01:00
Pierre
af0ebba5db Default icon. Lint fixes 2016-03-24 16:44:56 +01:00
Pierre
b454709e5e xo-icon- prefix in icon component. Default icon size: 1 2016-03-24 16:44:56 +01:00
Pierre
78684607bd Added VM actions icons 2016-03-24 16:44:56 +01:00
Pierre
3790cad5e5 Editable icon size. VM actions messages. 2016-03-24 16:44:56 +01:00
Pierre
77b7c091a8 Icon component 2016-03-24 16:44:56 +01:00
Pierre
450ad62fa9 ActionButton component. Added delete button in VM view. 2016-03-24 16:44:56 +01:00
Julien Fontanet
1d138c33a4 Col/Row can take a className prop. 2016-03-24 16:32:20 +01:00
Julien Fontanet
fb2a0e4a1e Add font-mfizz. 2016-03-24 16:18:04 +01:00
Julien Fontanet
da3db0b0f9 Cache node_modules on Travis. 2016-03-24 15:54:08 +01:00
Julien Fontanet
bc8aaadd90 Initial VM stats. 2016-03-24 15:39:18 +01:00
Olivier Lambert
e6054a4971 use Col from grid component 2016-03-24 13:44:54 +01:00
Olivier Lambert
26548e1929 using grid component 2016-03-24 13:37:45 +01:00
Julien Fontanet
4424cf8190 Initial grid component. 2016-03-24 12:53:19 +01:00
Julien Fontanet
67c1aacd54 @propTypes decorator. 2016-03-24 12:53:19 +01:00
Olivier Lambert
39b046f18b center properly 2016-03-24 12:50:29 +01:00
Julien Fontanet
d630f04872 Add missing dependency vinyl. 2016-03-24 11:43:22 +01:00
Julien Fontanet
35403c87bd Add missing dependency jsonrcp-websocket-client. 2016-03-24 11:30:27 +01:00
Julien Fontanet
f2b247e042 No tests for now: remove unused deps. 2016-03-24 11:16:04 +01:00
Julien Fontanet
94022bd9f2 Add missing dependency readable-stream. 2016-03-24 11:16:04 +01:00
Julien Fontanet
ccb2abb950 Remove support for Node < 4. 2016-03-24 11:16:00 +01:00
Julien Fontanet
cbafc15292 Various updates. 2016-03-24 10:52:49 +01:00
Julien Fontanet
e12c52294a Various updates. 2016-03-24 10:46:51 +01:00
Olivier Lambert
63b529da00 add react tabs 2016-03-24 10:06:51 +01:00
Olivier Lambert
e4be2fd19e add icons 2016-03-23 19:02:19 +01:00
Julien Fontanet
269bf4feec Various updates. 2016-03-23 18:40:56 +01:00
Olivier Lambert
9d4c4a1e2b translate tabs 2016-03-23 18:38:52 +01:00
Olivier Lambert
2e41fdcb41 More work on VM view 2016-03-23 18:01:32 +01:00
Olivier Lambert
2e48218623 conforming to the planned template display 2016-03-23 16:15:01 +01:00
Olivier Lambert
8576a54056 start the VM in the action bar 2016-03-23 16:02:30 +01:00
Julien Fontanet
79d924f920 Various updates. 2016-03-23 15:23:08 +01:00
Julien Fontanet
305beb3af8 Fix indent. 2016-03-23 13:00:04 +01:00
Julien Fontanet
06fceded14 Various updates. 2016-03-23 12:46:30 +01:00
Olivier Lambert
0eadfd5a58 Merge pull request #835 from vatesfr/abhamonr-fix-plugin-multiple-users-groups-loading
Avoid multiple users/groups loading in plugins view. (fix vatesfr/xo-…
2016-03-23 12:11:44 +01:00
wescoeur
eea34a4f6c Avoid multiple users/groups loading in plugins view. (fix vatesfr/xo-web#829) 2016-03-23 12:03:56 +01:00
Julien Fontanet
500ec36522 Various updates. 2016-03-23 11:21:47 +01:00
Julien Fontanet
ca525bd08c 4.15.1 2016-03-22 15:28:15 +01:00
Olivier Lambert
ac2ffc4586 Fix #830 2016-03-22 14:35:57 +01:00
Olivier Lambert
5781269557 Remove old message about not supported SMB remote for delta 2016-03-22 13:32:19 +01:00
Olivier Lambert
e4422b9fe7 Display only permitted SR for VM copy 2016-03-22 12:14:25 +01:00
Olivier Lambert
269f76d546 Fix #832 2016-03-22 11:47:34 +01:00
Julien Fontanet
540e3f0aaa 4.15.0 2016-03-21 16:50:22 +01:00
Olivier Lambert
5f64ae28e0 Merge pull request #800 from vatesfr/abhamonr-delta-backup-on-smb
SMB can be used with delta backups.
2016-03-21 09:37:40 +01:00
Julien Fontanet
ece364c823 Various updates. 2016-03-18 18:09:44 +01:00
Olivier Lambert
f669f64fcb add changelog 2016-03-18 16:17:36 +01:00
Julien Fontanet
2a53ed93c4 Various updates. 2016-03-18 15:00:33 +01:00
Julien Fontanet
2b731fb30c Various updates. 2016-03-18 12:14:06 +01:00
Julien Fontanet
be2db2dd8e Fix immediately spelling (thx @Danp2). 2016-03-18 10:02:30 +01:00
Julien Fontanet
09c08df1b9 Various updates. 2016-03-18 09:17:49 +01:00
Olivier Lambert
9ccd3438ad Fix #821 2016-03-17 19:15:35 +01:00
Julien Fontanet
393bcbcca5 Various updates. 2016-03-17 18:14:57 +01:00
Julien Fontanet
7fac0958b8 Various updates. 2016-03-17 16:35:45 +01:00
Olivier Lambert
c6a0874b3b Merge pull request #758 from vatesfr/marsaudf-backup-ui-fixes
Fixed backup remote lists
2016-03-17 14:23:56 +01:00
Olivier Lambert
9c80470185 Merge pull request #820 from vatesfr/pierre-server-version
Added server version in About view (See #807)
2016-03-17 14:21:56 +01:00
Julien Fontanet
2034445f5b Various updates. 2016-03-17 12:13:29 +01:00
Pierre
fd8da5ffba Added server version in About view 2016-03-17 10:52:32 +01:00
Olivier Lambert
e987af87f6 Merge pull request #809 from vatesfr/abhamonr-recursive-plugins-config
Complex configurations plugins. (recursion, array of objects...)
2016-03-17 10:31:31 +01:00
Pierre
0074cc3933 Fixed refresh bug. 2016-03-17 09:51:53 +01:00
Pierre
5f2ce89316 Fixed Object/String/Array display in plugin config. 2016-03-17 09:51:53 +01:00
Pierre
60492c48a6 Typo fix. 2016-03-17 09:51:53 +01:00
Pierre
eed2d70017 Better array display when items are objects 2016-03-17 09:51:53 +01:00
Pierre
b859adaa8c Fixes 2016-03-17 09:51:53 +01:00
Pierre
89a587f9ae enum handling 2016-03-17 09:51:53 +01:00
wescoeur
fb56bcff80 Complex configurations plugins. (recursion, array of objects...) 2016-03-17 09:51:53 +01:00
Fabrice Marsaud
99eb6907dd updater will block nav after 1 min out 2016-03-16 16:32:52 +01:00
Fabrice Marsaud
3743fad899 when updater-blocked, any nav attempt will retry connection 2016-03-16 16:18:02 +01:00
Olivier Lambert
c1e59a7e03 Merge pull request #818 from vatesfr/pierre-feedback-when-error-on-sr
Feedback when disconnecting a host from an SR does not work (See #810)
2016-03-16 15:54:11 +01:00
Pierre
b34dee1f83 Error message formatting: leading capital and trailing period. 2016-03-16 15:45:36 +01:00
Pierre
6edd65ad8f xo.pbd.disconnect instead of xoApi.call(...). Better error notification. 2016-03-16 15:01:29 +01:00
Olivier Lambert
0959ca6a40 Merge pull request #813 from vatesfr/pierre-network-management
Host & pool views: better network management (See #805)
2016-03-16 10:57:34 +01:00
Pierre
1287fa2cd0 Allowed network creation without PIF 2016-03-16 10:50:47 +01:00
Pierre
a5a07f250d UI improvements 2016-03-16 10:46:08 +01:00
Pierre
089fb526f5 IP configuration: DHCP & No IP. UI fixes. 2016-03-16 10:46:08 +01:00
Pierre
af58b7593a Configure PIF IP. UI fixes. 2016-03-16 10:46:07 +01:00
Pierre
d4508b25ce Only physical PIFs should be shown when creating network 2016-03-16 10:46:07 +01:00
Pierre
9edc218eaa Delete network 2016-03-16 10:46:07 +01:00
Olivier Lambert
3790f753aa Merge pull request #801 from vatesfr/pierre-pool-networks
Pool view: Interface panel and network creation (See #226)
2016-03-16 10:45:06 +01:00
Julien Fontanet
82b30d8388 Do not use nice-pipe (too buggy). 2016-03-10 17:23:04 +01:00
Julien Fontanet
e9a0dc7826 Various updates. 2016-03-10 17:01:16 +01:00
wescoeur
8ce3a4f904 SMB can be used with delta backups. 2016-03-09 17:12:15 +01:00
Julien Fontanet
1d42e9c348 Disable tests for now. 2016-03-09 14:29:27 +01:00
Julien Fontanet
2340a6bc37 Various updates. 2016-03-09 14:28:18 +01:00
Pierre
be0b9c7e53 Removed log 2016-03-09 10:38:45 +01:00
Pierre
6d75cd9025 Minor fix 2016-03-09 10:33:09 +01:00
Pierre
345d6f369e network.create instead of createNetwork for host and pool 2016-03-09 10:33:09 +01:00
Pierre
959ea86d85 Pool view: Interface panel and network creation 2016-03-09 10:33:09 +01:00
Olivier Lambert
b67a99af3d Add types for ISO SRs 2016-03-04 13:36:35 +01:00
Olivier Lambert
fa3b848d40 Merge pull request #799 from vatesfr/pierre-add-smb-sr
UI to add an SMB SR (user and password inputs) (Fix #731)
2016-03-03 18:25:55 +01:00
Pierre
0f971e9e7d Minor fix. 2016-03-03 17:55:43 +01:00
Pierre
c17f76c009 SMB case fix. 2016-03-03 17:50:23 +01:00
Pierre
bf23b5d295 Enabled Create button and removed search button when SMB. 2016-03-03 17:39:18 +01:00
Pierre
09c7256d42 UI to add an SMB SR (user and password inputs) 2016-03-03 16:58:46 +01:00
Olivier Lambert
eaee8a2fbb Merge pull request #798 from vatesfr/pierre-new-vm-from-pool-enhancement
New VM on pool: display all SRs (Fix #790)
2016-03-03 16:41:45 +01:00
Pierre
3b18dd67be Compatibility with self service 2016-03-03 16:16:03 +01:00
Pierre
c3f87b4248 Explicit message in summary 2016-03-03 15:27:07 +01:00
Julien Fontanet
f6b91ad652 Update deps. 2016-03-03 14:54:29 +01:00
Pierre
1c79edc52f Detection of incompatible SRs 2016-03-03 13:05:26 +01:00
Julien Fontanet
fe2dfd0e8f Merge branch 'stable' into next-release 2016-03-03 13:03:18 +01:00
Olivier Lambert
fa6056c1b1 Add unknown state 2016-03-03 12:26:06 +01:00
Olivier Lambert
d5762c7ad8 limit VDI form for admin users 2016-03-03 12:26:06 +01:00
Julien Fontanet
d9c9dd2a4f Welcome message in the issue template 2016-03-03 11:31:27 +01:00
Olivier Lambert
3a4d945c68 Merge pull request #793 from Danp2/next-release
Fix issue with gathering NFS shares
2016-03-02 23:41:04 +01:00
Danp2
f4a364816b Fix issue with gathering NFS shares
scsiList vs nfsList
2016-03-02 16:11:26 -06:00
Olivier Lambert
931bc03cab Inverse critical/warning SR usage in health view 2016-03-02 18:30:05 +01:00
Olivier Lambert
1abd4937cd Merge pull request #792 from vatesfr/pierre-licenses
Host and pool licenses (Fix #763)
2016-03-02 17:39:09 +01:00
Pierre
0df8b51c62 Host view and pool view: License panel. 2016-03-02 17:09:20 +01:00
Julien Fontanet
e5b7190015 Fix file uploads (2). 2016-03-02 16:24:15 +01:00
Olivier Lambert
279b8aacf6 add missing map filters 2016-03-02 15:44:23 +01:00
Olivier Lambert
9eebaab2f4 Add a map param for backup schedule view 2016-03-02 15:42:33 +01:00
Julien Fontanet
16e9d60033 Fix file uploads. 2016-03-02 15:27:30 +01:00
Olivier Lambert
335b378e9a Merge pull request #789 from vatesfr/pierre-disk-names
Default VDI names and descriptions (Fix #780)
2016-03-02 15:20:39 +01:00
Pierre
9c41bc33a3 Default names for template VDIs 2016-03-02 15:13:34 +01:00
Olivier Lambert
7f7d6b4d5d Merge pull request #772 from vatesfr/pierre-cpu-weight-in-resource-set
CPU weight edition should be disabled for resource set members (Fix #…
2016-03-02 14:56:05 +01:00
Olivier Lambert
cc0e3bbce0 Merge pull request #773 from vatesfr/pierre-host-patches-in-pool-view
Pool view: host updates panel (Fix #762)
2016-03-02 14:41:11 +01:00
Pierre
2eead65fef CPU weight should not be editable when creating a VM from a resource set 2016-03-02 14:36:46 +01:00
Pierre
e664be451f VDIs default names initialization in view 2016-03-02 12:40:44 +01:00
Pierre
d2d8160096 Default VDI names and descriptions 2016-03-02 10:28:29 +01:00
Olivier Lambert
3bd503c28d remove useless device number in VM migration window 2016-03-02 10:27:57 +01:00
Olivier Lambert
aa1df8eb33 Add Misc panel in host view to deliver system S/N etc. Fix #760 2016-03-01 17:17:36 +01:00
Olivier Lambert
c1aace45ae Match target network names for migration. Fix #782 2016-03-01 16:40:45 +01:00
Olivier Lambert
217a60aadc Improve the migration VM modal. Fix #785 2016-03-01 15:47:37 +01:00
Olivier Lambert
5654f528ca fix the task list for angular 1.5 2016-03-01 15:35:48 +01:00
Olivier Lambert
4da036a064 Add a missing angular map 2016-03-01 15:16:55 +01:00
Olivier Lambert
2256b3d262 Merge pull request #788 from Danp2/next-release
Minor fix to unitConfirms
2016-03-01 13:36:24 +01:00
Danp2
d84ecc307d Minor fix to unitConfirms 2016-03-01 06:32:30 -06:00
Julien Fontanet
237313d5fb Merge pull request #781 from vatesfr/fix-vdi-iteration-in-vm-creation
VM creation: correctly iterate over template VDIs (fix #778).
2016-02-29 18:49:33 +01:00
Olivier Lambert
7caf766bca Do not display CDs VBDs 2016-02-29 18:44:08 +01:00
Julien Fontanet
0a3f9f5ef1 Add issue template. 2016-02-29 16:37:13 +01:00
Julien Fontanet
e890b8f7c1 VM creation: correctly iterate over template VDIs (fix #778). 2016-02-29 15:14:52 +01:00
Olivier Lambert
dc4d5f0ecb fixing angular 1.5 2016-02-26 18:03:07 +01:00
Pierre
a2f0980731 Pool view: host updates panel (Fix #762) 2016-02-26 17:59:33 +01:00
Julien Fontanet
0a5c029f8b lodash.sum(): does not work with objects anymore. 2016-02-26 17:36:06 +01:00
Julien Fontanet
85bb79e4fb Move shims to app/node_modules.
Hopefully this will avoid there accidental deletion by users.
2016-02-26 15:55:05 +01:00
Julien Fontanet
f18d1e50f8 Fix ng-file-upload import. 2016-02-26 15:55:05 +01:00
Julien Fontanet
943b10dd5d Update angular-chart.js to 0.8. 2016-02-26 15:55:05 +01:00
Julien Fontanet
0a48e17c88 Update ng-angular-upload to 12.0. 2016-02-26 15:55:05 +01:00
Olivier Lambert
da1381e14e Map usage before orderBy for dashboard view 2016-02-26 15:53:14 +01:00
Pierre
bdffb0ee10 CPU weight edition should be disabled for resource set members (Fix #767) 2016-02-26 14:51:53 +01:00
Julien Fontanet
7bdb7d2ca8 Fix angular-bootstrap usage in updater. 2016-02-26 14:34:56 +01:00
Julien Fontanet
92567561b8 Update angularjs-toaster to 1.2. 2016-02-26 13:06:02 +01:00
Julien Fontanet
335bdcd89d Update angular-xeditable-npm to 0.1.9. 2016-02-26 13:05:56 +01:00
Julien Fontanet
4c2fc13abb Update ui-select to 0.14. 2016-02-26 13:05:51 +01:00
Julien Fontanet
7f8f29daa2 Update angular-ui-bootstrap to 0.14. 2016-02-26 12:43:48 +01:00
Julien Fontanet
8fac845ecb Use angular-ui-{event,indeterminate} instead of deprecated angular-ui-utils. 2016-02-26 12:34:56 +01:00
Olivier Lambert
d8076e7630 Merge pull request #756 from vatesfr/pierre-self-service-dashboard
Self service dashboard (Fix #741)
2016-02-26 11:56:27 +01:00
Pierre
b370bc27c4 Fixed condition when no resource set found 2016-02-26 11:41:08 +01:00
Pierre
334c3f4488 Removed arrows when only 1 page is needed 2016-02-26 11:34:46 +01:00
Pierre
33822109c0 Minor fixes 2016-02-26 11:20:11 +01:00
Pierre
b1f18b0f5b Added templates, SRs and networks to details 2016-02-26 10:00:32 +01:00
Julien Fontanet
c0d6284368 Fix Jade compilation for Node < v4. 2016-02-25 17:48:16 +01:00
Pierre
16e294f6fc Resource sets details 2016-02-25 17:47:46 +01:00
Pierre
5f7925b2b8 Display page number 2016-02-25 17:47:46 +01:00
Pierre
2e001b0ce4 Pages layout 2016-02-25 17:47:46 +01:00
Pierre
c1ca3ff5b5 [WIP] All resource sets together (other layout commented). 2016-02-25 17:47:46 +01:00
Pierre
1de33cd4ca Self service dashboard (Fix #741)
Fixed no limit condition and icons.
2016-02-25 17:47:46 +01:00
Olivier Lambert
77b773388f fix network list in new vm, using map before orderby 2016-02-25 15:47:34 +01:00
Olivier Lambert
3e668ee439 Merge pull request #759 from vatesfr/fix-babel-6-imports
Fix Babel 6: `require module` --> `require(module).default`
2016-02-25 11:38:23 +01:00
Pierre
0d3ea9af36 Fix Babel 6: require module --> require(module).default 2016-02-25 11:21:47 +01:00
Fabrice Marsaud
0d81bc8056 Fixed backup remote lists 2016-02-25 09:24:38 +01:00
Julien Fontanet
e4b532a34d week heatmap: fix lodash.pluck usage. 2016-02-24 17:49:32 +01:00
Julien Fontanet
61f86c0ac3 Merge pull request #739 from vatesfr/update-deps
Update deps
2016-02-24 16:58:35 +01:00
Julien Fontanet
66fad37116 Remove unused lodash.puck dep. 2016-02-24 16:53:12 +01:00
Julien Fontanet
c7bbd8c823 Update nice-pipe to v3. 2016-02-24 16:44:07 +01:00
Julien Fontanet
dc6f8baf1e Update Babel to v6. 2016-02-24 16:44:07 +01:00
Julien Fontanet
0e76e65d65 Update Coffeeify to v2. 2016-02-24 16:44:07 +01:00
Julien Fontanet
877dbed999 Update Browserify to v13. 2016-02-24 16:44:07 +01:00
Julien Fontanet
668fd05fae Update Bluebird to v3. 2016-02-24 16:44:07 +01:00
Julien Fontanet
3e49998f41 Update Lodash to v4. 2016-02-24 16:44:07 +01:00
Julien Fontanet
99b183ac17 Update Angular to v1.5. 2016-02-24 16:42:51 +01:00
Fabrice Marsaud
bb04cddc48 4.14.1 2016-02-24 13:57:24 +01:00
Fabrice Marsaud
ba3f095dd8 4.14.0 2016-02-24 13:57:13 +01:00
Julien Fontanet
f8438421c8 4.13.1 2016-02-24 13:42:15 +01:00
Julien Fontanet
334361860b Merge pull request #753 from vatesfr/pierre-self-service-user
New-VM: Fixed summary when modifying an existing disk size
2016-02-24 13:42:00 +01:00
Pierre
23bd211758 Fixed summary when modifying an existing disk size 2016-02-24 13:26:58 +01:00
Julien Fontanet
4cc8fb9891 Merge pull request #752 from vatesfr/pierre-state-go
Better redirection when creating VM(s)
2016-02-24 12:51:32 +01:00
Pierre
ed3cd690fe Better redirection when creating VM(s) 2016-02-24 12:50:04 +01:00
Julien Fontanet
9a40c7cdc6 Merge pull request #750 from vatesfr/pierre-self-service-user
No limit: limit value should not exist when input field is empty
2016-02-24 12:44:37 +01:00
Pierre
ae4c9ce819 Better handle for no limit 2016-02-24 12:44:06 +01:00
Pierre
2f8bae1356 No limit: limit value should be undefined when input field is empty 2016-02-24 12:23:51 +01:00
Olivier Lambert
2dfcd5b7ef Merge pull request #740 from vatesfr/pierre-self-service-user
Create VM with self service (See #285)
2016-02-23 18:56:48 +01:00
Pierre
739926f64e Fixed button addon in disk list (VM view) 2016-02-23 18:51:04 +01:00
Pierre
a18dde07de Fixed multiple lines ng-disabled 2016-02-23 18:42:54 +01:00
Pierre
6480017a91 Fixed Create button disabled condition 2016-02-23 18:39:47 +01:00
Pierre
4467ec52f7 No limit: undefined instead of null. 2016-02-23 18:24:53 +01:00
Pierre
072d82a10e Size unit fix. 2016-02-23 18:15:43 +01:00
Pierre
b50b759f4f Default RAM size unit in New VM view 2016-02-23 18:11:07 +01:00
Pierre
c1477ad45f Typo fix. 2016-02-23 18:09:02 +01:00
Pierre
679d45399b Typo fix. Default resource set RAM size unit. 2016-02-23 17:31:37 +01:00
Pierre
0e15a789ff Fixed buttons addons 2016-02-23 16:55:47 +01:00
Pierre
dff5b3f497 Fixed resourceSet edition 2016-02-23 16:55:47 +01:00
Pierre
483e49a6ae Connect interface and server 2016-02-23 16:55:47 +01:00
Pierre
a689b5b917 Changed resourceSet^Ctructure for limits. Minor fixes. 2016-02-23 16:55:46 +01:00
Pierre
1363d98280 New VM: progress bars to show remaining available resources 2016-02-23 16:55:46 +01:00
Pierre
405f3dcbdd Resource sets: CPU, RAM and disk space restrictions 2016-02-23 16:55:46 +01:00
Pierre
4b48408bc9 Margin fix 2016-02-23 16:55:46 +01:00
Pierre
d6f1e2d7e2 Form alignements 2016-02-23 16:55:46 +01:00
Pierre
c04c8e3aa4 Moved "select resource set" on title row 2016-02-23 16:55:46 +01:00
Pierre
ff5a08d3b0 Enabled resource set edition 2016-02-23 16:55:46 +01:00
Pierre
d2049c759e Enabled ISO installing method 2016-02-23 16:55:46 +01:00
Pierre
f940cb0ace Redirect to list view instead of VM view 2016-02-23 16:55:46 +01:00
Pierre
507f2f4af4 Creation form updated when resource set selected 2016-02-23 16:55:45 +01:00
Pierre
deca7099f3 Checking available resource sets for a specific user 2016-02-23 16:55:45 +01:00
Pierre
46a741825a [WIP] Create VM as non-admin user with self service 2016-02-23 16:55:45 +01:00
Fabrice Marsaud
14fdcd3052 Reworked for sequential SR and Network choice 2016-02-23 16:55:44 +01:00
Fabrice Marsaud
c76b01608a Fixes. Commented quotas for later use. 2016-02-23 16:55:44 +01:00
Fabrice Marsaud
be709d6601 Rewrite on server API 2016-02-23 16:55:44 +01:00
Fabrice Marsaud
7dc8fac198 Fix constraint rules 2016-02-23 16:55:44 +01:00
Fabrice Marsaud
29ae7d57fd display and mock save/edit 2016-02-23 16:55:43 +01:00
Fabrice Marsaud
1bf9ce872b constraint reolutions 2016-02-23 16:55:43 +01:00
Fabrice Marsaud
0071c9504f first steps 2016-02-23 16:55:43 +01:00
Olivier Lambert
594a872c84 forget _reportWhen 2016-02-23 16:54:23 +01:00
Julien Fontanet
0d1f78e82e Better permission handling when not connected. 2016-02-23 16:49:14 +01:00
Julien Fontanet
a12de51897 generic modal: fix missing cancel button. 2016-02-23 16:12:59 +01:00
Olivier Lambert
74fa084dd0 update changelog 2016-02-23 16:02:37 +01:00
Julien Fontanet
a1ee258da5 Do not call vm.set() directly in new VM controller. 2016-02-23 15:18:34 +01:00
Julien Fontanet
c1af171c5d Do not call vm.set() directly in VM controller. 2016-02-23 15:04:50 +01:00
Olivier Lambert
e36c9560fa fix form declaration for conditional backup. Thanks to @Danp2 for pointing it 2016-02-23 13:58:37 +01:00
Olivier Lambert
5cd19ddc8d UI fix for non admin users 2016-02-23 11:00:36 +01:00
Olivier Lambert
ac243e5d11 Merge pull request #745 from vatesfr/pierre-overview-remote-status-indicator
Fixed error button condition in Overview. Minor UI fix.
2016-02-23 10:23:00 +01:00
Olivier Lambert
b480d019f6 add conditional reporting for rolling snaps 2016-02-23 10:21:46 +01:00
Olivier Lambert
cc13ab97d6 add conditional reporting for DR 2016-02-23 10:19:00 +01:00
Olivier Lambert
fee1d2ed04 add conditional report for basic backup 2016-02-23 10:15:36 +01:00
Pierre
4144d5faa6 Fixed error button condition in Overview. Minor UI fix. 2016-02-23 10:11:09 +01:00
Olivier Lambert
38a23c0bee add conditional reporting for delta backup 2016-02-23 09:50:23 +01:00
Olivier Lambert
8fcfebe170 Merge pull request #744 from Danp2/reportWhen
CR conditional reporting
2016-02-22 21:54:34 +01:00
Danp2
f917fa8138 CR conditional reporting
Add functionality to allow conditional reporting to continuous
replication backup
2016-02-22 14:31:40 -06:00
Julien Fontanet
f60b611304 Revert to use ~ for Angular versions. 2016-02-22 18:32:40 +01:00
Olivier Lambert
a54624e5c8 Merge pull request #743 from vatesfr/sources-disclaimer
Sources disclaimer
2016-02-22 18:08:09 +01:00
Olivier Lambert
370b14b82e better text and link to XOA page 2016-02-17 13:21:00 +01:00
Julien Fontanet
88205adeb2 Update Angular to v1.4.9. 2016-02-17 12:42:57 +01:00
Julien Fontanet
351ce995d9 Sources disclaimer appears once every week. 2016-02-17 12:42:42 +01:00
Julien Fontanet
a9aa92de90 modal service: new htmlMessage option. 2016-02-17 12:34:46 +01:00
Olivier Lambert
9ea665dea2 include default cloudconfig + link toward the official doc 2016-02-17 11:42:53 +01:00
Julien Fontanet
30b52527e7 modal service: new alert() method. 2016-02-17 11:40:54 +01:00
Julien Fontanet
bb4125153b Dashboard/Health: fix deletion of VDIs/VMs after unselected item(s). 2016-02-16 17:50:41 +01:00
Olivier Lambert
f0d5b2b1da Merge pull request #732 from vatesfr/pierre-overview-remote-status-indicator
Backup overview: Status indicator for the remote associated to each job (Fix #728)
2016-02-12 18:04:25 +01:00
Pierre
30c4048e4a Handle remote/remoteId property 2016-02-12 17:48:51 +01:00
Pierre
93770ca9ce Alert when remote does not exist 2016-02-12 17:21:41 +01:00
Pierre
e788783d12 Backup overview: Status indicator for the remote associated to each job 2016-02-12 17:10:11 +01:00
Olivier Lambert
1314444d7c Merge pull request #730 from vatesfr/pierre-boot-order
Fixed boot order options bug (Fix #726)
2016-02-12 11:27:44 +01:00
Pierre
2a14664d34 Fixed boot order options bug (Fix #726)
Unchecking a boot option should not uncheck all the options below it.
2016-02-12 11:12:48 +01:00
Olivier Lambert
07a03940a0 order installed patch by name 2016-02-11 11:45:56 +01:00
Olivier Lambert
5f2f6fff56 Merge pull request #725 from vatesfr/pierre-vm-view-ui-enhancements
New VM: fixed summary refresh (Fix #721)
2016-02-11 11:06:55 +01:00
Pierre
353548660c New VM: fixed summary refresh (Fix #721) 2016-02-11 10:58:00 +01:00
Olivier Lambert
912f07225c Merge pull request #723 from vatesfr/pierre-custom-cloud-config
Custom config in Config Drive
2016-02-11 10:47:29 +01:00
Pierre
a23b7eeff1 Uplad config file: status icons, size check. Minor fixes. 2016-02-11 10:32:58 +01:00
Pierre
574f0d71b2 Custom config in Config Drive 2016-02-10 16:32:11 +01:00
Olivier Lambert
a368312035 Merge pull request #720 from vatesfr/pierre-vm-view-ui-enhancements
Size input should allow float numbers (Fix #719)
2016-02-10 14:46:16 +01:00
Pierre
2f88b1ab65 +string instead of parseFloat(string) 2016-02-10 14:43:15 +01:00
Pierre
f85f97e061 Size input should allow float numbers (Fix #719) 2016-02-10 14:36:00 +01:00
Olivier Lambert
30dec13903 Merge pull request #718 from vatesfr/pierre-table-ellipsis
Fixed text overflow in tables. (See #713)
2016-02-10 14:02:37 +01:00
Pierre
3057e5c997 Fixed text overflow in tables. (See #713)
`overflow: hidden` for td and th. Inline CSS style when `overflow: visible` is needed.
2016-02-10 10:04:49 +01:00
Olivier Lambert
6c413eb1ba Fix issue #703 2016-02-08 17:38:22 +01:00
Julien Fontanet
dcdd9132e2 The main bug tracker is xo-web. 2016-02-08 15:33:35 +01:00
Olivier Lambert
d9b1c36055 typo in changelog 2016-02-04 22:14:39 +01:00
Olivier Lambert
a593a247d7 change log update 2016-02-04 22:13:51 +01:00
Julien Fontanet
155debc864 4.13.0 2016-02-04 19:51:07 +01:00
Julien Fontanet
a5975ac38b Merge branch 'next-release' into stable 2016-02-04 19:50:39 +01:00
Olivier Lambert
204f1cfd6b Merge pull request #674 from vatesfr/pierre-vm-view-ui-enhancements
Select RAM units with dropdown menus
2016-02-04 09:52:50 +01:00
Pierre
2d22e043a0 Dropdown menus to select byte units (Fix #666)
New-VM view: RAM & VDIs sizes
VM view: RAM & VDIs sizes
SR view: VDIs sizes
2016-02-03 16:18:05 +01:00
Olivier Lambert
c26cacaf4e Merge pull request #687 from vatesfr/xo-acl-resolver
Use xo-acl-resolver.
2016-02-03 14:26:40 +01:00
Olivier Lambert
f0048544e2 Merge pull request #681 from vatesfr/olivierlambert-health
WIP: initial work on new health view
2016-02-03 14:16:45 +01:00
Olivier Lambert
cf227dbfa2 Merge pull request #695 from vatesfr/marsaudf-fix-sr-form
fix SR form
2016-02-03 14:00:38 +01:00
Olivier Lambert
a5f8bdbe61 fix issue #693 2016-02-03 13:26:07 +01:00
Olivier Lambert
e442553c6f Fix issue #691 2016-02-03 13:21:17 +01:00
Olivier Lambert
7134acfcd6 Fix issue #690 2016-02-03 13:18:21 +01:00
Olivier Lambert
82439f444e Fix issue #688 2016-02-03 13:03:58 +01:00
Fabrice Marsaud
1a17908488 fix SR form 2016-02-03 12:12:15 +01:00
Julien Fontanet
9af30e99f8 Use xo-acl-resolver. 2016-02-03 11:53:54 +01:00
Fabrice Marsaud
6f942c3417 orphans in sr view 2016-02-03 11:07:07 +01:00
Fabrice Marsaud
57083c90cd Fixes 2016-02-03 10:48:21 +01:00
Fabrice Marsaud
e28bcdd978 Correct rule for orphan snapshots 2016-02-03 10:28:49 +01:00
Olivier Lambert
0b4a5ab2eb working filter 2016-02-02 23:31:00 +01:00
Fabrice Marsaud
034704a330 No warnings for iso SRs 2016-02-02 17:59:59 +01:00
Fabrice Marsaud
5c60eaf6ab SR warnings 2016-02-02 17:45:49 +01:00
Fabrice Marsaud
f5709eac2c orphan snapshots panels 2016-02-02 16:52:57 +01:00
Fabrice Marsaud
5a5e714aca Fixed cpuWeight default choice 2016-02-02 09:43:18 +01:00
Olivier Lambert
747d48e4d9 adding missing files 2016-02-01 18:30:02 +01:00
Olivier Lambert
07a0200f30 WIP: initial work on new health view 2016-02-01 18:28:09 +01:00
Fabrice Marsaud
1c5313f2d9 Fixed cpuWeight type 2016-02-01 17:51:10 +01:00
Olivier Lambert
05e08719fb Merge pull request #678 from vatesfr/olivierlambert-srfixes
Use only physical usage for SRs
2016-02-01 17:31:10 +01:00
Olivier Lambert
ca0e616f88 Only use physical utilization for SR 2016-02-01 17:26:08 +01:00
Fabrice Marsaud
a8d20caba4 CPU Weight can back to default 2016-02-01 16:29:53 +01:00
Olivier Lambert
0d4bbb0a48 Merge pull request #677 from vatesfr/marsaudf-cpu-weigth#633
Edit VM CPU Weight
2016-02-01 16:10:36 +01:00
Fabrice Marsaud
b9cc219530 Handle VM CPU Weight 2016-02-01 15:54:52 +01:00
Olivier Lambert
e204ab5871 Merge pull request #650 from vatesfr/pierre-vm-migration-details
Custom VM migration in VM view
2016-01-28 17:02:35 +01:00
Fabrice Marsaud
16d0c05b4b Fiw attempt on console canvas 2016-01-28 15:33:48 +01:00
Fabrice Marsaud
6f8329d191 VDI multi delete 2016-01-28 15:09:36 +01:00
Olivier Lambert
d751463b26 Merge pull request #660 from vatesfr/abhamonr-avoid-metadata-imp-exp-delta-backups
New delta backup format used. (without 'xva' files) (fix #651)
2016-01-28 11:35:11 +01:00
Olivier Lambert
d3b66eff59 Merge pull request #671 from vatesfr/marsaudf-clear-logs#661
Delete job logs
2016-01-28 11:01:15 +01:00
Olivier Lambert
4257d0332a Merge pull request #672 from vatesfr/marsaudf-console-links#668
Differentiate VM and VM-controller console
2016-01-28 10:02:40 +01:00
Fabrice Marsaud
b80442c061 Fix to remove smb remotes for delta-backups 2016-01-28 09:22:22 +01:00
Fabrice Marsaud
3a0f6820ad Differentiate VM and VM-controller console 2016-01-28 09:02:58 +01:00
Fabrice Marsaud
1bc92f5363 Delete job logs 2016-01-28 08:50:50 +01:00
Olivier Lambert
818ddcf01e Merge pull request #663 from vatesfr/marsaudf-angular-crash#662
Secure VM object concurrent suppression
2016-01-28 08:18:43 +01:00
Olivier Lambert
618ba361c7 Merge pull request #568 from vatesfr/marsaudf-smb-mounts#338
SMB remotes
2016-01-28 08:18:24 +01:00
wescoeur
599160a325 New delta backup format used. (without 'xva' files) (fix #651) 2016-01-27 13:37:54 +01:00
Pierre
35fba6f4ed Custom VM migration from VM view (See #567)
- Migration on the same pool :
	- if the VM's VDIs are on the pool's SRs : standard migration
	- if the VM's VDIs are on local SRs : choose migration network and target SRs
- Migration on another pool : choose migration network, target SRs and target VIFs
2016-01-27 11:36:38 +01:00
Fabrice Marsaud
a14aad75fd external remote url module 2016-01-27 10:12:16 +01:00
Fabrice Marsaud
3513e85b0b remote url composing fix 2016-01-27 10:12:16 +01:00
Fabrice Marsaud
66c0390fc7 No smb remotes for delta backups 2016-01-27 10:12:16 +01:00
Fabrice Marsaud
a6549ccb08 SMB remotes 2016-01-27 10:12:16 +01:00
Julien Fontanet
15d2878014 Merge pull request #669 from Danp2/Danp2-patch-1
Fix spelling of "immediately"
2016-01-27 09:10:44 +01:00
Danp2
d271be8723 Update view.jade 2016-01-26 19:32:09 -06:00
Danp2
6f9d2d99dd Update view.jade 2016-01-26 19:31:43 -06:00
Danp2
5d62664ee3 Update view.jade 2016-01-26 19:30:45 -06:00
Danp2
7124d9f2f8 Update view.jade 2016-01-26 19:26:53 -06:00
Fabrice Marsaud
0459744771 Fixes disk save handling 2016-01-25 17:40:47 +01:00
Fabrice Marsaud
417544b781 Secure VM object concurrent suppression 2016-01-25 17:40:35 +01:00
Olivier Lambert
f9028cb366 Change the word Terminated by Finished for backups 2016-01-22 18:22:50 +01:00
Olivier Lambert
9a264719a9 Avoid broken Angular views. Fix #662 2016-01-21 15:11:42 +01:00
Olivier Lambert
96c213dcc4 Typo about the year in the changelog 2016-01-18 12:56:29 +01:00
Julien Fontanet
dec1a8e204 4.12.0 2016-01-18 10:45:50 +01:00
Olivier Lambert
a17fd697e2 update the CHANGELOG 2016-01-17 16:58:31 +01:00
Fabrice Marsaud
a6ab66e799 Fix #654 2016-01-17 16:12:11 +01:00
Fabrice Marsaud
17095ec3c6 Fix #652 2016-01-17 15:57:58 +01:00
Olivier Lambert
82687147b8 changelog for release 2016-01-17 10:24:28 +01:00
Olivier Lambert
ba76422c1f Merge pull request #648 from vatesfr/abhamonr-continuous-replication-view
Continuous replication view.
2016-01-16 20:04:05 +01:00
wescoeur
083b3c4ece Continuous replication view. (fix #582) 2016-01-15 13:15:43 +01:00
Olivier Lambert
5ecfdf38a8 Merge pull request #600 from vatesfr/abhamonr-button-bootable-disk
Use checkbox to disable/enable bootable disk property. (fix #583)
2016-01-14 16:26:54 +01:00
wescoeur
dd1acf3c2a Use checkbox to disable/enable bootable disk property. (fix #583) 2016-01-14 16:22:45 +01:00
Olivier Lambert
76e9c2d196 Fix issue #643 2016-01-13 18:54:19 +01:00
Julien Fontanet
15f046959d Fix lodash.trim to 3.0.1 (see lodash/lodash#1769). 2016-01-13 16:23:20 +01:00
Olivier Lambert
bf3ba04624 Merge pull request #620 from vatesfr/abhamonr-disable-vm-start-during-delta-import
Disable vm start during delta import and explicit notification. (fix #613)
2016-01-13 11:53:54 +01:00
Olivier Lambert
d997894d9a Merge pull request #614 from vatesfr/pierre-create-multiple-vms
Create multiple VMs at once
2016-01-13 11:33:18 +01:00
wescoeur
c1059db6e5 Disable vm start during delta import and explicit notification. (fix #613) 2016-01-13 11:04:24 +01:00
Pierre
8ad29a2836 Creation of multiple VMs at once
- Panel to enable the creation of multiple VMs at once
- Main name is no longer required when creating multiple VMs
- Number of VMs is checked before creating VMs names input fields
- Redirection to tree view instead of VM view when creating multiple VMs
- Number of new VMs in summary
2016-01-13 10:52:48 +01:00
Olivier Lambert
93a454b835 fix id propagation problem 2016-01-13 10:33:06 +01:00
Olivier Lambert
da899386ec Merge pull request #640 from vatesfr/marsaudf-plugin-view-fix#637
Plugin reload after changes
2016-01-07 11:53:16 +01:00
Fabrice Marsaud
05d22903ea Plugin reload after changes 2016-01-07 11:45:15 +01:00
Olivier Lambert
33945520f1 Fix issue #639 2016-01-06 10:22:52 +01:00
Olivier Lambert
40284809cf choose to boot VM after creation. Fix #635 2016-01-04 16:41:16 +01:00
Olivier Lambert
efc18aaaec ensure CloudConfig drive is created before going on the freshly created VM view 2016-01-04 16:37:04 +01:00
Olivier Lambert
348441b046 improve hostname regex for CloudInit 2016-01-04 12:35:47 +01:00
Olivier Lambert
66601b2e7c remove space in hostname for cloudconfig. Fix #634 2016-01-04 12:33:52 +01:00
Olivier Lambert
724c5e4b73 VM creation auto name, description & select existing install repo 2015-12-31 10:50:15 +01:00
Olivier Lambert
7eff29bc65 remove useless logs 2015-12-31 10:25:49 +01:00
Olivier Lambert
ca002003c2 fix VIFs issues in VM creation 2015-12-31 10:25:23 +01:00
Olivier Lambert
f0675f1f3c Merge pull request #618 from vatesfr/pierre-delete-running-vm
Delete a running or suspended VM (See #616)
2015-12-31 08:56:19 +01:00
Olivier Lambert
976186c525 Merge pull request #631 from vatesfr/olivierlambert-existing-vifs
manage existing VDIs, fix #630
2015-12-30 22:16:00 +01:00
Olivier Lambert
89d5777e52 allow existing VIFs edition during VM creation. Fix #630 2015-12-30 22:12:15 +01:00
Olivier Lambert
8dbb69809c Merge pull request #629 from vatesfr/olivierlambert-custom-templates
Custom templates, fix #627 and #628
2015-12-30 18:18:09 +01:00
Olivier Lambert
7348bd5d15 support templates with existing install_repository, as requested for issue #625 2015-12-30 16:08:39 +01:00
Olivier Lambert
9a46a466f7 properlly manage PV args (related to #625) 2015-12-30 15:27:41 +01:00
Fabrice Marsaud
fafc5c8553 Deltabackup display fix 2015-12-30 13:46:12 +01:00
Julien Fontanet
4ffdfaa506 Merge pull request #619 from vatesfr/pierre-fix-suspend-vm-tree-view
Fixed `suspendVM` in tree view.
2015-12-22 15:56:50 +01:00
Pierre
e3989840ee Fixed suspendVM in tree view. 2015-12-22 15:51:59 +01:00
Pierre
b3e6f531a1 Delete not halted VMs (See #616) 2015-12-22 15:46:19 +01:00
Julien Fontanet
4f6ee34592 4.11.0 2015-12-22 13:05:56 +01:00
Olivier Lambert
3ae58a323e update changelog 2015-12-22 12:36:12 +01:00
Fabrice Marsaud
26b958c270 SR host names displayed when necessary 2015-12-22 12:00:15 +01:00
Olivier Lambert
12a4af5900 fix a broken link 2015-12-22 10:39:18 +01:00
Julien Fontanet
69479d538c Merge pull request #611 from vatesfr/abhamonr-incremental-backups-integration
Delta Backup is now known by xo-web.
2015-12-21 18:52:26 +01:00
wescoeur
829397dd5a Delta Backup is now known by xo-web. 2015-12-21 18:00:31 +01:00
Olivier Lambert
2bc89026db Merge pull request #612 from vatesfr/marsaudf-sr-list#601
All host SRs from the pool are shown for Halted VMs disk edition
2015-12-21 16:03:22 +01:00
Fabrice Marsaud
ebbc44d181 All host SRs from the pool are shwon for Halted VMs disk edition 2015-12-21 15:58:49 +01:00
Olivier Lambert
2228a1e36b update changelog 2015-12-21 12:37:33 +01:00
Olivier Lambert
a8cbf3e8ff Merge pull request #602 from vatesfr/pierre-plugin-config-detailed-errors
Plugin config: feedback on form filling errors
2015-12-21 12:02:57 +01:00
Olivier Lambert
fa32e3d734 Merge pull request #598 from vatesfr/marsaudf-disk-size-edition#587
Marsaudf disk size edition#587
2015-12-21 12:02:13 +01:00
Pierre
0d17148ff0 Minor fixes 2015-12-21 11:30:07 +01:00
Pierre
aa38411cf7 Checking titles for each config group. Displaying errors only for the concerned plugin 2015-12-21 11:11:04 +01:00
Olivier Lambert
4913c8699d Merge pull request #610 from vatesfr/cache-missingPatches-dashboard
Cache # of missing patches in dashboard (fix #609).
2015-12-21 10:42:36 +01:00
Julien Fontanet
1035a11487 Cache # of missing patches in dashboard (fix #609). 2015-12-21 10:38:31 +01:00
Olivier Lambert
15c2efe706 Merge pull request #607 from vatesfr/fix-removeListener-dashboard
Stop computing charts data when no longer on dashboard (fix #604).
2015-12-21 10:09:19 +01:00
Julien Fontanet
d7fd71bb62 Same fix for dataviz view. 2015-12-21 10:05:51 +01:00
Julien Fontanet
b11ee993fa Stop computing charts data when no longer on dashboard (fix #604). 2015-12-21 10:05:46 +01:00
Olivier Lambert
614aa7873c update changelog 2015-12-20 14:21:30 +01:00
Pierre
1adf31fe15 [WIP] Display field title when possible and multiple fixes. 2015-12-18 17:24:19 +01:00
Julien Fontanet
824ffd7b5b Merge pull request #603 from vatesfr/abhamonr-fix-remote-importVm
The vm import call use a sr instead of a host.
2015-12-18 17:10:11 +01:00
wescoeur
c31c6fdebb The vm import call use a sr instead of a host. 2015-12-18 17:03:59 +01:00
Pierre
83f3276429 Plugin config: feedback on form filling errors 2015-12-18 15:25:24 +01:00
Julien Fontanet
d21f68ce54 Merge pull request #586 from vatesfr/pierre-read-only-connection
Added read-only checkboxes in the interface
2015-12-18 12:03:44 +01:00
Pierre
18b1e1b133 Connection to a server in read-only mode. (Fix #439)
Checkboxes in Settings view to connect to a Xen Server in read-only mode and then to toggle mode while connected to the server.
2015-12-18 11:48:29 +01:00
Fabrice Marsaud
0edaa40052 Confirm modals before disk resizing 2015-12-18 10:18:02 +01:00
Fabrice Marsaud
627077c8f3 Better code for Human readable size input 2015-12-18 10:18:02 +01:00
Fabrice Marsaud
a897b1798d bytesToSize only alters numbers 2015-12-18 10:18:02 +01:00
Olivier Lambert
50e39993bf Merge pull request #599 from vatesfr/marsaudf-newmv-disk-size
HR size for new VM new disks
2015-12-17 10:59:50 +01:00
Fabrice Marsaud
5e397dd01e HR size for new VM new disks 2015-12-17 10:53:32 +01:00
Olivier Lambert
f57ff5d5e0 Merge pull request #593 from vatesfr/pierre-no-orderby-when-focus
Settings view: the servers list should not re-order while a field is being edited. Fix #594
2015-12-16 18:26:55 +01:00
Olivier Lambert
5c3e40917c Merge pull request #577 from vatesfr/olivierlambert-configdrive
Allow cloud drive usage for disk templates VMs
2015-12-16 18:20:13 +01:00
Olivier Lambert
90a2dc4581 Merge pull request #597 from vatesfr/marsaudf-disk-size-edition#587
Marsaudf disk size edition. Fix #587
2015-12-16 17:37:15 +01:00
Olivier Lambert
b64243fdd6 add parent :o 2015-12-16 17:12:51 +01:00
Fabrice Marsaud
42db87d305 resize disks in SR View 2015-12-16 16:43:39 +01:00
Fabrice Marsaud
e7ab1b589a resize disks in VM view 2015-12-16 16:17:15 +01:00
Fabrice Marsaud
e9979c9887 Human readable sizes for editing template disks on VM creation 2015-12-16 16:07:08 +01:00
Olivier Lambert
3bb9bb56f0 better placeholder 2015-12-16 16:07:08 +01:00
Olivier Lambert
5a99474c55 add stuff 2015-12-16 16:07:08 +01:00
Olivier Lambert
182ee6c25f add stuff 2015-12-16 16:07:08 +01:00
Fabrice Marsaud
4d3f0a06db Modfified template disk properties are stored for future update 2015-12-16 16:07:08 +01:00
Fabrice Marsaud
0e182c519b Config drive button looks better 2015-12-16 16:07:08 +01:00
Fabrice Marsaud
b1ee30ce7d cloud config message 2015-12-16 16:07:08 +01:00
Fabrice Marsaud
93ba764e23 Config drive can be toggled, modified template disks data are isolated in the controller 2015-12-16 16:07:08 +01:00
Olivier Lambert
433e17bb81 more comments 2015-12-16 16:07:08 +01:00
Olivier Lambert
61c09083ad more modifications 2015-12-16 16:07:08 +01:00
Olivier Lambert
018377e724 reorder the cloud config at the end of the vm creation process 2015-12-16 16:07:08 +01:00
Olivier Lambert
b76f9513ba various fixes 2015-12-16 16:07:08 +01:00
Olivier Lambert
40ebb7ba75 add a removed by error stuff 2015-12-16 16:07:08 +01:00
Olivier Lambert
a9e52e8954 remove preliminar work in existing VM view 2015-12-16 16:07:08 +01:00
Olivier Lambert
3c8876cac7 Allow cloud drive usage for disk templates VMs 2015-12-16 16:07:08 +01:00
Olivier Lambert
b7e005f9c7 fix an indentation problem 2015-12-16 16:06:37 +01:00
Olivier Lambert
e6fe0a19fa Merge pull request #596 from vatesfr/olivierlambert-clean-size-to-byte
clean size to byte filter
2015-12-16 16:04:07 +01:00
Olivier Lambert
fba11b6a44 fix a useless filter 2015-12-16 16:01:10 +01:00
Olivier Lambert
c270e7f5dd clean size to byte filter 2015-12-16 15:41:42 +01:00
Pierre
9ee00d345e Settings: the servers list should not re-order while a field is being edited. (Fix #594)
The angular `orderBy` is triggered when the server list is triggered ie every 3 seconds when every text fields are unfocused.
2015-12-16 13:47:13 +01:00
Julien Fontanet
0379fbc4eb Merge pull request #590 from vatesfr/pierre-no-refresh-when-focus
Servers should not refresh while a field is being edited
2015-12-16 13:44:43 +01:00
Olivier Lambert
9748a3ae91 display correct size in interface (binary scale). fix #592 2015-12-16 11:23:37 +01:00
Pierre
1881944748 Servers infos should not refresh while a field is being edited 2015-12-15 16:56:52 +01:00
Olivier Lambert
3721fa194c remove metadata export. Fix #580 2015-12-11 16:38:03 +01:00
Olivier Lambert
8c3fcad20b Merge pull request #574 from vatesfr/marsaudf-prevent-add-host#466
Check IP of a new server to avoid double connection. Fix #466
2015-12-10 14:02:41 +01:00
Fabrice Marsaud
decf373d0b Check IP of a new server to avoid double connection 2015-12-10 12:25:04 +01:00
Olivier Lambert
ff1d50f993 Merge pull request #573 from vatesfr/olivierlambert-set-default-sr
set default SR. Fix #572
2015-12-10 12:14:14 +01:00
Olivier Lambert
ef34204b59 set default SR. Fix #572 2015-12-10 12:10:47 +01:00
Olivier Lambert
270b636d80 Merge pull request #569 from vatesfr/pierre-users-cannot-add-tags-on-disks
Non-admin users don't see the '+' button to add a tag (Fix Issue #516)
2015-12-09 19:22:48 +01:00
Olivier Lambert
ac01da2ae9 Merge pull request #570 from vatesfr/marsaudf-run-job#562
Buttons to run jobs for one shot (backup or job manager). Fix #570
2015-12-09 19:17:13 +01:00
Fabrice Marsaud
0136310c54 Buttons to run jobs for one shot (backup or job manager) 2015-12-09 16:57:50 +01:00
Pierre
ecf4cf852e Non-admin users don't see the '+' button to add a tag (Issue #516) 2015-12-09 16:51:12 +01:00
Olivier Lambert
c66384adfb Merge pull request #566 from vatesfr/olivierlambert-recoveryStart
Generic recovery start (both PV and HVM compatible). Fix #564
2015-12-07 18:03:33 +01:00
Olivier Lambert
98bdda629d Order ISOs by their name. Fix #565 2015-12-07 17:28:48 +01:00
Olivier Lambert
a8286f9cba minor fix 2015-12-07 17:22:40 +01:00
Olivier Lambert
fa3db4fcf6 Generic recovery start (both PV and HVM) 2015-12-07 15:43:46 +01:00
Olivier Lambert
ddac0cfee1 Display failures on VM boot order modification. Fix #560 2015-12-07 13:48:32 +01:00
Olivier Lambert
9368673459 Display PV args for PV guest even if they are empty. Fix #557 2015-12-07 13:37:25 +01:00
Olivier Lambert
43dc999ab5 display boot order only for HVM guests. Fix #558 2015-12-07 13:32:21 +01:00
Olivier Lambert
3b7333e866 remove most of the left margin 2015-12-04 14:11:38 +01:00
Olivier Lambert
bc0ddbaf16 Merge pull request #554 from vatesfr/abhamonr-plugin-config-avoid-reset-form
Avoid plugin config form reset. Fix #529
2015-12-04 12:38:23 +01:00
Olivier Lambert
45f0ae7e1c Merge pull request #425 from vatesfr/pierre-search-bar
Improved search bar
2015-12-04 12:35:35 +01:00
Pierre
a521c4ae01 Clicking on the search button will always bring to the list view. Lag fix when the text field is emptied. Transparent background for filter menus. 2015-12-04 12:00:55 +01:00
Pierre
5b8238adeb 2 filter menus : 'Types' and 'States'. FontAwesome checkboxes. Bug fix. 2015-12-04 10:48:32 +01:00
Pierre
ec330474fa The 'Filters' menu is below the search bar and appears only in the list view. *disconnected filter shows hosts and SRs which have at least 1 PBD not attached. 2015-12-04 10:48:32 +01:00
Pierre
ece28904a8 All the checkboxes are unchecked when the search field is empty 2015-12-04 10:48:32 +01:00
Pierre
4f1c495afb Added *disconnected option. Added a 'Filters' dropdown menu in the search bar to add/remove options with a Github like behaviour 2015-12-04 10:48:32 +01:00
Pierre
5fdd27b7e6 Added option key-word *halted 2015-12-04 10:48:32 +01:00
Pierre
91f449af9a Search improvements
1) select several types of objects (eg: *vm *host)
2) combine types and states (eg: *vm *running)
3) negation is considered as an option
2015-12-04 10:48:32 +01:00
Pierre
efc0a0dfe3 Added '*running' option to show only running entities (for VMs and hosts). This option can be banned (statusrunning) to show only not running entities 2015-12-04 10:48:32 +01:00
Pierre
fee47baa66 Added key-words to ban some objects from search list (host, vm, sr, pool) + Back to tree view when search field is empty 2015-12-04 10:48:32 +01:00
wescoeur
0ad7bfc7e7 Avoid plugin config form reset. 2015-12-03 15:11:44 +01:00
Julien Fontanet
bd64143ae1 Merge pull request #551 from vatesfr/abhamonr-plugin-root-integer-properties
Root integer properties can be edited in plugins configuration form. …
2015-12-03 15:09:52 +01:00
wescoeur
ec982ba9a3 Root integer properties can be edited in plugins configuration form. fix #550 2015-12-03 15:03:47 +01:00
Julien Fontanet
6280f6ff98 Merge pull request #542 from vatesfr/abhamonr-purge-plugin-config
The plugins configurations can be cleaned.
2015-12-03 14:37:52 +01:00
Olivier Lambert
35d20390a9 hide non auhorized containers for VMs (host or pool). Fix #545 2015-12-03 14:23:32 +01:00
wescoeur
c487c5042f The plugins configurations can be cleaned. fix #539 2015-12-02 16:14:36 +01:00
Olivier Lambert
aaf7927aa2 Cloud config default SR. (Fix #548) 2015-11-30 18:45:29 +01:00
Julien Fontanet
3c677f3d21 Merge pull request #544 from vatesfr/abhamonr-plugin-config-boolean-default-value
Plugin config boolean properties have a default false value.
2015-11-27 17:01:22 +01:00
wescoeur
94eb76b3a6 Plugin config boolean properties have a default false value. fix #543 2015-11-27 16:49:46 +01:00
Julien Fontanet
a921cb2d0d 4.10.0 2015-11-27 14:35:50 +01:00
Olivier Lambert
f3aaa363d8 Merge pull request #541 from vatesfr/marsaudf-UI-fix
Minor UI fix
2015-11-27 13:39:20 +01:00
Fabrice Marsaud
45a79e1920 Minor UI fix 2015-11-27 13:35:27 +01:00
Olivier Lambert
6fd9b2a453 Merge pull request #493 from vatesfr/marsaudf-task-manager
Generic task manager
2015-11-27 12:03:01 +01:00
Olivier Lambert
01d8e89a71 add changelog 2015-11-27 11:58:00 +01:00
Fabrice Marsaud
c89fa63910 Minor UI fix 2015-11-27 11:25:23 +01:00
Fabrice Marsaud
9fc5c49dbf UI enhancements 2015-11-27 11:25:23 +01:00
Fabrice Marsaud
7dfc269df9 Enhanced UI inputs for XO object management 2015-11-27 11:25:18 +01:00
Fabrice Marsaud
76d0b397db Instant one shot for generic jobs 2015-11-27 11:24:25 +01:00
Fabrice Marsaud
5413f887af Bare generic job creation and scheduling 2015-11-27 11:24:13 +01:00
Julien Fontanet
b3d0c61f0e Merge pull request #540 from vatesfr/abhamonr-plugin-input-type-number
Plugin 'number' property use input number in config form (fix #538)
2015-11-27 10:49:32 +01:00
wescoeur
4ce0441d68 Plugin 'number' property use input number in config form 2015-11-27 10:42:45 +01:00
Julien Fontanet
72be34e18d Move clipboard to dev deps. 2015-11-27 10:04:43 +01:00
Julien Fontanet
d2961b7650 Merge pull request #537 from vatesfr/abhamonr-plugins-supports-numbers
Plugin config supports integer properties (fix #531).
2015-11-27 09:54:46 +01:00
wescoeur
fdca1bbf72 Plugin config supports integer properties. 2015-11-27 09:43:33 +01:00
Julien Fontanet
ab7a2f9dee Merge pull request #536 from vatesfr/abhamonr-plugin-boolean-checkbox
Plugin boolean properties use checkboxes (fix #528).
2015-11-27 09:42:06 +01:00
wescoeur
7b72857a3b Plugin boolean properties use checkboxes 2015-11-26 22:44:24 +01:00
Olivier Lambert
4787146658 Merge pull request #533 from vatesfr/marsaudf-backup-ui
better backup log display
2015-11-26 16:30:18 +01:00
Fabrice Marsaud
430f9356c3 Minor button fix 2015-11-26 16:27:40 +01:00
Fabrice Marsaud
70a3b3518f Better schedule state UI in overview 2015-11-26 16:25:00 +01:00
Fabrice Marsaud
c0944c17e0 better backup log display 2015-11-26 16:07:47 +01:00
Julien Fontanet
da1b2a91e7 Merge pull request #526 from vatesfr/pierre-console-keyboard-unfocus
Console has keyboard and mouse focus only when mouse is hovering
2015-11-26 15:59:48 +01:00
Pierre
aa27492713 Console catches keyboard and mouse inputs only when mouse is hovering.
Also, when the mouse enters the VM screen, the current active element is unfocused.
2015-11-26 15:44:01 +01:00
Julien Fontanet
afe589dec3 Merge pull request #527 from vatesfr/abhamonr-plugin-title-property
Support title property in plugin configuration schema
2015-11-26 15:25:25 +01:00
wescoeur
978d140c8f Support title property in plugin configuration schema 2015-11-26 14:31:32 +01:00
Olivier Lambert
2ce213b62c Merge pull request #525 from vatesfr/pierre-clipboard-management-through-console
Clipboard management through console
2015-11-26 11:35:57 +01:00
Pierre
7748266078 Clipboard support in console.
- From VM to client :
	1) Copy text in VM
	2) The text field (above the console) updates automatically with the VM's clipboard content
	3) Click on the 'Copy' button to get the text in the local clipboard
- From client to VM :
	1) Write text in the text field
	2) The VM's clipboard updates automatically with the new content
	3) Paste text anywhere in the VM
2015-11-26 11:23:40 +01:00
Olivier Lambert
83783d07a1 hide action panel for host or VM if only viewer 2015-11-25 14:38:47 +01:00
Olivier Lambert
49a1f2c7c5 Merge pull request #517 from vatesfr/marsaudf-disable-host-buttons#474
Disable host buttons relying on ACLs
2015-11-25 14:31:49 +01:00
Olivier Lambert
ddfc0151fc Merge pull request #515 from vatesfr/marsaudf-backup-display#512
Tag display for backup schedules in overview #512
2015-11-25 14:01:33 +01:00
Fabrice Marsaud
81c508e13c Host view OK 2015-11-25 10:25:10 +01:00
Fabrice Marsaud
7195cfc3cf First step 2015-11-24 17:31:26 +01:00
Fabrice Marsaud
93fe5e2cf7 PR feedback 2015-11-24 17:31:00 +01:00
Fabrice Marsaud
a2bf795d12 Tag display for backup schedules in overview 2015-11-24 17:31:00 +01:00
Julien Fontanet
c8d78f39e0 Upgrade npm to latest on Travis. 2015-11-24 17:17:58 +01:00
Fabrice Marsaud
d9ab8a1c8b Fix #508 2015-11-23 15:24:23 +01:00
Olivier Lambert
5125ad4889 Merge pull request #506 from vatesfr/pierre-emergency-host-shutdown
Emergency button in host view is now calling the server function
2015-11-20 17:32:17 +01:00
Olivier Lambert
951e85b04b Merge pull request #507 from vatesfr/olivierlambert-cloudconfig
CoreOS cloud config management during VM creation
2015-11-20 17:32:10 +01:00
Olivier Lambert
711d922695 CoreOS cloud config during VM creation 2015-11-20 17:10:10 +01:00
Pierre
3692ffcde7 Rename function : emergencyHostShutdown -> emergencyShutdownHost 2015-11-20 10:19:06 +01:00
Pierre
b049420c59 Emergency button in host view is now calling the server function (suspends all the VMs running on the host and then shuts the host down) 2015-11-19 17:07:10 +01:00
Olivier Lambert
241103c369 Merge pull request #501 from vatesfr/pierre-install-patches-on-all-pools
Created panel in dashboard
2015-11-19 14:49:59 +01:00
Pierre
2128367113 Update panel in dashboard. 2015-11-19 12:29:48 +01:00
Julien Fontanet
f555c8190d Revert "nvm (on Travis) does not use stable correctly."
This reverts commit f85dc3b7e7.
2015-11-18 17:32:54 +01:00
Pierre
d5df633def Removed some useless CSS 2015-11-18 17:13:54 +01:00
Olivier Lambert
fe7dc859e3 Merge pull request #499 from vatesfr/pierre-suspend-all-vms-and-shutdown-host
emergency shutdown feature in host view (suspend all VMs then shutdown)
2015-11-18 17:04:04 +01:00
Pierre
569c5046c6 Added an emergency button in Action panel (host view) : suspends all the VMs and shuts the host down. 2015-11-18 16:56:56 +01:00
Julien Fontanet
e0210ae2d8 Stable is the new stable branch. 2015-11-18 16:30:30 +01:00
Julien Fontanet
f85dc3b7e7 nvm (on Travis) does not use stable correctly. 2015-11-18 16:10:07 +01:00
Olivier Lambert
92d4363120 tree view improvements and fix 2015-11-17 15:18:57 +01:00
Olivier Lambert
6c69220de2 add start in recovery mode for HVM guests and support new API call setBootOrder() instead of bootOrder() 2015-11-17 14:59:26 +01:00
Julien Fontanet
3a1229b072 Only test on stable as there is just linting for now. 2015-11-16 16:59:55 +01:00
Olivier Lambert
45538c9f62 add quiesce display in VM view 2015-11-16 13:28:51 +01:00
242 changed files with 43650 additions and 361 deletions

View File

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

View File

@@ -1,3 +0,0 @@
{
"directory": "dist/bower_components"
}

8
.gitignore vendored
View File

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

View File

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

View File

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

View File

@@ -1,9 +1,11 @@
language: node_js
node_js:
- 'stable'
- '4'
- '0.12'
- '0.10'
#- '4' # Disabled for now because npm 2 cannot properly handled broken peer dependencies.
cache:
directories:
- node_modules
# Use containers.
# http://docs.travis-ci.com/user/workers/container-based-infrastructure/

View File

@@ -1,5 +1,585 @@
# ChangeLog
## **5.2.5** (2016-10-07)
### Enhancements
- Stats in home/host view when expanded [\#1634](https://github.com/vatesfr/xo-web/issues/1634)
- Bar for used and total RAM on home pool view [\#1625](https://github.com/vatesfr/xo-web/issues/1625)
- Can't translate some text [\#1624](https://github.com/vatesfr/xo-web/issues/1624)
- Dynamic RAM allocation at creation time [\#1603](https://github.com/vatesfr/xo-web/issues/1603)
### Bug fixes
- Do not expose shortcuts while console is focused [\#1614](https://github.com/vatesfr/xo-web/issues/1614)
## **5.2.4** (2016-10-04)
### Enhancements
- Display memory bar in home/host view [\#1616](https://github.com/vatesfr/xo-web/issues/1616)
### Bug fixes
- All users can see VM templates [\#1621](https://github.com/vatesfr/xo-web/issues/1621)
## **5.2.3** (2016-10-03)
### Enhancements
- Improve keyboard navigation [\#1578](https://github.com/vatesfr/xo-web/issues/1578)
- Strongly suggest to install the guest tools [\#1575](https://github.com/vatesfr/xo-web/issues/1575)
- Missing tooltip [\#1568](https://github.com/vatesfr/xo-web/issues/1568)
- Emphasize already used ips in ipPools [\#1566](https://github.com/vatesfr/xo-web/issues/1566)
- Change "missing feature message" for non-admins [\#1564](https://github.com/vatesfr/xo-web/issues/1564)
- Allow VIF edition [\#1446](https://github.com/vatesfr/xo-web/issues/1446)
- Disable browser autocomplete on credentials on the Update page [\#1304](https://github.com/vatesfr/xo-web/issues/1304)
- keyboard shortcuts [\#1279](https://github.com/vatesfr/xo-web/issues/1279)
- Add network bond creation [\#876](https://github.com/vatesfr/xo-web/issues/876)
### Bug fixes
- Profile page is broken [\#1612](https://github.com/vatesfr/xo-web/issues/1612)
- SR delete should redirect to home [\#1611](https://github.com/vatesfr/xo-web/issues/1611)
- Delta VHD backup checksum is invalidated by chaining [\#1606](https://github.com/vatesfr/xo-web/issues/1606)
- VM with long description break on 2 lines [\#1580](https://github.com/vatesfr/xo-web/issues/1580)
- Network status on VM edition [\#1573](https://github.com/vatesfr/xo-web/issues/1573)
- VM template deletion fails [\#1571](https://github.com/vatesfr/xo-web/issues/1571)
- Template edition - "no such object" [\#1569](https://github.com/vatesfr/xo-web/issues/1569)
- missing links / element not displayed as links [\#1567](https://github.com/vatesfr/xo-web/issues/1567)
- Backup restore stalled on some SMB shares [\#1412](https://github.com/vatesfr/xo-web/issues/1412)
- Wrong bond display [\#1156](https://github.com/vatesfr/xo-web/issues/1156)
## **5.2.2** (2016-09-21)
### Enhancements
- `pool.setDefaultSr\(\)` should not require `pool` param [\#1558](https://github.com/vatesfr/xo-web/issues/1558)
- Select default SR [\#1554](https://github.com/vatesfr/xo-web/issues/1554)
- No error message when I exceed my resource set quota [\#1541](https://github.com/vatesfr/xo-web/issues/1541)
- Hide some buttons for self service VMs [\#1539](https://github.com/vatesfr/xo-web/issues/1539)
- Add Job ID to backup schedules [\#1534](https://github.com/vatesfr/xo-web/issues/1534)
- Correct name for VM selector with templates [\#1530](https://github.com/vatesfr/xo-web/issues/1530)
- Help text when no matches for a filter [\#1517](https://github.com/vatesfr/xo-web/issues/1517)
- Icon or tooltip to allow VDI migration in VM disk view [\#1512](https://github.com/vatesfr/xo-web/issues/1512)
- Create a snapshot before restoring one [\#1445](https://github.com/vatesfr/xo-web/issues/1445)
- Auto power on setting at creation time [\#1444](https://github.com/vatesfr/xo-web/issues/1444)
- local remotes should be avoided if possible [\#1441](https://github.com/vatesfr/xo-web/issues/1441)
- Self service edition unclear [\#1429](https://github.com/vatesfr/xo-web/issues/1429)
- Avoid "\_" char in job tag name [\#1414](https://github.com/vatesfr/xo-web/issues/1414)
- Display message if host reboot needed to apply patches [\#1352](https://github.com/vatesfr/xo-web/issues/1352)
- Color code on host PIF stats can be misleading [\#1265](https://github.com/vatesfr/xo-web/issues/1265)
- Sign in page is not rendered correctly [\#1161](https://github.com/vatesfr/xo-web/issues/1161)
- Template management [\#1091](https://github.com/vatesfr/xo-web/issues/1091)
### Bug fixes
- Multiple reboot selection doesn't work [\#1562](https://github.com/vatesfr/xo-web/issues/1562)
- Server logs should be displayed in reverse chonological order [\#1547](https://github.com/vatesfr/xo-web/issues/1547)
- Cannot create resource sets without limits [\#1537](https://github.com/vatesfr/xo-web/issues/1537)
- UI - Weird display when editing long VM desc [\#1528](https://github.com/vatesfr/xo-web/issues/1528)
- Useless iso selector in host console [\#1527](https://github.com/vatesfr/xo-web/issues/1527)
- Pool and Host dummy welcome message [\#1519](https://github.com/vatesfr/xo-web/issues/1519)
- Bug on Network VM tab [\#1518](https://github.com/vatesfr/xo-web/issues/1518)
- Link to home with filter in query does not work [\#1513](https://github.com/vatesfr/xo-web/issues/1513)
- VHD merge fails with "RangeError: index out of range" on SMB remote [\#1511](https://github.com/vatesfr/xo-web/issues/1511)
- DR: previous VDIs are not removed [\#1510](https://github.com/vatesfr/xo-web/issues/1510)
- DR: previous copies not removed when same number as depth [\#1509](https://github.com/vatesfr/xo-web/issues/1509)
- Empty Saved Search doesn't load when set to default filter [\#1354](https://github.com/vatesfr/xo-web/issues/1354)
- Removing a user/group should delete its ACLs [\#899](https://github.com/vatesfr/xo-web/issues/899)
- OVA Import - XO stuck during import [\#1551](https://github.com/vatesfr/xo-web/issues/1551)
## **5.2.1** (2016-09-13)
### Enhancements
- On pool view: collapse network list [\#1461](https://github.com/vatesfr/xo-web/issues/1461)
- Alert when trying to reboot/halt the pool master XS [\#1458](https://github.com/vatesfr/xo-web/issues/1458)
- Adding tooltip on Home page [\#1456](https://github.com/vatesfr/xo-web/issues/1456)
- Docker container management functionality missing from v5 [\#1442](https://github.com/vatesfr/xo-web/issues/1442)
- bad error message - delete snapshot [\#1433](https://github.com/vatesfr/xo-web/issues/1433)
- Create tag during VM creation [\#1431](https://github.com/vatesfr/xo-web/issues/1431)
### Bug fixes
- SMB remote empty domain fails [\#1499](https://github.com/vatesfr/xo-web/issues/1499)
- Can't edit a remote password [\#1498](https://github.com/vatesfr/xo-web/issues/1498)
- Issue in VM create with CoreOS [\#1493](https://github.com/vatesfr/xo-web/issues/1493)
- Overlapping months in backup view [\#1488](https://github.com/vatesfr/xo-web/issues/1488)
- No line break for SSH key in user view [\#1475](https://github.com/vatesfr/xo-web/issues/1475)
- Create VIF UI issues [\#1472](https://github.com/vatesfr/xo-web/issues/1472)
## **5.2.0** (2016-09-09)
### Enhancements
- IP management [\#1350](https://github.com/vatesfr/xo-web/issues/1350), [\#988](https://github.com/vatesfr/xo-web/issues/988), [\#1427](https://github.com/vatesfr/xo-web/issues/1427) and [\#240](https://github.com/vatesfr/xo-web/issues/240)
- Update reverse proxy example [\#1474](https://github.com/vatesfr/xo-web/issues/1474)
- Improve log view [\#1467](https://github.com/vatesfr/xo-web/issues/1467)
- Backup Reports: e-mail subject [\#1463](https://github.com/vatesfr/xo-web/issues/1463)
- Backup Reports: report the error [\#1462](https://github.com/vatesfr/xo-web/issues/1462)
- Vif selector: select management network by default [\#1425](https://github.com/vatesfr/xo-web/issues/1425)
- Display when browser disconnected to server [\#1417](https://github.com/vatesfr/xo-web/issues/1417)
- Tooltip on OS icon in VM view [\#1416](https://github.com/vatesfr/xo-web/issues/1416)
- Display pool master [\#1407](https://github.com/vatesfr/xo-web/issues/1407)
- Missing tooltips in VM creation view [\#1402](https://github.com/vatesfr/xo-web/issues/1402)
- Handle VDB disconnect and connect [\#1397](https://github.com/vatesfr/xo-web/issues/1397)
- Eject host from a pool [\#1395](https://github.com/vatesfr/xo-web/issues/1395)
- Improve pool general view [\#1393](https://github.com/vatesfr/xo-web/issues/1393)
- Improve patching system [\#1392](https://github.com/vatesfr/xo-web/issues/1392)
- Pool name modification [\#1390](https://github.com/vatesfr/xo-web/issues/1390)
- Confirmation dialog before destroying VDIs [\#1388](https://github.com/vatesfr/xo-web/issues/1388)
- Tooltips for meter object [\#1387](https://github.com/vatesfr/xo-web/issues/1387)
- New Host assistant [\#1374](https://github.com/vatesfr/xo-web/issues/1374)
- New VM assistant [\#1373](https://github.com/vatesfr/xo-web/issues/1373)
- New SR assistant [\#1372](https://github.com/vatesfr/xo-web/issues/1372)
- Direct access to VDI listing from dashboard's SR usage breakdown [\#1371](https://github.com/vatesfr/xo-web/issues/1371)
- Can't set a network name at pool level [\#1368](https://github.com/vatesfr/xo-web/issues/1368)
- Change a few mouse over descriptions [\#1363](https://github.com/vatesfr/xo-web/issues/1363)
- Hide network install in VM create if template is HVM [\#1362](https://github.com/vatesfr/xo-web/issues/1362)
- SR space left during VM creation [\#1358](https://github.com/vatesfr/xo-web/issues/1358)
- Add destination SR on migration modal in VM view [\#1357](https://github.com/vatesfr/xo-web/issues/1357)
- Ability to create a new VM from a snapshot [\#1353](https://github.com/vatesfr/xo-web/issues/1353)
- Missing explanation/confirmation on Snapshot Page [\#1349](https://github.com/vatesfr/xo-web/issues/1349)
- Log view: expose API errors in the web UI [\#1344](https://github.com/vatesfr/xo-web/issues/1344)
- Registration on update page [\#1341](https://github.com/vatesfr/xo-web/issues/1341)
- Add export snapshot button [\#1336](https://github.com/vatesfr/xo-web/issues/1336)
- Use saved SSH keys in VM create CloudConfig [\#1319](https://github.com/vatesfr/xo-web/issues/1319)
- Collapse header in console view [\#1268](https://github.com/vatesfr/xo-web/issues/1268)
- Two max concurrent jobs in parallel [\#915](https://github.com/vatesfr/xo-web/issues/915)
- Handle OVA import via the web UI [\#709](https://github.com/vatesfr/xo-web/issues/709)
### Bug fixes
- Bug on VM console when header is hidden [\#1485](https://github.com/vatesfr/xo-web/issues/1485)
- Disks not removed when deleting multiple VMs [\#1484](https://github.com/vatesfr/xo-web/issues/1484)
- Do not display VDI disconnect button when a VM is not running [\#1470](https://github.com/vatesfr/xo-web/issues/1470)
- Do not display VIF disconnect button when a VM is not running [\#1468](https://github.com/vatesfr/xo-web/issues/1468)
- Error on migration if no default SR \(even when not used\) [\#1466](https://github.com/vatesfr/xo-web/issues/1466)
- DR issue while rotating old backup [\#1464](https://github.com/vatesfr/xo-web/issues/1464)
- Giving resource set to end-user ends with error [\#1448](https://github.com/vatesfr/xo-web/issues/1448)
- Error thrown when cancelling out of Delete User confirmation dialog [\#1439](https://github.com/vatesfr/xo-web/issues/1439)
- Wrong month label shown in Backup and Job scheduler [\#1438](https://github.com/vatesfr/xo-web/issues/1438)
- Bug on Self service creation/edition [\#1428](https://github.com/vatesfr/xo-web/issues/1428)
- ISO selection during VM create is not mounted after [\#1415](https://github.com/vatesfr/xo-web/issues/1415)
- Hosts general view: bad link for storage [\#1408](https://github.com/vatesfr/xo-web/issues/1408)
- Backup Schedule - "Month" and "Day of Week" display error [\#1404](https://github.com/vatesfr/xo-web/issues/1404)
- Migrate dialog doesn't present all available VIF's in new UI interface [\#1403](https://github.com/vatesfr/xo-web/issues/1403)
- NFS mount issues [\#1396](https://github.com/vatesfr/xo-web/issues/1396)
- Select component color [\#1391](https://github.com/vatesfr/xo-web/issues/1391)
- SR created with local path shouldn't be shared [\#1389](https://github.com/vatesfr/xo-web/issues/1389)
- Disks (VBD) are attached to VM in RO mode instead of RW even if RO is unchecked [\#1386](https://github.com/vatesfr/xo-web/issues/1386)
- Re-connection issues between server and XS hosts [\#1384](https://github.com/vatesfr/xo-web/issues/1384)
- Meter object style with Chrome 52 [\#1383](https://github.com/vatesfr/xo-web/issues/1383)
- Editing a rolling snapshot job seems to fail [\#1376](https://github.com/vatesfr/xo-web/issues/1376)
- Dashboard SR usage and total inverted [\#1370](https://github.com/vatesfr/xo-web/issues/1370)
- XenServer connection issue with host while using VGPUs [\#1369](https://github.com/vatesfr/xo-web/issues/1369)
- Job created with v4 are not correctly displayed in v5 [\#1366](https://github.com/vatesfr/xo-web/issues/1366)
- CPU accounting in resource set [\#1365](https://github.com/vatesfr/xo-web/issues/1365)
- Tooltip stay displayed when a button change state [\#1360](https://github.com/vatesfr/xo-web/issues/1360)
- Failure on host reboot [\#1351](https://github.com/vatesfr/xo-web/issues/1351)
- Editing Backup Jobs Without Compression, Slider Always Set To On [\#1339](https://github.com/vatesfr/xo-web/issues/1339)
- Month Selection on Backup Screen Wrong [\#1338](https://github.com/vatesfr/xo-web/issues/1338)
- Delta backup fail when removed VDIs [\#1333](https://github.com/vatesfr/xo-web/issues/1333)
## **5.1.0** (2016-07-26)
### Enhancements
- Improve backups timezone UI [\#1314](https://github.com/vatesfr/xo-web/issues/1314)
- HOME view submenus [\#1306](https://github.com/vatesfr/xo-web/issues/1306)
- Ability for a user to save SSH keys [\#1299](https://github.com/vatesfr/xo-web/issues/1299)
- \[ACLs\] Ability to select all hosts/VMs [\#1296](https://github.com/vatesfr/xo-web/issues/1296)
- Improve scheduling UI [\#1295](https://github.com/vatesfr/xo-web/issues/1295)
- Plugins: Predefined configurations [\#1289](https://github.com/vatesfr/xo-web/issues/1289)
- Button to recompute resource sets limits [\#1287](https://github.com/vatesfr/xo-web/issues/1287)
- Credit scheduler CAP and weight configuration [\#1283](https://github.com/vatesfr/xo-web/issues/1283)
- Migration form problem on the /v5/vms/\_\_UUID\_\_ page when doing xenmotion inside a pool [\#1254](https://github.com/vatesfr/xo-web/issues/1254)
- /v5/\#/pools/\_\_UUID\_\_: patch table improvement [\#1246](https://github.com/vatesfr/xo-web/issues/1246)
- /v5/\#/hosts/\_\_UUID\_\_: patch list improvements ? [\#1245](https://github.com/vatesfr/xo-web/issues/1245)
- F\*cking patches, how do they work? [\#1236](https://github.com/vatesfr/xo-web/issues/1236)
- Change Default Filter [\#1235](https://github.com/vatesfr/xo-web/issues/1235)
- Add a property on jobs to know their state [\#1232](https://github.com/vatesfr/xo-web/issues/1232)
- Spanish translation [\#1231](https://github.com/vatesfr/xo-web/issues/1231)
- Home: "Filter" input and keyboard focus [\#1228](https://github.com/vatesfr/xo-web/issues/1228)
- Display xenserver version [\#1225](https://github.com/vatesfr/xo-web/issues/1225)
- Plugin config: presets & defaults [\#1222](https://github.com/vatesfr/xo-web/issues/1222)
- Allow halted VM migration [\#1216](https://github.com/vatesfr/xo-web/issues/1216)
- Missing confirm dialog on critical button [\#1211](https://github.com/vatesfr/xo-web/issues/1211)
- Backup logs are not sortable [\#1196](https://github.com/vatesfr/xo-web/issues/1196)
- Page title with the name of current object [\#1185](https://github.com/vatesfr/xo-web/issues/1185)
- Existing VIF management [\#1176](https://github.com/vatesfr/xo-web/issues/1176)
- Do not display fast clone option is there isn't template disks [\#1172](https://github.com/vatesfr/xo-web/issues/1172)
- UI issue when adding a user [\#1159](https://github.com/vatesfr/xo-web/issues/1159)
- Combined values on stats [\#1158](https://github.com/vatesfr/xo-web/issues/1158)
- Parallel coordinates graph [\#1157](https://github.com/vatesfr/xo-web/issues/1157)
- VM creation on self-service as user [\#1155](https://github.com/vatesfr/xo-web/issues/1155)
- VM copy bulk action on home view [\#1154](https://github.com/vatesfr/xo-web/issues/1154)
- Better VDI map [\#1151](https://github.com/vatesfr/xo-web/issues/1151)
- Missing tooltips on buttons [\#1150](https://github.com/vatesfr/xo-web/issues/1150)
- Patching from pool view [\#1149](https://github.com/vatesfr/xo-web/issues/1149)
- Missing patches in dashboard [\#1148](https://github.com/vatesfr/xo-web/issues/1148)
- Improve tasks view [\#1147](https://github.com/vatesfr/xo-web/issues/1147)
- Home bulk VM migration [\#1146](https://github.com/vatesfr/xo-web/issues/1146)
- LDAP plugin clear password field [\#1145](https://github.com/vatesfr/xo-web/issues/1145)
- Cron default behavior [\#1144](https://github.com/vatesfr/xo-web/issues/1144)
- Modal for migrate on home [\#1143](https://github.com/vatesfr/xo-web/issues/1143)
- /v5/\#/srs/\_\_UUID\_\_: UI improvements [\#1142](https://github.com/vatesfr/xo-web/issues/1142)
- /v5/\#/pools/: some name should be links [\#1141](https://github.com/vatesfr/xo-web/issues/1141)
- create the page /v5/\#/pools/ [\#1140](https://github.com/vatesfr/xo-web/issues/1140)
- Dashboard: add links to different part of XOA [\#1139](https://github.com/vatesfr/xo-web/issues/1139)
- /v5/\#/dashboard/overview: add link on the "Top 5 SR Usage" graph [\#1135](https://github.com/vatesfr/xo-web/issues/1135)
- /v5/\#/backup/overview: display the error when there is one returned by xenserver on failed job. [\#1134](https://github.com/vatesfr/xo-web/issues/1134)
- /v5/: add an option to set the number of element displayed in tables [\#1133](https://github.com/vatesfr/xo-web/issues/1133)
- Updater refresh page after update [\#1131](https://github.com/vatesfr/xo-web/issues/1131)
- /v5/\#/settings/plugins [\#1130](https://github.com/vatesfr/xo-web/issues/1130)
- /v5/\#/new/sr: layout issue [\#1129](https://github.com/vatesfr/xo-web/issues/1129)
- v5 /v5/\#/vms/new: layout issue [\#1128](https://github.com/vatesfr/xo-web/issues/1128)
- v5 user page missing style [\#1127](https://github.com/vatesfr/xo-web/issues/1127)
- Remote helper/tester [\#1075](https://github.com/vatesfr/xo-web/issues/1075)
- Generate uiSchema from custom schema properties [\#951](https://github.com/vatesfr/xo-web/issues/951)
- Customizing VM names generation during batch creation [\#949](https://github.com/vatesfr/xo-web/issues/949)
### Bug fixes
- Plugins: Don't use `default` attributes in presets list [\#1288](https://github.com/vatesfr/xo-web/issues/1288)
- CPU weight must be an integer [\#1286](https://github.com/vatesfr/xo-web/issues/1286)
- Overview of self service is always empty [\#1282](https://github.com/vatesfr/xo-web/issues/1282)
- SR attach/creation issue [\#1281](https://github.com/vatesfr/xo-web/issues/1281)
- Self service resources not modified after a VM deletion [\#1276](https://github.com/vatesfr/xo-web/issues/1276)
- Scheduled jobs seems use GMT since 5.0 [\#1258](https://github.com/vatesfr/xo-web/issues/1258)
- Can't create a VM with disks on 2 different SRs [\#1257](https://github.com/vatesfr/xo-web/issues/1257)
- Graph display bug [\#1247](https://github.com/vatesfr/xo-web/issues/1247)
- /v5/#/hosts/__UUID__: Patch list not limited to the current pool [\#1244](https://github.com/vatesfr/xo-web/issues/1244)
- Replication issues [\#1233](https://github.com/vatesfr/xo-web/issues/1233)
- VM creation install method disabled fields [\#1198](https://github.com/vatesfr/xo-web/issues/1198)
- Update icon shouldn't be displayed when menu is collapsed [\#1188](https://github.com/vatesfr/xo-web/issues/1188)
- /v5/ : Load average graph axis issue [\#1167](https://github.com/vatesfr/xo-web/issues/1167)
- Some remote can't be opened [\#1164](https://github.com/vatesfr/xo-web/issues/1164)
- Bulk action for hosts in home and pool view [\#1153](https://github.com/vatesfr/xo-web/issues/1153)
- New Vif [\#1138](https://github.com/vatesfr/xo-web/issues/1138)
- Missing SRs [\#1123](https://github.com/vatesfr/xo-web/issues/1123)
- Continuous replication email alert does not obey per job setting [\#1121](https://github.com/vatesfr/xo-web/issues/1121)
- Safari XO5 issue [\#1120](https://github.com/vatesfr/xo-web/issues/1120)
- ACLs shoud be available in Enterprise Edition [\#1118](https://github.com/vatesfr/xo-web/issues/1118)
- SR edit name or description doesn't work [\#1116](https://github.com/vatesfr/xo-web/issues/1116)
- Bad RRD parsing for VIFs [\#969](https://github.com/vatesfr/xo-web/issues/969)
## **5.0.0** (2016-06-24)
### Enhancements
- Handle failed quiesce in snapshots [\#1088](https://github.com/vatesfr/xo-web/issues/1088)
- Sparklines stats [\#1061](https://github.com/vatesfr/xo-web/issues/1061)
- Task view [\#1060](https://github.com/vatesfr/xo-web/issues/1060)
- Improved import system [\#1048](https://github.com/vatesfr/xo-web/issues/1048)
- Backup restore view improvements [\#1021](https://github.com/vatesfr/xo-web/issues/1021)
- Restore VM - Wrong VLAN on the VMs interface [\#1016](https://github.com/vatesfr/xo-web/issues/1016)
- Fast Disk Cloning [\#960](https://github.com/vatesfr/xo-web/issues/960)
- Disaster recovery job should target SRs, not pools [\#955](https://github.com/vatesfr/xo-web/issues/955)
- Improve Header/Content interaction in a page [\#926](https://github.com/vatesfr/xo-web/issues/926)
- New default view [\#912](https://github.com/vatesfr/xo-web/issues/912)
- Xen Patching - Restart Pending [\#883](https://github.com/vatesfr/xo-web/issues/883)
- Hide About page for user that are not admin [\#877](https://github.com/vatesfr/xo-web/issues/877)
- ACL: Ability to view/sort/group by User/Group, Objects or Role [\#875](https://github.com/vatesfr/xo-web/issues/875)
- ACL: Ability to select multiple users & group when creating a rule [\#874](https://github.com/vatesfr/xo-web/issues/874)
- Translation [\#839](https://github.com/vatesfr/xo-web/issues/839)
- XO offer useless network interfaces for XenMontion [\#833](https://github.com/vatesfr/xo-web/issues/833)
- Show HVM, PVM, PVHVM modes in guest details [\#806](https://github.com/vatesfr/xo-web/issues/806)
- Tree view: display cpu available/total for each host [\#696](https://github.com/vatesfr/xo-web/issues/696)
- Greenkeeper integration [\#667](https://github.com/vatesfr/xo-web/issues/667)
- Clarify vCPUs and RAM editor [\#658](https://github.com/vatesfr/xo-web/issues/658)
- Backup LZ4 compression [\#647](https://github.com/vatesfr/xo-web/issues/647)
- Support enum in plugins configuration [\#638](https://github.com/vatesfr/xo-web/issues/638)
- Add configuration option to disable xoa-updater [\#535](https://github.com/vatesfr/xo-web/issues/535)
- Use cursors to add more context to actions [\#523](https://github.com/vatesfr/xo-web/issues/523)
- Review UI for flat view [\#354](https://github.com/vatesfr/xo-web/issues/354)
- Review UI for the tree view [\#353](https://github.com/vatesfr/xo-web/issues/353)
- Tag filtering [\#233](https://github.com/vatesfr/xo-web/issues/233)
- GUI review [\#230](https://github.com/vatesfr/xo-web/issues/230)
- Review UI for VM creation [\#214](https://github.com/vatesfr/xo-web/issues/214)
- Ability to collapse pools/hosts in main view [\#173](https://github.com/vatesfr/xo-web/issues/173)
- Issue importing .xva VM via xo-web [\#1022](https://github.com/vatesfr/xo-web/issues/1022)
- Enhancement Proposal - Cancel In Progress Backups [\#1003](https://github.com/vatesfr/xo-web/issues/1003)
- Can't create VM with CloudConfigDrive [\#917](https://github.com/vatesfr/xo-web/issues/917)
- Auth: LDAP User causes problems [\#893](https://github.com/vatesfr/xo-web/issues/893)
- No tags in Continuous Replication [\#838](https://github.com/vatesfr/xo-web/issues/838)
- Delta backup Depth not working [\#802](https://github.com/vatesfr/xo-web/issues/802)
- Update Section - Running version info missing - gui enhancement [\#795](https://github.com/vatesfr/xo-web/issues/795)
- On reboot, vnc console wrongly scaled [\#722](https://github.com/vatesfr/xo-web/issues/722)
- Make the object name \(title\) "sticky" at the top of the page [\#705](https://github.com/vatesfr/xo-web/issues/705)
- pool view: display Local SR from hosts in the current pool [\#692](https://github.com/vatesfr/xo-web/issues/692)
- tree view: display all IPs [\#689](https://github.com/vatesfr/xo-web/issues/689)
- XO5 parallel distribution [\#462](https://github.com/vatesfr/xo-web/issues/462)
- Load balancing with XO [\#423](https://github.com/vatesfr/xo-web/issues/423)
### Bug fixes
- vCPUs number when no tools installed [\#1089](https://github.com/vatesfr/xo-web/issues/1089)
- Config Drive textbox disappears when content is deleted [\#1012](https://github.com/vatesfr/xo-web/issues/1012)
- storage status not changed in host view page after disconnect/connect [\#1009](https://github.com/vatesfr/xo-web/issues/1009)
- Cannot Delete Logs From Backup Overview [\#1004](https://github.com/vatesfr/xo-web/issues/1004)
- \[v5.x\] Plugins configuration: optional non-used objects are sent [\#1000](https://github.com/vatesfr/xo-web/issues/1000)
- "@" char in remote password break the remote view [\#997](https://github.com/vatesfr/xo-web/issues/997)
- Handle MEMORY\_CONSTRAINT\_VIOLATION correctly [\#970](https://github.com/vatesfr/xo-web/issues/970)
- VM creation error on XenServer Dundee [\#964](https://github.com/vatesfr/xo-web/issues/964)
- Copy VMs doesn't display all SRs [\#945](https://github.com/vatesfr/xo-web/issues/945)
- Autopower\_on wrong value [\#937](https://github.com/vatesfr/xo-web/issues/937)
- Correctly handle unknown users in group view [\#900](https://github.com/vatesfr/xo-web/issues/900)
- Importing into Dundee [\#887](https://github.com/vatesfr/xo-web/issues/887)
- update status - gui resize issue [\#803](https://github.com/vatesfr/xo-web/issues/803)
- Backup Remote Stores Problem [\#751](https://github.com/vatesfr/xo-web/issues/751)
- VM view is broken when changing a disk SR twice [\#670](https://github.com/vatesfr/xo-web/issues/670)
- console mouse sync [\#280](https://github.com/vatesfr/xo-web/issues/280)
## **4.16.0** (2016-04-29)
Maintenance release
### Enhancements
- TOO\_MANY\_PENDING\_TASKS [\#861](https://github.com/vatesfr/xo-web/issues/861)
### Bug fixes
- Incorrect VM target name with continuous replication [\#904](https://github.com/vatesfr/xo-web/issues/904)
- Error while deleting users [\#901](https://github.com/vatesfr/xo-web/issues/901)
- Use an available path to the SR to create a config drive [\#882](https://github.com/vatesfr/xo-web/issues/882)
- VM autoboot don't set the right pool parameter [\#879](https://github.com/vatesfr/xo-web/issues/879)
- BUG: ACL with NFS ISO Library not working! [\#870](https://github.com/vatesfr/xo-web/issues/870)
- Broken paths in backups in SMB [\#865](https://github.com/vatesfr/xo-web/issues/865)
- Plugins page loads users/groups multiple times [\#829](https://github.com/vatesfr/xo-web/issues/829)
- "Ghost" VM remains after migration [\#769](https://github.com/vatesfr/xo-web/issues/769)
## **4.15.0** (2016-03-21)
Load balancing, SMB delta support, advanced network operations...
### Enhancements
- Add the job name inside the backup email report [\#819](https://github.com/vatesfr/xo-web/issues/819)
- Delta backup with quiesce [\#812](https://github.com/vatesfr/xo-web/issues/812)
- Hosts: No user feedback when error occurs with SR connect / disconnect [\#810](https://github.com/vatesfr/xo-web/issues/810)
- Expose components versions [\#807](https://github.com/vatesfr/xo-web/issues/807)
- Rework networks/PIFs management [\#805](https://github.com/vatesfr/xo-web/issues/805)
- Displaying all SRs and a list of available hosts for creating VM from a pool [\#790](https://github.com/vatesfr/xo-web/issues/790)
- Add "Source network" on "VM migration" screen [\#785](https://github.com/vatesfr/xo-web/issues/785)
- Migration queue [\#783](https://github.com/vatesfr/xo-web/issues/783)
- Match network names for VM migration [\#782](https://github.com/vatesfr/xo-web/issues/782)
- Disk names [\#780](https://github.com/vatesfr/xo-web/issues/780)
- Self service: should the user be able to set the CPU weight? [\#767](https://github.com/vatesfr/xo-web/issues/767)
- host & pool Citrix license status [\#763](https://github.com/vatesfr/xo-web/issues/763)
- pool view: Provide "updates" section [\#762](https://github.com/vatesfr/xo-web/issues/762)
- XOA ISO image: ambigious root disk label [\#761](https://github.com/vatesfr/xo-web/issues/761)
- Host info: provide system serial number [\#760](https://github.com/vatesfr/xo-web/issues/760)
- CIFS ISO SR Creation [\#731](https://github.com/vatesfr/xo-web/issues/731)
- MAC address not preserved on VM restore [\#707](https://github.com/vatesfr/xo-web/issues/707)
- Failing replication job should send reports [\#659](https://github.com/vatesfr/xo-web/issues/659)
- Display networks in the Pool view [\#226](https://github.com/vatesfr/xo-web/issues/226)
### Bug fixes
- Broken link to backup remote [\#821](https://github.com/vatesfr/xo-web/issues/821)
- Issue with self-signed cert for email plugin [\#817](https://github.com/vatesfr/xo-web/issues/817)
- Plugins view, reset form and errors [\#815](https://github.com/vatesfr/xo-web/issues/815)
- HVM recovery mode is broken [\#794](https://github.com/vatesfr/xo-web/issues/794)
- Disk bug when creating vm from template [\#778](https://github.com/vatesfr/xo-web/issues/778)
- Can't mount NFS shares in remote stores [\#775](https://github.com/vatesfr/xo-web/issues/775)
- VM disk name and description not passed during creation [\#774](https://github.com/vatesfr/xo-web/issues/774)
- NFS mount problem for Windows share [\#771](https://github.com/vatesfr/xo-web/issues/771)
- lodash.pluck not installed [\#757](https://github.com/vatesfr/xo-web/issues/757)
- this.\_getAuthenticationTokensForUser is not a function [\#755](https://github.com/vatesfr/xo-web/issues/755)
- CentOS 6.x 64bit template creates a VM that won't boot [\#733](https://github.com/vatesfr/xo-web/issues/733)
- Lot of xo:perf leading to XO crash [\#575](https://github.com/vatesfr/xo-web/issues/575)
- New collection checklist [\#262](https://github.com/vatesfr/xo-web/issues/262)
## **4.14.0** (2016-02-23)
Self service, custom CloudInit...
### Enhancements
- VM creation self service with quotas [\#285](https://github.com/vatesfr/xo-web/issues/285)
- Cloud config custom user data [\#706](https://github.com/vatesfr/xo-web/issues/706)
- Patches behind a proxy [\#737](https://github.com/vatesfr/xo-web/issues/737)
- Remote store status indicator [\#728](https://github.com/vatesfr/xo-web/issues/728)
- Patch list order [\#724](https://github.com/vatesfr/xo-web/issues/724)
- Enable reporting on additional backup types [\#717](https://github.com/vatesfr/xo-web/issues/717)
- Tooltip name for cancel [\#703](https://github.com/vatesfr/xo-web/issues/703)
- Portable VHD merging [\#646](https://github.com/vatesfr/xo-web/issues/646)
### Bug fixes
- Avoid merge between two delta vdi backups [\#702](https://github.com/vatesfr/xo-web/issues/702)
- Text in table is not cut anymore [\#713](https://github.com/vatesfr/xo-web/issues/713)
- Disk size edition issue with float numbers [\#719](https://github.com/vatesfr/xo-web/issues/719)
- Create vm, summary is not refreshed [\#721](https://github.com/vatesfr/xo-web/issues/721)
- Boot order problem [\#726](https://github.com/vatesfr/xo-web/issues/726)
## **4.13.0** (2016-02-05)
Backup checksum, SMB remotes...
### Enhancements
- Add SMB mount for remote [\#338](https://github.com/vatesfr/xo-web/issues/338)
- Centralize Perm in a lib [\#345](https://github.com/vatesfr/xo-web/issues/345)
- Expose interpool migration details [\#567](https://github.com/vatesfr/xo-web/issues/567)
- Add checksum for delta backup [\#617](https://github.com/vatesfr/xo-web/issues/617)
- Redirect from HTTP to HTTPS [\#626](https://github.com/vatesfr/xo-web/issues/626)
- Expose vCPU weight [\#633](https://github.com/vatesfr/xo-web/issues/633)
- Avoid metadata in delta backup [\#651](https://github.com/vatesfr/xo-web/issues/651)
- Button to clear logs [\#661](https://github.com/vatesfr/xo-web/issues/661)
- Units for RAM and disks [\#666](https://github.com/vatesfr/xo-web/issues/666)
- Remove multiple VDIs at once [\#676](https://github.com/vatesfr/xo-web/issues/676)
- Find orphaned VDI snapshots [\#679](https://github.com/vatesfr/xo-web/issues/679)
- New health view in Dashboard [\#680](https://github.com/vatesfr/xo-web/issues/680)
- Use physical usage for VDI and SR [\#682](https://github.com/vatesfr/xo-web/issues/682)
- TLS configuration [\#685](https://github.com/vatesfr/xo-web/issues/685)
- Better VM info on tree view [\#688](https://github.com/vatesfr/xo-web/issues/688)
- Absolute values in tooltips for tree view [\#690](https://github.com/vatesfr/xo-web/issues/690)
- Absolute values for host memory [\#691](https://github.com/vatesfr/xo-web/issues/691)
### Bug fixes
- Issues on host console screen [\#672](https://github.com/vatesfr/xo-web/issues/672)
- NFS remote mount fails in particular case [\#665](https://github.com/vatesfr/xo-web/issues/665)
- Unresponsive pages [\#662](https://github.com/vatesfr/xo-web/issues/662)
- Live migration fail in the same pool with local SR fails [\#655](https://github.com/vatesfr/xo-web/issues/655)
## **4.12.0** (2016-01-18)
Continuous Replication, Continuous Delta backup...
### Enhancements
- Continuous VM replication [\#582](https://github.com/vatesfr/xo-web/issues/582)
- Continuous Delta Backup [\#576](https://github.com/vatesfr/xo-web/issues/576)
- Scheduler should not run job again if previous instance is not finished [\#642](https://github.com/vatesfr/xo-web/issues/642)
- Boot VM automatically after creation [\#635](https://github.com/vatesfr/xo-web/issues/635)
- Manage existing VIFs in templates [\#630](https://github.com/vatesfr/xo-web/issues/630)
- Support templates with existing install repository [\#627](https://github.com/vatesfr/xo-web/issues/627)
- Remove running VMs [\#616](https://github.com/vatesfr/xo-web/issues/616)
- Prevent a VM to start before delta import is finished [\#613](https://github.com/vatesfr/xo-web/issues/613)
- Spawn multiple VMs at once [\#606](https://github.com/vatesfr/xo-web/issues/606)
- Fixed `suspendVM` in tree view. [\#619](https://github.com/vatesfr/xo-web/pull/619) ([pdonias](https://github.com/pdonias))
### Bug fixes
- User defined MAC address is not fetch in VM install [\#643](https://github.com/vatesfr/xo-web/issues/643)
- CoreOsCloudConfig is not shown with CoreOS [\#639](https://github.com/vatesfr/xo-web/issues/639)
- Plugin activation/deactivation in web UI seems broken [\#637](https://github.com/vatesfr/xo-web/issues/637)
- Issue when creating CloudConfig drive [\#636](https://github.com/vatesfr/xo-web/issues/636)
- CloudConfig hostname shouldn't have space [\#634](https://github.com/vatesfr/xo-web/issues/634)
- Cloned VIFs are not properly deleted on VM creation [\#632](https://github.com/vatesfr/xo-web/issues/632)
- Default PV args missing during VM creation [\#628](https://github.com/vatesfr/xo-web/issues/628)
- VM creation problems from custom templates [\#625](https://github.com/vatesfr/xo-web/issues/625)
- Emergency shutdown race condition [\#622](https://github.com/vatesfr/xo-web/issues/622)
- `vm.delete\(\)` should not delete VDIs attached to other VMs [\#621](https://github.com/vatesfr/xo-web/issues/621)
- VM creation error from template with a disk [\#581](https://github.com/vatesfr/xo-web/issues/581)
- Only delete VDI exports when VM backup is successful [\#644](https://github.com/vatesfr/xo-web/issues/644)
- Change the name of an imported VM during the import process [\#641](https://github.com/vatesfr/xo-web/issues/641)
- Creating a new VIF in view is partially broken [\#652](https://github.com/vatesfr/xo-web/issues/652)
- Grey out the "create button" during VM creation [\#654](https://github.com/vatesfr/xo-web/issues/654)
## **4.11.0** (2015-12-22)
Delta backup, CloudInit...
### Enhancements
- Visible list of SR inside a VM [\#601](https://github.com/vatesfr/xo-web/issues/601)
- VDI move [\#591](https://github.com/vatesfr/xo-web/issues/591)
- Edit pre-existing disk configuration during VM creation [\#589](https://github.com/vatesfr/xo-web/issues/589)
- Allow disk size edition [\#587](https://github.com/vatesfr/xo-web/issues/587)
- Better VDI resize support [\#585](https://github.com/vatesfr/xo-web/issues/585)
- Remove manual VM export metadata in UI [\#580](https://github.com/vatesfr/xo-web/issues/580)
- Support import VM metadata [\#579](https://github.com/vatesfr/xo-web/issues/579)
- Set a default pool SR [\#572](https://github.com/vatesfr/xo-web/issues/572)
- ISOs should be sorted by name [\#565](https://github.com/vatesfr/xo-web/issues/565)
- Button to boot a VM from a disc once [\#564](https://github.com/vatesfr/xo-web/issues/564)
- Ability to boot a PV VM from a disc [\#563](https://github.com/vatesfr/xo-web/issues/563)
- Add an option to manually run backup jobs [\#562](https://github.com/vatesfr/xo-web/issues/562)
- backups to unmounted storage [\#561](https://github.com/vatesfr/xo-web/issues/561)
- Root integer properties cannot be edited in plugins configuration form [\#550](https://github.com/vatesfr/xo-web/issues/550)
- Generic CloudConfig drive [\#549](https://github.com/vatesfr/xo-web/issues/549)
- Auto-discovery of installed xo-server plugins [\#546](https://github.com/vatesfr/xo-web/issues/546)
- Hide info on flat view [\#545](https://github.com/vatesfr/xo-web/issues/545)
- Config plugin boolean properties must have a default value \(undefined prohibited\) [\#543](https://github.com/vatesfr/xo-web/issues/543)
- Present detailed errors on plugin configuration failures [\#530](https://github.com/vatesfr/xo-web/issues/530)
- Do not reset form on failures in plugins configuration [\#529](https://github.com/vatesfr/xo-web/issues/529)
- XMPP alert plugin [\#518](https://github.com/vatesfr/xo-web/issues/518)
- Hide tag adders depending on ACLs [\#516](https://github.com/vatesfr/xo-web/issues/516)
- Choosing a framework for xo-web 5 [\#514](https://github.com/vatesfr/xo-web/issues/514)
- Prevent adding a host in an existing XAPI connection [\#466](https://github.com/vatesfr/xo-web/issues/466)
- Read only connection to Xen servers/pools [\#439](https://github.com/vatesfr/xo-web/issues/439)
- generic notification system [\#391](https://github.com/vatesfr/xo-web/issues/391)
- Data architecture review [\#384](https://github.com/vatesfr/xo-web/issues/384)
- Make filtering easier to understand/add some "default" filters [\#207](https://github.com/vatesfr/xo-web/issues/207)
- Improve performance [\#148](https://github.com/vatesfr/xo-web/issues/148)
### Bug fixes
- VM metadata export should not require a snapshot [\#615](https://github.com/vatesfr/xo-web/issues/615)
- Missing patch for all hosts is continuously refreshed [\#609](https://github.com/vatesfr/xo-web/issues/609)
- Backup import memory issue [\#608](https://github.com/vatesfr/xo-web/issues/608)
- Host list missing patch is buggy [\#604](https://github.com/vatesfr/xo-web/issues/604)
- Servers infos should not been refreshed while a field is being edited [\#595](https://github.com/vatesfr/xo-web/issues/595)
- Servers list should not been re-order while a field is being edited [\#594](https://github.com/vatesfr/xo-web/issues/594)
- Correctly display size in interface \(binary scale\) [\#592](https://github.com/vatesfr/xo-web/issues/592)
- Display failures on VM boot order modification [\#560](https://github.com/vatesfr/xo-web/issues/560)
- `vm.setBootOrder\(\)` should throw errors on failures \(non-HVM VMs\) [\#559](https://github.com/vatesfr/xo-web/issues/559)
- Hide boot order form for non-HVM VMs [\#558](https://github.com/vatesfr/xo-web/issues/558)
- Allow editing PV args even when empty \(but only for PV VMs\) [\#557](https://github.com/vatesfr/xo-web/issues/557)
- Crashes when using legacy event system [\#556](https://github.com/vatesfr/xo-web/issues/556)
- XenServer patches check error for 6.1 [\#555](https://github.com/vatesfr/xo-web/issues/555)
- activation plugin xo-server-transport-email [\#553](https://github.com/vatesfr/xo-web/issues/553)
- Server error with JSON on 32 bits Dom0 [\#552](https://github.com/vatesfr/xo-web/issues/552)
- Cloud Config drive shouldn't be created on default SR [\#548](https://github.com/vatesfr/xo-web/issues/548)
- Deep properties cannot be edited in plugins configuration form [\#521](https://github.com/vatesfr/xo-web/issues/521)
- Aborted VM export should cancel the operation [\#490](https://github.com/vatesfr/xo-web/issues/490)
- VM missing with same UUID after an inter-pool migration [\#284](https://github.com/vatesfr/xo-web/issues/284)
## **4.10.0** (2015-11-27)
Job management, email notifications, CoreOS/Docker, Quiesce snapshots...
### Enhancements
- Job management ([xo-web#487](https://github.com/vatesfr/xo-web/issues/487))
- Patch upload on all connected servers ([xo-web#168](https://github.com/vatesfr/xo-web/issues/168))
- Emergency shutdown ([xo-web#185](https://github.com/vatesfr/xo-web/issues/185))
- CoreOS/docker template install ([xo-web#246](https://github.com/vatesfr/xo-web/issues/246))
- Email for backups ([xo-web#308](https://github.com/vatesfr/xo-web/issues/308))
- Console Clipboard ([xo-web#408](https://github.com/vatesfr/xo-web/issues/408))
- Logs from CLI ([xo-web#486](https://github.com/vatesfr/xo-web/issues/486))
- Save disconnected servers ([xo-web#489](https://github.com/vatesfr/xo-web/issues/489))
- Snapshot with quiesce ([xo-web#491](https://github.com/vatesfr/xo-web/issues/491))
- Start VM in reovery mode ([xo-web#495](https://github.com/vatesfr/xo-web/issues/495))
- Username in logs ([xo-web#498](https://github.com/vatesfr/xo-web/issues/498))
- Delete associated tokens with user ([xo-web#500](https://github.com/vatesfr/xo-web/issues/500))
- Validate plugin configuration ([xo-web#503](https://github.com/vatesfr/xo-web/issues/503))
- Avoid non configured plugins to be loaded ([xo-web#504](https://github.com/vatesfr/xo-web/issues/504))
- Verbose API logs if configured ([xo-web#505](https://github.com/vatesfr/xo-web/issues/505))
- Better backup overview ([xo-web#512](https://github.com/vatesfr/xo-web/issues/512))
- VM auto power on ([xo-web#519](https://github.com/vatesfr/xo-web/issues/519))
- Title property supported in config schema ([xo-web#522](https://github.com/vatesfr/xo-web/issues/522))
- Start VM export only when necessary ([xo-web#534](https://github.com/vatesfr/xo-web/issues/534))
- Input type should be number ([xo-web#538](https://github.com/vatesfr/xo-web/issues/538))
### Bug fixes
- Numbers/int support in plugins config ([xo-web#531](https://github.com/vatesfr/xo-web/issues/531))
- Boolean support in plugins config ([xo-web#528](https://github.com/vatesfr/xo-web/issues/528))
- Keyboard unusable outside console ([xo-web#513](https://github.com/vatesfr/xo-web/issues/513))
- UsernameField for SAML ([xo-web#513](https://github.com/vatesfr/xo-web/issues/513))
- Wrong display of "no plugin found" ([xo-web#508](https://github.com/vatesfr/xo-web/issues/508))
- Bower build error ([xo-web#488](https://github.com/vatesfr/xo-web/issues/488))
- VM cloning should require SR permission ([xo-web#472](https://github.com/vatesfr/xo-web/issues/472))
- Xen tools status ([xo-web#471](https://github.com/vatesfr/xo-web/issues/471))
- Can't delete ghost user ([xo-web#464](https://github.com/vatesfr/xo-web/issues/464))
- Stats with old versions of Node ([xo-web#463](https://github.com/vatesfr/xo-web/issues/463))
## **4.9.0** (2015-11-13)
Automated DR, restore backup, VM copy

28
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1,28 @@
<!--
Welcome to the issue section of Xen Orchestra!
Here you can:
- report an issue
- propose an enhancement
- ask a question
The template below is only a proposition for your ticket, feel free to
change it as appropriate :)
-->
### Context
- **XO version**: XO appliance / `stable` branch / `next-release` branch
If from the sources:
- **Component**: xo-web / xo-server / *unknown*
- **Node/npm version**: *just execute `npm version`*
### Expected behavior
<!-- What you expect to happen -->
### Current behavior
<!-- What is actually happening -->

View File

@@ -1,4 +1,4 @@
# Xen Orchestra Web
# Xen Orchestra Web [![Build Status](https://travis-ci.org/vatesfr/xo-web.png?branch=master)](https://travis-ci.org/vatesfr/xo-web)
![](http://i.imgur.com/tRffA5y.png)
@@ -6,14 +6,11 @@ XO-Web is part of [Xen Orchestra](https://github.com/vatesfr/xo), a web interfac
It is a web client for [XO-Server](https://github.com/vatesfr/xo-server).
[![Dependency Status](https://david-dm.org/vatesfr/xo-web.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-web)
[![devDependency Status](https://david-dm.org/vatesfr/xo-web/dev-status.svg?theme=shields.io)](https://david-dm.org/vatesfr/xo-web#info=devDependencies)
___
## Installation
XOA or manual install procedure is [available here](https://github.com/vatesfr/xo/blob/master/doc/installation/README.md)
XOA or manual install procedure is [available here](https://xen-orchestra.com/docs/installation.html)
## Compilation
@@ -29,6 +26,31 @@ Development build:
$ npm run dev
```
### Environment
#### `NODE_ENV`
Set to *production* it disables many checks which result in increased
performance.
#### `XOA_PLAN`
- 1: Free
- 2: Starter
- 3: Enterprise
- 4: Premium
- 5: Sources
```js
if (process.env.XOA_PLAN < 5) {
console.log('included only in XOA')
}
if (process.env.XOA_PLAN > 3) {
console.log('included only in Premium and Sources')
}
```
## How to report a bug?
If you are certain the bug is exclusively related to XO-Web, you may use the [bugtracker of this repository](https://github.com/vatesfr/xo-web/issues).
@@ -38,8 +60,8 @@ Otherwise, please consider using the [bugtracker of the general repository](http
## Process for new release
```bash
# Switch to the master branch.
git checkout master
# Switch to the stable branch.
git checkout stable
# Fetches latest changes.
git pull --ff-only
@@ -53,12 +75,12 @@ npm version minor
# Go back to the next-release branch.
git checkout next-release
# Fetches the last changes (the merge and version bump) from master to
# Fetches the last changes (the merge and version bump) from stable to
# next-release.
git merge --ff-only master
git merge --ff-only stable
# Push the changes on git.
git push --follow-tags origin master next-release
git push --follow-tags origin stable next-release
# Publish this release to npm.
npm publish

View File

@@ -2,8 +2,8 @@
// ===================================================================
var SRC_DIR = __dirname + '/src'
var DIST_DIR = __dirname + '/dist'
var SRC_DIR = __dirname + '/src' // eslint-disable-line no-path-concat
var DIST_DIR = __dirname + '/dist' // eslint-disable-line no-path-concat
// Port to use for the livereload server.
//
@@ -11,20 +11,13 @@ var DIST_DIR = __dirname + '/dist'
// http://www.random.org/integers/?num=1&min=1024&max=65535&col=1&base=10&format=plain&rnd=new
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
// 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 PRODUCTION = process.argv.indexOf('--production') !== -1
var PRODUCTION = process.env.NODE_ENV === 'production'
var DEVELOPMENT = !PRODUCTION
if (!process.env.XOA_PLAN) {
process.env.XOA_PLAN = '5' // Open Source
}
// ===================================================================
var gulp = require('gulp')
@@ -45,20 +38,47 @@ function lazyFn (factory) {
// -------------------------------------------------------------------
var livereload = lazyFn(function () {
var livereload = require('gulp-livereload')
livereload.listen(LIVERELOAD_PORT)
var livereload = require('gulp-refresh')
livereload.listen({
port: 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 current
function pipeCore (streams) {
var i, n, stream
for (i = 0, n = streams.length; i < n; ++i) {
stream = streams[i]
if (!stream) {
// Nothing to do
} else if (stream instanceof Array) {
pipeCore(stream)
} else {
current = current
? current.pipe(stream)
: stream
}
}
}
var push = Array.prototype.push
return function (streams) {
try {
if (!(streams instanceof Array)) {
streams = []
push.apply(streams, arguments)
}
pipeCore(streams)
return current
} finally {
current = null
}
}
})
var resolvePath = lazyFn(function () {
@@ -77,23 +97,25 @@ var src = lazyFn(function () {
}
return PRODUCTION
? function src (pattern, base) {
base = resolve(base)
? function src (pattern, opts) {
var base = resolve(opts && opts.base)
return gulp.src(pattern, {
base: base,
cwd: base,
sourcemaps: true
passthrough: opts && opts.passthrough,
sourcemaps: opts && opts.sourcemaps
})
}
: function src (pattern, base) {
base = resolve(base)
: function src (pattern, opts) {
var base = resolve(opts && opts.base)
return pipe(
gulp.src(pattern, {
base: base,
cwd: base,
sourcemaps: true
passthrough: opts && opts.passthrough,
sourcemaps: opts && opts.sourcemaps
}),
require('gulp-watch')(pattern, {
base: base,
@@ -125,10 +147,9 @@ var dest = lazyFn(function () {
return gulp.dest(resolve(path), opts)
}
: function dest (path) {
return pipe(
gulp.dest(resolve(path), opts),
livereload()
)
var stream = gulp.dest(resolve(path), opts)
stream.pipe(livereload())
return stream
}
})
@@ -141,9 +162,10 @@ function browserify (path, opts) {
var bundler = require('browserify')(path, {
basedir: SRC_DIR,
debug: DEVELOPMENT,
debug: DEVELOPMENT, // TODO: enable also in production but need to make it work with gulp-uglify.
extensions: opts.extensions,
fullPaths: DEVELOPMENT,
fullPaths: false,
paths: SRC_DIR + '/common',
standalone: opts.standalone,
// Required by Watchify.
@@ -151,8 +173,15 @@ function browserify (path, opts) {
packageCache: {}
})
var plugins = opts.plugins
for (var i = 0, n = plugins && plugins.length; i < n; ++i) {
var plugin = plugins[i]
bundler.plugin(require(plugin[0]), plugin[1])
}
if (PRODUCTION) {
bundler.plugin('bundle-collapser/plugin')
// FIXME: does not work with react-intl (?!)
// bundler.plugin('bundle-collapser/plugin')
} else {
bundler = require('watchify')(bundler)
}
@@ -209,7 +238,7 @@ function browserify (path, opts) {
gulp.task(function buildPages () {
return pipe(
src('index.jade'),
src('index.jade', { sourcemaps: true }),
require('gulp-jade')(),
DEVELOPMENT && require('gulp-embedlr')({
port: LIVERELOAD_PORT
@@ -220,17 +249,22 @@ gulp.task(function buildPages () {
gulp.task(function buildScripts () {
return pipe(
browserify('./index.js'),
PRODUCTION && require('gulp-uglify')({
mangle: false // Avoid breaking Angular deps injector.
browserify('./index.js', {
plugins: [
// ['css-modulesify', {
['modular-css/browserify', {
css: DIST_DIR + '/modules.css'
}]
]
}),
PRODUCTION && require('gulp-uglify')(),
dest()
)
})
gulp.task(function buildStyles () {
return pipe(
src('index.scss'),
src('index.scss', { sourcemaps: true }),
require('gulp-sass')(),
require('gulp-autoprefixer')([
'last 1 version',
@@ -244,10 +278,14 @@ gulp.task(function buildStyles () {
gulp.task(function copyAssets () {
return pipe(
src(['assets/**/*', 'favicon.*']),
src(
'fontawesome-webfont.*',
__dirname + '/node_modules/font-awesome/fonts'
),
src('fontawesome-webfont.*', {
base: __dirname + '/node_modules/font-awesome/fonts', // eslint-disable-line no-path-concat
passthrough: true
}),
src(['!*.css', 'font-mfizz.*'], {
base: __dirname + '/node_modules/font-mfizz/dist', // eslint-disable-line no-path-concat
passthrough: true
}),
dest()
)
})
@@ -264,28 +302,3 @@ gulp.task('build', gulp.parallel(
gulp.task(function clean (done) {
require('rimraf')(DIST_DIR, done)
})
// -------------------------------------------------------------------
gulp.task(function server (done) {
require('connect')()
.use(require('serve-static')(DIST_DIR))
.listen(SERVER_PORT, SERVER_ADDR, function onListen () {
var address = this.address()
var port = address.port
address = address.address
// Correctly handle IPv6 addresses.
if (address.indexOf(':') !== -1) {
address = '[' + address + ']'
}
/* jshint devel: true*/
console.log('Listening on http://' + address + ':' + port)
})
.on('error', done)
.on('close', function onClose () {
done()
})
})

View File

@@ -1,7 +1,7 @@
{
"private": true,
"private": false,
"name": "xo-web",
"version": "5.0.0",
"version": "5.2.5",
"license": "AGPL-3.0",
"description": "Web interface client for Xen-Orchestra",
"keywords": [
@@ -27,81 +27,164 @@
"dist/"
],
"engines": {
"node": ">=0.12"
"node": ">=4",
"npm": ">=3"
},
"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",
"ansi_up": "^1.3.0",
"asap": "^2.0.4",
"ava": "^0.16.0",
"babel-eslint": "^7.0.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-react-constant-elements": "^6.5.0",
"babel-plugin-transform-react-inline-elements": "^6.6.5",
"babel-plugin-transform-react-jsx-self": "^6.11.0",
"babel-plugin-transform-react-jsx-source": "^6.9.0",
"babel-plugin-transform-runtime": "^6.6.0",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.5.0",
"babel-runtime": "^6.6.1",
"babelify": "^7.2.0",
"benchmark": "^2.1.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",
"chartist-plugin-legend": "^0.5.0",
"chartist-plugin-tooltip": "0.0.11",
"classnames": "^2.2.3",
"cookies-js": "^1.2.2",
"d3": "^4.0.0-alpha.50",
"dependency-check": "^2.5.1",
"event-to-promise": "^0.7.0",
"font-awesome": "^4.5.0",
"font-mfizz": "github:fizzed/font-mfizz",
"get-stream": "^2.3.0",
"ghooks": "^1.1.1",
"globby": "^6.0.0",
"gulp": "github:gulpjs/gulp#4.0",
"gulp-autoprefixer": "^3.1.0",
"gulp-csso": "^1.0.1",
"gulp-csso": "^2.0.0",
"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-plumber": "^1.1.0",
"gulp-refresh": "^1.1.0",
"gulp-sass": "^2.2.0",
"gulp-uglify": "^2.0.0",
"gulp-watch": "^4.3.5",
"history": "^2.0.0-rc2",
"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",
"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",
"human-format": "^0.6.0",
"index-modules": "0.0.0",
"is-ip": "^1.0.0",
"jsonrpc-websocket-client": "0.0.1-5",
"later": "^1.2.0",
"lodash": "^4.6.1",
"loose-envify": "^1.1.0",
"make-error": "^1.2.1",
"marked": "^0.3.5",
"modular-css": "^0.27.1",
"moment": "^2.13.0",
"moment-timezone": "^0.5.4",
"notifyjs": "^2.0.1",
"novnc-node": "^0.5.3",
"promise-toolbox": "^0.5.0",
"random-password": "^0.1.2",
"react": "^15.0.0",
"react-addons-shallow-compare": "^15.1.0",
"react-bootstrap-4": "^0.29.1",
"react-chartist": "^0.10.1",
"react-copy-to-clipboard": "^4.0.2",
"react-debounce-input": "^2.4.0",
"react-dnd": "^2.1.4",
"react-dnd-html5-backend": "^2.1.2",
"react-document-title": "^2.0.2",
"react-dom": "^15.0.0",
"react-dropzone": "^3.5.0",
"react-intl": "^2.0.1",
"react-key-handler": "^0.3.0",
"react-notify": "^2.0.1",
"react-redux": "^4.4.0",
"react-router": "^3.0.0-alpha.1",
"react-select": "^1.0.0-beta13",
"react-shortcuts": "^1.0.7",
"react-sparklines": "^1.5.0",
"react-virtualized": "^8.0.8",
"readable-stream": "^2.0.6",
"redux": "^3.3.1",
"redux-devtools": "^3.1.1",
"redux-devtools-dock-monitor": "^1.1.0",
"redux-devtools-log-monitor": "^1.0.5",
"redux-thunk": "^2.0.1",
"reselect": "^2.2.1",
"standard": "^8.2.0",
"superagent": "^2.0.0",
"tar-stream": "^1.5.2",
"vinyl": "^2.0.0",
"watchify": "^3.7.0",
"xo-lib": "^0.8.0-1"
"xml2js": "^0.4.17",
"xo-acl-resolver": "^0.2.2",
"xo-lib": "^0.8.0",
"xo-remote-parser": "^0.3"
},
"scripts": {
"build": "gulp build --production",
"dev": "gulp build server",
"dev-test": "mocha --opts .mocha.opts --watch --reporter=min \"dist/**/*.spec.js\"",
"benchmarks": "./tools/run-benchmarks.js 'src/**/*.bench.js'",
"build": "npm run build-indexes && NODE_ENV=production gulp build",
"build-indexes": "index-modules --auto src",
"dev": "npm run build-indexes && NODE_ENV=development gulp build",
"dev-test": "ava --watch",
"lint": "standard",
"depcheck": "dependency-check ./package.json",
"posttest": "npm run lint && npm run depcheck",
"posttest": "npm run lint",
"prepublish": "npm run build",
"test": "nyc mocha --opts .mocha.opts \"dist/**/*.spec.js\""
},
"browser": {
"node_modules/ws/index.js": "./ws.js"
"test": "ava"
},
"browserify": {
"transform": [
"babelify",
"browserify-plain-jade"
"loose-envify"
]
},
"ava": {
"babel": "inherit",
"files": [
"src/**/*.spec.js"
],
"require": [
"babel-register"
]
},
"babel": {
"env": {
"development": {
"plugins": [
"transform-react-jsx-self",
"transform-react-jsx-source"
]
},
"production": {
"plugins": [
"transform-react-constant-elements",
"transform-react-inline-elements"
]
}
},
"plugins": [
"transform-decorators-legacy",
"transform-runtime"
],
"presets": [
"es2015",
"react",
"stage-0"
]
},
"config": {
"ghooks": {
"commit-msg": "npm test"
}
},
"standard": {
"ignore": [
"dist"
],
"parser": "babel-eslint"
},
"dependencies": {
"babel-runtime": "^6.3.19",
"redux-promise": "^0.5.1"
}
}

1
src/assets/loading.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><svg width='62px' height='62px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-ring-alt"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><circle cx="50" cy="50" r="40" stroke="#cfcfcf" fill="none" stroke-width="10" stroke-linecap="round"></circle><circle cx="50" cy="50" r="40" stroke="#366e98" fill="none" stroke-width="6" stroke-linecap="round"><animate attributeName="stroke-dashoffset" dur="1s" repeatCount="indefinite" from="0" to="502"></animate><animate attributeName="stroke-dasharray" dur="1s" repeatCount="indefinite" values="150.6 100.4;1 250;150.6 100.4"></animate></circle></svg>

After

Width:  |  Height:  |  Size: 707 B

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

126
src/chartist.scss Normal file
View File

@@ -0,0 +1,126 @@
// CHARTIST ===================================================================
$ct-series-colors: (
$brand-success,
$brand-primary,
#f17cb0,
#86797d,
#b276b2,
#f15854,
#b2912f,
#decf3f,
#dda458,
#60bd68,
#4d4d4d,
#eacf7d,
#b2c326,
#6188e2,
#a748ca
) !default;
@import "../node_modules/chartist/dist/scss/settings/_chartist-settings";
@import "../node_modules/chartist/dist/scss/chartist";
.ct-chart {
display: flex;
flex-direction: column-reverse;
}
// Line in charts with only 2px in width
.ct-line {
stroke-width: 2px;
}
.ct-bar {
stroke-width: 10%;
}
.ct-point {
stroke-width: 30px;
stroke-opacity: 0!important;
}
.ct-point:hover {
stroke-opacity: 0.2!important;
stroke-width: 20px;
}
.ct-tooltip {
position: absolute;
display: inline-block;
min-width: 5em;
padding: 8px 10px;
background: #383838;
color: #fff;
text-align: center;
pointer-events: none;
z-index: 10;
font-weight: 700;
// Arrow!
&:before {
bottom: -14px;
top: 100%;
left: 50%;
border: solid transparent;
content: '';
height: 0;
width: 0;
pointer-events: none;
border-color: rgba(251, 249, 228, 0);
border-top-color: #383838;
border-width: 7px;
margin-left: -8px;
}
&.hide {
display: block;
opacity: 0;
visibility: hidden;
}
}
// CHARTIST LEGEND =============================================================
.ct-legend {
bottom: 0;
margin-bottom: -1em;
li {
position: relative;
padding-left: 0.5em;
list-style-type: none;
display: inline-block;
margin-right: 0.5em;
font-size: 0.8em;
}
li:before {
display: inline-block;
width: 1em;
height: 1em;
left: 0;
content: '';
border: 3px solid transparent;
border-radius: 2px;
margin-right: 0.2em;
}
li.inactive:before {
background: transparent;
}
&.ct-legend-inside {
position: absolute;
top: 0;
right: 0;
}
@for $i from 0 to length($ct-series-colors) {
.ct-series-#{$i}:before {
background-color: nth($ct-series-colors, $i + 1);
border-color: nth($ct-series-colors, $i + 1);
}
}
}

46
src/common/action-bar.js Normal file
View File

@@ -0,0 +1,46 @@
import _ from 'intl'
import ActionButton from 'action-button'
import map from 'lodash/map'
import React from 'react'
import Tooltip from 'tooltip'
import {
ButtonGroup
} from 'react-bootstrap-4/lib'
import {
noop
} from 'utils'
const ActionBar = ({ actions, param }) => (
<ButtonGroup>
{map(actions, (button, index) => {
if (!button) {
return
}
const { handler, handlerParam = param, label, icon, redirectOnSuccess } = button
return <Tooltip key={index} content={_(label)}>
<ActionButton
key={index}
btnStyle='secondary'
handler={handler || noop}
handlerParam={handlerParam}
icon={icon}
redirectOnSuccess={redirectOnSuccess}
size='large'
/>
</Tooltip>
})}
</ButtonGroup>
)
ActionBar.propTypes = {
actions: React.PropTypes.arrayOf(
React.PropTypes.shape({
label: React.PropTypes.string.isRequired,
icon: React.PropTypes.string.isRequired,
handler: React.PropTypes.func,
redirectOnSuccess: React.PropTypes.string
})
).isRequired,
display: React.PropTypes.oneOf(['icon', 'text', 'both'])
}
export { ActionBar as default }

130
src/common/action-button.js Normal file
View File

@@ -0,0 +1,130 @@
import Icon from 'icon'
import isFunction from 'lodash/isFunction'
import React from 'react'
import { Button } from 'react-bootstrap-4/lib'
import Component from './base-component'
import logError from './log-error'
import propTypes from './prop-types'
import Tooltip from './tooltip'
@propTypes({
btnStyle: propTypes.string,
disabled: propTypes.bool,
form: propTypes.string,
handler: propTypes.func.isRequired,
handlerParam: propTypes.any,
icon: propTypes.string.isRequired,
redirectOnSuccess: propTypes.oneOfType([
propTypes.func,
propTypes.string
]),
size: propTypes.oneOf([
'large',
'small'
]),
tooltip: propTypes.node
})
export default class ActionButton extends Component {
static contextTypes = {
router: React.PropTypes.object
}
async _execute () {
if (this.state.working) {
return
}
const {
handler,
handlerParam
} = this.props
try {
this.setState({
error: null,
working: true
})
const result = await handler(handlerParam)
let { redirectOnSuccess } = this.props
if (redirectOnSuccess) {
if (isFunction(redirectOnSuccess)) {
redirectOnSuccess = redirectOnSuccess(result)
}
return this.context.router.push(redirectOnSuccess)
}
this.setState({
working: false
})
} catch (error) {
this.setState({
error,
working: false
})
// ignore when undefined because it usually means that the action has been canceled
if (error !== undefined) {
logError(error)
}
}
}
_execute = ::this._execute
_eventListener = event => {
event.preventDefault()
this._execute()
}
componentDidMount () {
const { form } = this.props
if (form) {
document.getElementById(form).addEventListener('submit', this._eventListener)
}
}
componentWillUnmount () {
const { form } = this.props
if (form) {
document.getElementById(form).removeEventListener('submit', this._eventListener)
}
}
render () {
const {
props: {
btnStyle,
children,
className,
disabled,
form,
icon,
size: bsSize,
style,
tooltip
},
state: { error, working }
} = this
const button = <Button
bsStyle={error ? 'warning' : btnStyle}
form={form}
onClick={!form && this._execute}
disabled={working || disabled}
type={form ? 'submit' : 'button'}
{...{ bsSize, className, style }}
>
<Icon icon={working ? 'loading' : icon} fixedWidth />
{children && ' '}
{children}
</Button>
return tooltip
? <Tooltip content={tooltip}>{button}</Tooltip>
: button
}
}

View File

@@ -0,0 +1,7 @@
.button {
opacity: 0.5;
}
tr:hover .button, tr:focus .button {
opacity: 1;
}

View File

@@ -0,0 +1,14 @@
import React from 'react'
import ActionButton from '../action-button'
import styles from './index.css'
const ActionRowButton = props => (
<ActionButton
{...props}
className={styles.button}
size='small'
/>
)
export { ActionRowButton as default }

View File

@@ -0,0 +1,15 @@
import React from 'react'
import ActionButton from './action-button'
import propTypes from './prop-types'
const ActionToggle = ({ className, value, ...props }) =>
<ActionButton
{...props}
btnStyle={value ? 'success' : null}
icon={value ? 'toggle-on' : 'toggle-off'}
/>
export default propTypes({
value: propTypes.bool
})(ActionToggle)

View File

@@ -0,0 +1,139 @@
import clone from 'lodash/clone'
import includes from 'lodash/includes'
import isArray from 'lodash/isArray'
import forEach from 'lodash/forEach'
import map from 'lodash/map'
import { Component } from 'react'
import getEventValue from './get-event-value'
import invoke from './invoke'
import shallowEqual from './shallow-equal'
const cowSet = (object, path, value, depth) => {
if (depth >= path.length) {
return value
}
object = clone(object)
const prop = path[depth]
object[prop] = cowSet(object[prop], path, value, depth + 1)
return object
}
const get = (object, path, depth) => {
if (depth >= path.length) {
return object
}
const prop = path[depth++]
return isArray(object) && prop === '*'
? map(object, value => get(value, path, depth))
: get(object[prop], path, depth)
}
export default class BaseComponent extends Component {
constructor (props, context) {
super(props, context)
// It really should have been done in React.Component!
this.state = {}
this._linkedState = null
if (process.env.NODE_ENV !== 'production') {
this.render = invoke(this.render, render => () => {
console.log('render', this.constructor.name)
return render.call(this)
})
}
}
// See https://preactjs.com/guide/linked-state
linkState (name, targetPath) {
const key = targetPath
? `${name}##${targetPath}`
: name
let linkedState = this._linkedState
let cb
if (!linkedState) {
linkedState = this._linkedState = {}
} else if ((cb = linkedState[key])) {
return cb
}
let getValue
if (targetPath) {
const path = targetPath.split('.')
getValue = event => get(getEventValue(event), path, 0)
} else {
getValue = getEventValue
}
if (includes(name, '.')) {
const path = name.split('.')
return (linkedState[key] = event => {
this.setState(cowSet(this.state, path, getValue(event), 0))
})
}
return (linkedState[key] = event => {
this.setState({
[name]: getValue(event)
})
})
}
toggleState (name) {
let linkedState = this._linkedState
let cb
if (!linkedState) {
linkedState = this._linkedState = {}
} else if ((cb = linkedState[name])) {
return cb
}
if (includes(name, '.')) {
const path = name.split('.')
return (linkedState[path] = event => {
this.setState(cowSet(this.state, path, !get(this.state, path, 0), 0))
})
}
return (linkedState[name] = () => {
this.setState({
[name]: !this.state[name]
})
})
}
shouldComponentUpdate (newProps, newState) {
return !(
shallowEqual(this.props, newProps) &&
shallowEqual(this.state, newState)
)
}
}
if (process.env.NODE_ENV !== 'production') {
const diff = (name, old, cur) => {
const keys = []
forEach(old, (value, key) => {
if (cur[key] !== value) {
keys.push(key)
}
})
if (keys.length) {
console.log(name, keys.sort().join())
}
}
BaseComponent.prototype.componentDidUpdate = function (oldProps, oldState) {
const prefix = `${this.constructor.name} updated because of its`
diff(`${prefix} props:`, oldProps, this.props)
diff(`${prefix} state:`, oldState, this.state)
}
}

View File

@@ -0,0 +1,35 @@
import { noop } from 'utils'
import Notify from 'notifyjs'
let notify
export { notify as default }
const sendNotification = (title, body) => {
new Notify(title, {
body,
timeout: 5,
icon: 'assets/logo.png'
}).show()
}
const requestPermission = (...args) => {
if (Notify.isSupported()) {
Notify.requestPermission(
() => {
console.log('notifications allowed')
return (notify = sendNotification)(...args)
},
() => {
console.log('notifications denied')
notify = noop
}
)
} else {
notify = noop
console.warn('notifications are not supported')
}
}
notify = Notify.needsPermission ? requestPermission : sendNotification

51
src/common/card.js Normal file
View File

@@ -0,0 +1,51 @@
import React from 'react'
import propTypes from './prop-types'
const CARD_STYLE = {
minHeight: '100%'
}
const CARD_STYLE_WITH_SHADOW = {
...CARD_STYLE,
boxShadow: '0 10px 6px -6px #777' // https://css-tricks.com/almanac/properties/b/box-shadow/
}
const CARD_HEADER_STYLE = {
minHeight: '100%',
textAlign: 'center'
}
export const Card = propTypes({
disableMaxHeight: propTypes.bool,
shadow: propTypes.bool
})(({
children,
shadow
}) => (
<div className='card' style={shadow ? CARD_STYLE_WITH_SHADOW : CARD_STYLE}>
{children}
</div>
))
export const CardHeader = propTypes({
className: propTypes.string
})(({
children,
className
}) => (
<h4 className={`card-header ${className || ''}`} style={CARD_HEADER_STYLE}>
{children}
</h4>
))
export const CardBlock = propTypes({
className: propTypes.string
})(({
children,
className
}) => (
<div className={`card-block ${className || ''}`}>
{children}
</div>
))

View File

@@ -0,0 +1,9 @@
.container {
display: flex;
min-height: 100%;
}
.content {
margin: auto;
text-align: center;
}

View File

@@ -0,0 +1,12 @@
import React from 'react'
import styles from './index.css'
const CenterPanel = ({ children }) =>
<div className={styles.container}>
<div className={styles.content}>
{children}
</div>
</div>
export { CenterPanel as default }

32
src/common/collapse.js Normal file
View File

@@ -0,0 +1,32 @@
import React from 'react'
import Component from './base-component'
import Icon from './icon'
import propTypes from './prop-types'
@propTypes({
children: propTypes.any.isRequired,
className: propTypes.string,
buttonText: propTypes.any.isRequired
})
export default class Collapse extends Component {
_onClick = () => {
this.setState({
isOpened: !this.state.isOpened
})
}
render () {
const { props } = this
const { isOpened } = this.state
return (
<div className={props.className}>
<button className='btn btn-lg btn-primary btn-block' onClick={this._onClick}>
{props.buttonText} <Icon icon={`chevron-${isOpened ? 'up' : 'down'}`} />
</button>
{isOpened && props.children}
</div>
)
}
}

View File

@@ -0,0 +1,3 @@
.button {
border-radius: 0px;
};

View File

@@ -0,0 +1,101 @@
import map from 'lodash/map'
import React from 'react'
import size from 'lodash/size'
import Component from '../base-component'
import propTypes from '../prop-types'
import { ensureArray } from '../utils'
import {
DropdownButton,
MenuItem
} from 'react-bootstrap-4/lib'
import styles from './index.css'
@propTypes({
defaultValue: propTypes.any,
disabled: propTypes.bool,
options: propTypes.oneOfType([
propTypes.arrayOf(propTypes.string),
propTypes.number,
propTypes.objectOf(propTypes.string),
propTypes.string
]),
onChange: propTypes.func,
placeholder: propTypes.string,
required: propTypes.bool,
step: propTypes.any,
type: propTypes.string,
value: propTypes.any
})
export default class Combobox extends Component {
static defaultProps = {
type: 'text'
}
get value () {
return this.refs.input.value
}
set value (value) {
this.refs.input.value = value
}
_handleChange = event => {
const { onChange } = this.props
if (onChange) {
onChange(event.target.value)
}
}
_setText (value) {
this.refs.input.value = value
}
render () {
const { props } = this
const options = ensureArray(props.options)
const Input = (
<input
className='form-control'
defaultValue={props.defaultValue}
disabled={props.disabled}
options={options}
onChange={this._handleChange}
placeholder={props.placeholder}
ref='input'
required={props.required}
step={props.step}
type={props.type}
value={props.value}
/>
)
if (!size(options)) {
return Input
}
return (
<div className='input-group'>
<div className='input-group-btn'>
<DropdownButton
bsStyle='secondary'
className={styles.button}
disabled={props.disabled}
id='selectInput'
title=''
>
{map(options, option => (
<MenuItem key={option} onClick={() => this._setText(option)}>
{option}
</MenuItem>
))}
</DropdownButton>
</div>
{Input}
</div>
)
}
}

View File

@@ -0,0 +1,18 @@
import {
parse,
toString
} from './'
import {
ast,
pattern
} from './index.fixtures'
export default ({ benchmark }) => {
benchmark('parse', () => {
parse(pattern)
})
benchmark('toString', () => {
ast::toString()
})
}

View File

@@ -0,0 +1,18 @@
import {
createAnd,
createOr,
createNot,
createProperty,
createString
} from './'
export const pattern = 'foo !"\\\\ \\"" name:|(wonderwoman batman)'
export const ast = createAnd([
createString('foo'),
createNot(createString('\\ "')),
createProperty('name', createOr([
createString('wonderwoman'),
createString('batman')
]))
])

View File

@@ -0,0 +1,405 @@
import every from 'lodash/every'
import filter from 'lodash/filter'
import forEach from 'lodash/forEach'
import isArray from 'lodash/isArray'
import isPlainObject from 'lodash/isPlainObject'
import isString from 'lodash/isString'
import map from 'lodash/map'
import some from 'lodash/some'
import filterReduce from '../filter-reduce'
import invoke from '../invoke'
// ===================================================================
const RAW_STRING_CHARS = invoke(() => {
const chars = { __proto__: null }
const add = (a, b = a) => {
let i = a.charCodeAt(0)
const j = b.charCodeAt(0)
while (i <= j) {
chars[String.fromCharCode(i++)] = true
}
}
add('$')
add('-')
add('.')
add('0', '9')
add('_')
add('A', 'Z')
add('a', 'z')
return chars
})
const isRawString = string => {
const { length } = string
for (let i = 0; i < length; ++i) {
if (!RAW_STRING_CHARS[string[i]]) {
return false
}
}
return true
}
// -------------------------------------------------------------------
export const createAnd = children => children.length === 1
? children[0]
: { type: 'and', children }
export const createOr = children => children.length === 1
? children[0]
: { type: 'or', children }
export const createNot = child => ({ type: 'not', child })
export const createProperty = (name, child) => ({ type: 'property', name, child })
export const createString = value => ({ type: 'string', value })
// -------------------------------------------------------------------
// *and = terms
// terms = term+
// term = ws (groupedAnd | or | not | property | string) ws
// ws = ' '*
// groupedAnd = "(" and ")"
// *or = "|" ws "(" terms ")"
// *not = "!" term
// *property = string ws ":" term
// *string = quotedString | rawString
// quotedString = "\"" ( /[^"\]/ | "\\\\" | "\\\"" )+
// rawString = /[a-z0-9-_.]+/i
export const parse = invoke(() => {
let i
let n
let input
// -----
const backtrace = parser => () => {
const pos = i
const node = parser()
if (node != null) {
return node
}
i = pos
}
// -----
const parseAnd = () => parseTerms(createAnd)
const parseTerms = fn => {
let term = parseTerm()
if (!term) {
return
}
const terms = [ term ]
while ((term = parseTerm())) {
terms.push(term)
}
return fn(terms)
}
const parseTerm = () => {
parseWs()
const child = (
parseGroupedAnd() ||
parseOr() ||
parseNot() ||
parseProperty() ||
parseString()
)
if (child) {
parseWs()
return child
}
}
const parseWs = () => {
while (input[i] === ' ') {
++i
}
return true
}
const parseGroupedAnd = backtrace(() => {
let and
if (
input[i++] === '(' &&
(and = parseAnd()) &&
input[i++] === ')'
) {
return and
}
})
const parseOr = backtrace(() => {
let or
if (
input[i++] === '|' &&
parseWs() &&
input[i++] === '(' &&
(or = parseTerms(createOr)) &&
input[i++] === ')'
) {
return or
}
})
const parseNot = backtrace(() => {
let child
if (
input[i++] === '!' &&
(child = parseTerm())
) {
return createNot(child)
}
})
const parseProperty = backtrace(() => {
let name, child
if (
(name = parseString()) &&
parseWs() &&
(input[i++] === ':') &&
(child = parseTerm())
) {
return createProperty(name.value, child)
}
})
const parseString = () => {
let value
if (
(value = parseQuotedString()) != null ||
(value = parseRawString()) != null
) {
return createString(value)
}
}
const parseQuotedString = backtrace(() => {
if (input[i++] !== '"') {
return
}
const value = []
let char
while (i < n && (char = input[i++]) !== '"') {
if (char === '\\') {
char = input[i++]
}
value.push(char)
}
return value.join('')
})
const parseRawString = () => {
let value = ''
let c
while (
(c = input[i]) &&
RAW_STRING_CHARS[c]
) {
++i
value += c
}
if (value.length) {
return value
}
}
return input_ => {
if (!input_) {
return
}
i = 0
input = input_.split('')
n = input.length
try {
return parseAnd()
} finally {
input = null
}
}
})
// -------------------------------------------------------------------
const _getPropertyClauseStrings = ({ child }) => {
const { type } = child
if (type === 'or') {
const strings = []
forEach(child.children, child => {
if (child.type === 'string') {
strings.push(child.value)
}
})
return strings
}
if (type === 'string') {
return [ child.value ]
}
return []
}
// Find possible values for property clauses in a and clause.
export const getPropertyClausesStrings = function () {
if (!this) {
return {}
}
const { type } = this
if (type === 'property') {
return {
[this.name]: _getPropertyClauseStrings(this)
}
}
if (type === 'and') {
const strings = {}
forEach(this.children, node => {
if (node.type === 'property') {
const { name } = node
const values = strings[name]
if (values) {
values.push.apply(values, _getPropertyClauseStrings(node))
} else {
strings[name] = _getPropertyClauseStrings(node)
}
}
})
return strings
}
return {}
}
// -------------------------------------------------------------------
export const removePropertyClause = function (name) {
let type
if (
!this ||
(type = this.type) === 'property' && this.name === name
) {
return
}
if (type === 'and') {
return createAnd(filter(this.children, node =>
node.type !== 'property' || node.name !== name
))
}
return this
}
// -------------------------------------------------------------------
const _addAndClause = (node, child, predicate, reducer) =>
createAnd(filterReduce(
node.type === 'and'
? node.children
: [ node ],
predicate,
reducer,
child
))
export const setPropertyClause = function (name, child) {
const property = createProperty(
name,
isString(child) ? createString(child) : child
)
if (!this) {
return property
}
return _addAndClause(
this,
property,
node => node.type === 'property' && node.name === name,
)
}
// -------------------------------------------------------------------
export const execute = invoke(() => {
const visitors = {
and: ({ children }, value) => (
every(children, child => child::execute(value))
),
not: ({ child }, value) => (
!child::execute(value)
),
or: ({ children }, value) => (
some(children, child => child::execute(value))
),
property: ({ name, child }, value) => (
value != null && child::execute(value[name])
),
string: invoke(() => {
const match = (pattern, value) => {
if (isString(value)) {
return value.toLowerCase().indexOf(pattern) !== -1
}
if (isArray(value) || isPlainObject(value)) {
return some(value, value => match(pattern, value))
}
return false
}
return ({ value: pattern }, value) => (
match(pattern.toLowerCase(), value)
)
})
}
return function (value) {
return visitors[this.type](this, value)
}
})
// -------------------------------------------------------------------
export const toString = invoke(() => {
const toStringTerms = terms => map(terms, toString).join(' ')
const toStringGroup = terms => `(${toStringTerms(terms)})`
const visitors = {
and: ({ children }) => toStringGroup(children),
not: ({ child }) => `!${toString(child)}`,
or: ({ children }) => `|${toStringGroup(children)}`,
property: ({ name, child }) => `${toString(createString(name))}:${toString(child)}`,
string: ({ value }) => isRawString(value)
? value
: `"${value.replace(/\\|"/g, match => `\\${match}`)}"`
}
const toString = node => visitors[node.type](node)
// Special case for a root “and”: do not add braces.
return function () {
return !this
? ''
: this.type === 'and'
? toStringTerms(this.children)
: toString(this)
}
})
// -------------------------------------------------------------------
export const create = pattern => {
pattern = parse(pattern)
if (!pattern) {
return
}
return value => pattern::execute(value)
}

View File

@@ -0,0 +1,53 @@
import test from 'ava'
import {
getPropertyClausesStrings,
parse,
setPropertyClause,
toString
} from './'
import {
ast,
pattern
} from './index.fixtures'
test('getPropertyClausesStrings', t => {
let tmp = parse('foo bar:baz baz:|(foo bar)')::getPropertyClausesStrings()
t.deepEqual(
tmp,
{
bar: [ 'baz' ],
baz: [ 'foo', 'bar' ]
}
)
})
test('parse', t => {
t.deepEqual(parse(pattern), ast)
})
test('setPropertyClause', t => {
t.is(
null::setPropertyClause('foo', 'bar')::toString(),
'foo:bar'
)
t.is(
parse('baz')::setPropertyClause('foo', 'bar')::toString(),
'baz foo:bar'
)
t.is(
parse('plip foo:baz plop')::setPropertyClause('foo', 'bar')::toString(),
'plip plop foo:bar'
)
t.is(
parse('foo:|(baz plop)')::setPropertyClause('foo', 'bar')::toString(),
'foo:bar'
)
})
test('toString', t => {
t.is(pattern, ast::toString())
})

View File

@@ -0,0 +1,9 @@
.container .button {
position: absolute;
margin-left: 1ex;
visibility: hidden;
}
.container:hover .button {
visibility: visible;
}

View File

@@ -0,0 +1,31 @@
import _ from 'intl'
import CopyToClipboard from 'react-copy-to-clipboard'
import classNames from 'classnames'
import Tooltip from 'tooltip'
import React, { createElement } from 'react'
import Icon from '../icon'
import propTypes from '../prop-types'
import styles from './index.css'
const Copiable = propTypes({
data: propTypes.string,
tagName: propTypes.string
})(({ className, tagName = 'span', ...props }) => createElement(
tagName,
{
...props,
className: classNames(styles.container, className)
},
props.children,
' ',
<Tooltip content={_('copyToClipboard')}>
<CopyToClipboard text={props.data || props.children}>
<button className={classNames('btn btn-sm btn-secondary', styles.button)}>
<Icon icon='clipboard' />
</button>
</CopyToClipboard>
</Tooltip>
))
export { Copiable as default }

9
src/common/d3-utils.js vendored Normal file
View File

@@ -0,0 +1,9 @@
import forEach from 'lodash/forEach'
export function setStyles (style) {
forEach(style, (value, key) => {
this.style(key, value)
})
return this
}

53
src/common/debug.js Normal file
View File

@@ -0,0 +1,53 @@
import React, { Component, PropTypes } from 'react'
import { isPromise } from 'promise-toolbox'
const toString = value => JSON.stringify(value, null, 2)
// This component does not handle changes in its `promise` property.
class DebugAsync extends Component {
static propTypes = {
promise: PropTypes.object.isRequired
}
constructor (props) {
super()
this.state = {
status: 'pending'
}
props.promise.then(
value => this.setState({ status: 'resolved', value }),
value => this.setState({ status: 'rejected', value })
)
}
shouldComponentUpdate (_, newState) {
return this.state.status !== newState.status
}
render () {
const { status, value } = this.state
if (status === 'pending') {
return <pre>{'Promise { <pending> }'}</pre>
}
return <pre>
{'Promise { '}
{status === 'rejected' && '<rejected> '}
{toString(value)}
{' }'}
</pre>
}
}
const Debug = ({ value }) => isPromise(value)
? <DebugAsync promise={value} />
: <pre>{toString(value)}</pre>
Debug.propTypes = {
value: PropTypes.any.isRequired
}
export { Debug as default }

View File

@@ -0,0 +1,13 @@
.clickToEdit * {
cursor: context-menu !important;
}
.shortClick {
border-bottom: 1px dashed #ccc;
}
.select {
padding: 0px;
}
.size {
width: 10rem;
}

View File

@@ -0,0 +1,476 @@
import classNames from 'classnames'
import findKey from 'lodash/findKey'
import isFunction from 'lodash/isFunction'
import isString from 'lodash/isString'
import map from 'lodash/map'
import pick from 'lodash/pick'
import React from 'react'
import _ from '../intl'
import Component from '../base-component'
import Icon from '../icon'
import logError from '../log-error'
import propTypes from '../prop-types'
import Tooltip from '../tooltip'
import { formatSize } from '../utils'
import { SizeInput } from '../form'
import {
SelectHost,
SelectIp,
SelectNetwork,
SelectPool,
SelectRemote,
SelectSr,
SelectSubject,
SelectTag,
SelectVm,
SelectVmTemplate
} from '../select-objects'
import styles from './index.css'
const LONG_CLICK = 400
@propTypes({
alt: propTypes.node.isRequired
})
class Hover extends Component {
constructor () {
super()
this.state = {
hover: false
}
this._onMouseEnter = () => this.setState({ hover: true })
this._onMouseLeave = () => this.setState({ hover: false })
}
render () {
if (this.state.hover) {
return <span onMouseLeave={this._onMouseLeave}>
{this.props.alt}
</span>
}
return <span onMouseEnter={this._onMouseEnter}>
{this.props.children}
</span>
}
}
@propTypes({
onChange: propTypes.func.isRequired,
onUndo: propTypes.oneOfType([
propTypes.bool,
propTypes.func
]),
useLongClick: propTypes.bool,
value: propTypes.any.isRequired
})
class Editable extends Component {
get value () {
throw new Error('not implemented')
}
_onKeyDown = event => {
const { keyCode } = event
if (keyCode === 27) {
return this._closeEdition()
}
if (keyCode === 13) {
return this._save()
}
}
_closeEdition = () => {
this.setState({ editing: false })
}
_openEdition = () => {
this.setState({
editing: true,
error: null,
saving: false
})
}
_undo = () => {
const { props } = this
const { onUndo } = props
if (onUndo === false) {
return
}
return this.__save(
() => this.state.previous,
isFunction(onUndo) ? onUndo : props.onChange
)
}
_save () {
return this.__save(
() => this.value,
this.props.onChange
)
}
async __save (getValue, saveValue) {
const { props } = this
try {
const value = getValue()
const previous = props.value
if (value === previous) {
return this._closeEdition()
}
this.setState({ saving: true })
await saveValue(value)
this.setState({ previous })
this._closeEdition()
} catch (error) {
this.setState({
error: isString(error) ? error : error.message,
saving: false
})
logError(error)
}
}
__startTimer = event => {
event.persist()
this._timeout = setTimeout(() => {
event.preventDefault()
this._openEdition()
}, LONG_CLICK)
}
__stopTimer = () => clearTimeout(this._timeout)
render () {
const { state, props } = this
if (!state.editing) {
const { onUndo, previous } = state
const { useLongClick } = props
const success = <Icon icon='success' />
return <span className={classNames(styles.clickToEdit, !useLongClick && styles.shortClick)}>
<span
onClick={!useLongClick && this._openEdition}
onMouseDown={useLongClick && this.__startTimer}
onMouseUp={useLongClick && this.__stopTimer}
>
{this._renderDisplay()}
</span>
{previous != null && (onUndo !== false
? <Hover
alt={<a onClick={this._undo}><Icon icon='undo' /></a>}
>
{success}
</Hover>
: success
)}
</span>
}
const { error, saving } = state
return <span>
{this._renderEdition()}
{saving && <span>{' '}<Icon icon='loading' /></span>}
{error != null && <span>
{' '}<Tooltip content={error}><Icon icon='error' /></Tooltip>
</span>}
</span>
}
}
@propTypes({
autoComplete: propTypes.string,
maxLength: propTypes.number,
minLength: propTypes.number,
pattern: propTypes.string,
value: propTypes.string.isRequired
})
export class Text extends Editable {
get value () {
const { input } = this.refs
// FIXME: should be properly forwarded to the user.
const error = input.validationMessage
if (error) {
throw new Error(error)
}
return input.value
}
_onInput = ({ target }) => {
target.style.width = `${target.value.length + 1}ex`
}
_renderDisplay () {
const {
children,
value
} = this.props
if (children || value) {
return <span> {children || value} </span>
}
const {
placeholder,
useLongClick
} = this.props
return <span className='text-muted'>
{placeholder ||
(useLongClick ? _('editableLongClickPlaceholder') : _('editableClickPlaceholder'))
}
</span>
}
_renderEdition () {
const { value } = this.props
const { saving } = this.state
// Optional props that the user may set on the input.
const extraProps = pick(this.props, [
'autoComplete',
'maxLength',
'minLength',
'pattern'
])
return <input
{...extraProps}
autoFocus
defaultValue={value}
onBlur={this._closeEdition}
onInput={this._onInput}
onKeyDown={this._onKeyDown}
readOnly={saving}
ref='input'
style={{
width: `${value.length + 1}ex`,
maxWidth: '50ex'
}}
type={this._isPassword ? 'password' : 'text'}
/>
}
}
export class Password extends Text {
// TODO: this is a hack, this class should probably have a better
// implementation.
_isPassword = true
}
@propTypes({
nullable: propTypes.bool,
value: propTypes.number
})
export class Number extends Component {
get value () {
return +this.refs.input.value
}
_onChange = value => {
if (value === '') {
if (this.props.nullable) {
value = null
} else {
return
}
} else {
value = +value
}
this.props.onChange(value)
}
render () {
const { value } = this.props
return <Text
{...this.props}
onChange={this._onChange}
value={value === null ? '' : String(value)}
/>
}
}
@propTypes({
labelProp: propTypes.string.isRequired,
options: propTypes.oneOfType([
propTypes.array,
propTypes.object
]).isRequired
})
export class Select extends Editable {
constructor (props) {
super()
this._defaultValue = findKey(props.options, option => option === props.value)
}
get value () {
return this.props.options[this._select.value]
}
_onChange = event => {
this._save()
}
_optionToJsx = (option, index) => {
const { labelProp } = this.props
return <option
key={index}
value={index}
>
{labelProp ? option[labelProp] : option}
</option>
}
_onEditionMount = ref => {
this._select = ref
// Seems to work in Google Chrome (not in Firefox)
ref && ref.dispatchEvent(new window.MouseEvent('mousedown'))
}
_renderDisplay () {
return this.props.children ||
<span>{this.props.value[this.props.labelProp]}</span>
}
_renderEdition () {
const { saving } = this.state
const { options } = this.props
return <select
autoFocus
className={classNames('form-control', styles.select)}
defaultValue={this._defaultValue}
onBlur={this._closeEdition}
onChange={this._onChange}
onKeyDown={this._onKeyDown}
readOnly={saving}
ref={this._onEditionMount}
>
{map(options, this._optionToJsx)}
</select>
}
}
const MAP_TYPE_SELECT = {
host: SelectHost,
ip: SelectIp,
network: SelectNetwork,
pool: SelectPool,
remote: SelectRemote,
SR: SelectSr,
subject: SelectSubject,
tag: SelectTag,
VM: SelectVm,
'VM-template': SelectVmTemplate
}
@propTypes({
labelProp: propTypes.string.isRequired,
predicate: propTypes.func,
value: propTypes.oneOfType([
propTypes.string,
propTypes.object
]).isRequired
})
export class XoSelect extends Editable {
get value () {
return this.refs.select.value
}
_renderDisplay () {
return this.props.children ||
<span>{this.props.value[this.props.labelProp]}</span>
}
_onChange = object => {
object ? this._save() : this._closeEdition()
}
_renderEdition () {
const {
placeholder,
predicate,
saving,
xoType
} = this.props
const Select = MAP_TYPE_SELECT[xoType]
if (process.env.NODE_ENV !== 'production') {
if (!Select) {
throw new Error(`${xoType} is not a valid XoSelect type.`)
}
}
// Anchor is needed so that the BlockLink does not trigger a redirection
// when this element is clicked.
return <a onBlur={this._closeEdition}>
<Select
autoFocus
disabled={saving}
onChange={this._onChange}
placeholder={placeholder}
predicate={predicate}
ref='select'
/>
</a>
}
}
@propTypes({
value: propTypes.number.isRequired
})
export class Size extends Editable {
get value () {
return this.refs.input.value
}
_renderDisplay () {
return this.props.children || formatSize(this.props.value)
}
_closeEditionIfUnfocused = () => {
this._focused = false
setTimeout(() => {
!this._focused && this._closeEdition()
}, 10)
}
_focus = () => { this._focused = true }
_renderEdition () {
const { saving } = this.state
const { value } = this.props
return <span
// SizeInput uses `input-group` which makes it behave as a block element (display: table).
// `form-inline` to use it as an inline element
className='form-inline'
onBlur={this._closeEditionIfUnfocused}
onFocus={this._focus}
onKeyDown={this._onKeyDown}
>
<SizeInput
autoFocus
className={styles.size}
ref='input'
readOnly={saving}
defaultValue={value}
/>
</span>
}
}

26
src/common/ellipsis.js Normal file
View File

@@ -0,0 +1,26 @@
import React from 'react'
const ellipsisStyle = {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
const ellipsisContainerStyle = {
display: 'flex'
}
const Ellipsis = ({ children }) => (
<span style={ellipsisStyle}>
{children}
</span>
)
export { Ellipsis as default }
export const EllipsisContainer = ({ children }) => (
<div style={ellipsisContainerStyle}>
{React.Children.map(children, child =>
child == null || child.type === Ellipsis ? child : <span>{child}</span>
)}
</div>
)

View File

@@ -0,0 +1,45 @@
import findIndex from 'lodash/findIndex'
import identity from 'lodash/identity'
// Returns a copy of the array containing:
// - the elements which did not matches the predicate
// - the result of the reduction of the elements matching the
// predicates
//
// As a special case, if the predicate is not provided, it is
// considered to have not matched.
const filterReduce = (array, predicate, reducer, initial) => {
const { length } = array
let i
if (
!length ||
!predicate ||
(i = findIndex(array, predicate)) === -1
) {
return initial == null
? array.slice(0)
: array.concat(initial)
}
if (reducer == null) {
reducer = identity
}
const result = array.slice(0, i)
let value = initial == null
? array[i]
: reducer(initial, array[i], i, array)
for (i = i + 1; i < length; ++i) {
const current = array[i]
if (predicate(current, i, array)) {
value = reducer(value, current, i, array)
} else {
result.push(current)
}
}
result.push(value)
return result
}
export { filterReduce as default }

View File

@@ -0,0 +1,28 @@
import test from 'ava'
import filterReduce from './filter-reduce'
const add = (a, b) => a + b
const data = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
const isEven = x => !(x & 1)
test('filterReduce', t => {
// Returns all elements not matching the predicate and the result of
// a reduction over those who do.
t.deepEqual(
filterReduce(data, isEven, add),
[ 1, 3, 5, 7, 9, 20 ]
)
// The default reducer is the identity.
t.deepEqual(
filterReduce(data, isEven),
[ 1, 3, 5, 7, 9, 0 ]
)
// If an initial value is passed it is used.
t.deepEqual(
filterReduce(data, isEven, add, 22),
[ 1, 3, 5, 7, 9, 42 ]
)
})

24
src/common/form-grid.js Normal file
View File

@@ -0,0 +1,24 @@
import React from 'react'
import * as Grid from './grid'
import propTypes from './prop-types'
export const LabelCol = propTypes({
children: propTypes.any.isRequired
})(({ children }) => (
<label className='col-md-2 form-control-label'>{children}</label>
))
export const InputCol = propTypes({
children: propTypes.any.isRequired
})(({ children }) => (
<Grid.Col mediumSize={10}>{children}</Grid.Col>
))
export const Row = propTypes({
children: propTypes.arrayOf(propTypes.element).isRequired
})(({ children }) => (
<Grid.Row className='form-group'>
{children}
</Grid.Row>
))

325
src/common/form/index.js Normal file
View File

@@ -0,0 +1,325 @@
import BaseComponent from 'base-component'
import classNames from 'classnames'
import Icon from 'icon'
import map from 'lodash/map'
import randomPassword from 'random-password'
import React from 'react'
import round from 'lodash/round'
import {
DropdownButton,
MenuItem
} from 'react-bootstrap-4/lib'
import Component from '../base-component'
import propTypes from '../prop-types'
import {
firstDefined,
formatSizeRaw,
parseSize
} from '../utils'
export Select from './select'
export SelectPlainObject from './select-plain-object'
// ===================================================================
@propTypes({
enableGenerator: propTypes.bool
})
export class Password extends Component {
get value () {
return this.refs.field.value
}
set value (value) {
this.refs.field.value = value
}
_generate = () => {
this.refs.field.value = randomPassword(8)
this.setState({
visible: true
})
}
_toggleVisibility = () => {
this.setState({
visible: !this.state.visible
})
}
render () {
const {
className,
enableGenerator = false,
...props
} = this.props
const { visible } = this.state
return <div className='input-group'>
{enableGenerator && <span className='input-group-btn'>
<button type='button' className='btn btn-secondary' onClick={this._generate}>
<Icon icon='password' />
</button>
</span>}
<input
{...props}
className={classNames(className, 'form-control')}
ref='field'
type={visible ? 'text' : 'password'}
/>
<span className='input-group-btn'>
<button type='button' className='btn btn-secondary' onClick={this._toggleVisibility}>
<Icon icon={visible ? 'shown' : 'hidden'} />
</button>
</span>
</div>
}
}
// ===================================================================
@propTypes({
defaultValue: propTypes.number,
max: propTypes.number.isRequired,
min: propTypes.number.isRequired,
step: propTypes.number,
onChange: propTypes.func
})
export class Range extends Component {
constructor (props) {
super()
this.state = {
value: props.defaultValue || props.min
}
}
get value () {
return this.state.value
}
set value (value) {
this.setState({
value: +value
})
}
_handleChange = event => {
const { onChange } = this.props
const { value } = event.target
if (value === this.state.value) {
return
}
this.setState({
value
}, onChange && (() => onChange(value)))
}
render () {
const {
props
} = this
const step = props.step || 1
const { value } = this.state
return (
<div className='form-group row'>
<label className='col-sm-2 control-label'>
{value}
</label>
<div className='col-sm-10'>
<input
className='form-control'
type='range'
min={props.min}
max={props.max}
step={step}
value={value}
onChange={this._handleChange}
/>
</div>
</div>
)
}
}
export Toggle from './toggle'
const UNITS = ['kiB', 'MiB', 'GiB']
const DEFAULT_UNIT = 'GiB'
@propTypes({
autoFocus: propTypes.bool,
className: propTypes.string,
defaultUnit: propTypes.oneOf(UNITS),
defaultValue: propTypes.number,
onChange: propTypes.func,
placeholder: propTypes.string,
readOnly: propTypes.bool,
required: propTypes.bool,
style: propTypes.object,
value: propTypes.oneOfType([
propTypes.number,
propTypes.oneOf([ null ])
])
})
export class SizeInput extends BaseComponent {
constructor (props) {
super(props)
this.state = this._createStateFromBytes(firstDefined(props.value, props.defaultValue, null))
}
componentWillReceiveProps (props) {
const { value } = props
if (value !== undefined && value !== this.props.value) {
this.setState(this._createStateFromBytes(value))
}
}
_createStateFromBytes (bytes) {
if (bytes === this._bytes) {
return {
input: this._input,
unit: this._unit
}
}
if (bytes === null) {
return {
input: '',
unit: this.props.defaultUnit || DEFAULT_UNIT
}
}
const { prefix, value } = formatSizeRaw(bytes)
return {
input: String(round(value, 2)),
unit: `${prefix}B`
}
}
get value () {
const { input, unit } = this.state
if (!input) {
return null
}
return parseSize(`${+input} ${unit}`)
}
set value (value) {
if (
process.env.NODE_ENV !== 'production' &&
this.props.value !== undefined
) {
throw new Error('cannot set value of controlled SizeInput')
}
this.setState(this._createStateFromBytes(value))
}
_onChange (input, unit) {
const { onChange } = this.props
// Empty input equals null.
const bytes = input
? parseSize(`${+input} ${unit}`)
: null
const isControlled = this.props.value !== undefined
if (isControlled) {
// Store input and unit for this change to update correctly on new
// props.
this._bytes = bytes
this._input = input
this._unit = unit
} else {
this.setState({ input, unit })
// onChange is optional in uncontrolled mode.
if (!onChange) {
return
}
}
onChange(bytes)
}
_updateNumber = event => {
const input = event.target.value
if (!input) {
return this._onChange(input, this.state.unit)
}
const number = +input
// NaN: do not ack this change.
if (number !== number) { // eslint-disable-line no-self-compare
return
}
// Same numeric value: simply update the input.
const prevInput = this.state.input
if (prevInput && +prevInput === number) {
return this.setState({ input })
}
this._onChange(input, this.state.unit)
}
_updateUnit = unit => {
const { input } = this.state
// 0 is always 0, no matter the unit.
if (+input) {
this._onChange(input, unit)
} else {
this.setState({ unit })
}
}
render () {
const {
autoFocus,
className,
readOnly,
placeholder,
required,
style
} = this.props
return <span className={classNames('input-group', className)} style={style}>
<input
autoFocus={autoFocus}
className='form-control'
disabled={readOnly}
onChange={this._updateNumber}
placeholder={placeholder}
required={required}
type='text'
value={this.state.input}
/>
<span className='input-group-btn'>
<DropdownButton
bsStyle='secondary'
id='size'
pullRight
disabled={readOnly}
title={this.state.unit}
>
{map(UNITS, unit =>
<MenuItem
key={unit}
onClick={() => this._updateUnit(unit)}
>
{unit}
</MenuItem>
)}
</DropdownButton>
</span>
</span>
}
}

View File

@@ -0,0 +1,117 @@
import find from 'lodash/find'
import map from 'lodash/map'
import React, { Component } from 'react'
import propTypes from '../prop-types'
import Select from './select'
@propTypes({
autoFocus: propTypes.bool,
defaultValue: propTypes.any,
disabled: propTypes.bool,
optionRenderer: propTypes.func,
multi: propTypes.bool,
onChange: propTypes.func,
options: propTypes.array,
placeholder: propTypes.string,
predicate: propTypes.func,
required: propTypes.bool
})
export default class SelectPlainObject extends Component {
constructor (props) {
super(props)
this.state = {
value: this._computeValue(props.defaultValue, props)
}
}
_computeValue (value, props = this.props) {
let { optionKey } = props
optionKey || (optionKey = 'id')
const reduceValue = value => value != null ? (value[optionKey] || value) : ''
if (props.multi) {
if (!Array.isArray(value)) {
value = [value]
}
return map(value, reduceValue)
}
return reduceValue(value)
}
componentWillMount () {
const { options } = this.props
this.setState({
options: this._computeOptions(options)
})
}
componentWillReceiveProps (newProps) {
const { options } = newProps
this.setState({
options: this._computeOptions(options)
})
}
_computeOptions (options) {
const { optionKey = 'id' } = this.props
const { optionRenderer = o => o.label || o[optionKey] || o } = this.props
return map(options, option => ({
value: option[optionKey] || option,
label: optionRenderer(option)
}))
}
get value () {
const { optionKey = 'id' } = this.props
const { value } = this.state
const { options } = this.props
const pickValue = value => {
value = value.value || value
return find(options, option => option[optionKey] === value || option === value)
}
if (this.props.multi) {
return map(value, pickValue)
}
return pickValue(value)
}
set value (value) {
this.setState({
value: this._computeValue(value)
})
}
_handleChange = value => {
const { onChange } = this.props
this.setState({
value: this._computeValue(value)
}, onChange && (() => { onChange(this.value) }))
}
_renderOption = option => option.label
render () {
const { props, state } = this
return (
<Select
autofocus={props.autoFocus}
disabled={props.disabled}
multi={props.multi}
onChange={this._handleChange}
openOnFocus
optionRenderer={this._renderOption}
options={state.options}
placeholder={props.placeholder}
required={props.required}
value={state.value}
valueRenderer={this._renderOption} />
)
}
}

117
src/common/form/select.js Normal file
View File

@@ -0,0 +1,117 @@
import React, { Component } from 'react'
import ReactSelect from 'react-select'
import {
AutoSizer,
List
} from 'react-virtualized'
import propTypes from '../prop-types'
const SELECT_MENU_STYLE = {
overflow: 'hidden'
}
const SELECT_STYLE = {
minWidth: '10em'
}
// See: https://github.com/bvaughn/react-virtualized-select/blob/master/source/VirtualizedSelect/VirtualizedSelect.js
@propTypes({
maxHeight: propTypes.number,
optionHeight: propTypes.number
})
export default class Select extends Component {
static defaultProps = {
maxHeight: 200,
optionHeight: 40,
optionRenderer: (option, labelKey) => option[labelKey]
}
_renderMenu = ({
focusedOption,
options,
...otherOptions
}) => {
const {
maxHeight,
optionHeight
} = this.props
const focusedOptionIndex = options.indexOf(focusedOption)
const height = Math.min(maxHeight, options.length * optionHeight)
const wrappedRowRenderer = ({ index, key, style }) =>
this._optionRenderer({
...otherOptions,
focusedOption,
focusedOptionIndex,
key,
option: options[index],
options,
style
})
return (
<AutoSizer disableHeight>
{({ width }) => (
<List
height={height}
rowCount={options.length}
rowHeight={optionHeight}
rowRenderer={wrappedRowRenderer}
scrollToIndex={focusedOptionIndex}
width={width}
/>
)}
</AutoSizer>
)
}
_optionRenderer = ({
focusedOption,
focusOption,
key,
labelKey,
option,
style,
selectValue
}) => {
let className = 'Select-option'
if (option === focusedOption) {
className += ' is-focused'
}
const { disabled } = option
if (disabled) {
className += ' is-disabled'
}
const { props } = this
return (
<div
className={className}
onClick={!disabled && (() => selectValue(option))}
onMouseOver={!disabled && (() => focusOption(option))}
style={{ ...style, height: props.optionHeight }}
key={key}
>
{props.optionRenderer(option, labelKey)}
</div>
)
}
render () {
return (
<ReactSelect
{...this.props}
backspaceToRemoveMessage=''
menuRenderer={this._renderMenu}
menuStyle={SELECT_MENU_STYLE}
style={SELECT_STYLE}
/>
)
}
}

View File

@@ -0,0 +1,3 @@
.checkbox {
display: none;
}

View File

@@ -0,0 +1,90 @@
import React from 'react'
import classNames from 'classnames'
import Component from '../../base-component'
import Icon from '../../icon'
import propTypes from '../../prop-types'
import styles from './index.css'
@propTypes({
className: propTypes.string,
defaultValue: propTypes.bool,
onChange: propTypes.func,
icon: propTypes.string,
iconOn: propTypes.string,
iconOff: propTypes.string,
iconSize: propTypes.number,
value: propTypes.bool
})
export default class Toggle extends Component {
static defaultProps = {
iconOn: 'toggle-on',
iconOff: 'toggle-off',
iconSize: 2
}
get value () {
const { props } = this
const { value } = props
if (value != null) {
return value
}
const { input } = this.refs
if (input) {
return input.checked
}
return props.defaultValue || false
}
set value (value) {
if (
process.env.NODE_ENV !== 'production' &&
this.props.value != null
) {
throw new Error('cannot set value of controlled Toggle')
}
this.refs.input.checked = Boolean(value)
this.forceUpdate()
}
_onChange = event => {
if (this.props.value == null) {
this.forceUpdate()
}
const { onChange } = this.props
onChange && onChange(event.target.checked)
}
render () {
const { props, value } = this
return (
<label
className={classNames(
props.disabled ? 'text-muted' : value ? 'text-success' : null,
props.className
)}
>
<Icon
icon={props.icon || (value ? props.iconOn : props.iconOff)}
size={props.iconSize}
/>
<input
checked={props.value}
className={styles.checkbox}
defaultChecked={props.defaultValue}
disabled={props.disabled}
onChange={this._onChange}
ref='input'
type='checkbox'
/>
</label>
)
}
}

View File

@@ -0,0 +1,17 @@
// If the param is an event, returns the value of it's target,
// otherwise returns the param.
const getEventValue = event => {
let target
if (!event || !(target = event.target)) {
return event
}
return (
target.nodeName.toLowerCase() === 'input' &&
target.type.toLowerCase() === 'checkbox'
)
? target.checked
: target.value
}
export { getEventValue as default }

58
src/common/grid.js Normal file
View File

@@ -0,0 +1,58 @@
import classNames from 'classnames'
import React from 'react'
import propTypes from './prop-types'
export const Col = propTypes({
className: propTypes.string,
size: propTypes.number,
smallSize: propTypes.number,
mediumSize: propTypes.number,
largeSize: propTypes.number,
offset: propTypes.number,
smallOffset: propTypes.number,
mediumOffset: propTypes.number,
largeOffset: propTypes.number
})(({
children,
className,
size = 12,
smallSize = size,
mediumSize,
largeSize,
offset,
smallOffset = offset,
mediumOffset,
largeOffset,
style
}) => <div className={classNames(
className,
smallSize && `col-xs-${smallSize}`,
mediumSize && `col-md-${mediumSize}`,
largeSize && `col-lg-${largeSize}`,
smallOffset && `offset-xs-${smallOffset}`,
mediumOffset && `offset-md-${mediumOffset}`,
largeOffset && `offset-lg-${largeOffset}`
)}
style={style}
>
{children}
</div>)
export const Container = propTypes({
className: propTypes.string
})(({
children,
className
}) => <div className={classNames(className, 'container-fluid')}>
{children}
</div>)
export const Row = propTypes({
className: propTypes.string
})(({
children,
className
}) => <div className={`${className || ''} row`}>
{children}
</div>)

View File

@@ -0,0 +1,20 @@
export const VM = {
homeFilterPendingVms: 'current_operations:"" ',
homeFilterNonRunningVms: '!power_state:running ',
homeFilterHvmGuests: 'virtualizationMode:hvm ',
homeFilterRunningVms: 'power_state:running ',
homeFilterTags: 'tags:'
}
export const host = {
homeFilterRunningHosts: 'power_state:running ',
homeFilterTags: 'tags:'
}
export const pool = {
homeFilterTags: 'tags:'
}
export const vmTemplate = {
homeFilterTags: 'tags:'
}

View File

@@ -0,0 +1,226 @@
import isEmpty from 'lodash/isEmpty'
import keys from 'lodash/keys'
import map from 'lodash/map'
import React from 'react'
import { Portal } from 'react-overlays'
import _ from './intl'
import ActionButton from './action-button'
import Component from './base-component'
import forEach from 'lodash/forEach'
import Link from './link'
import propTypes from './prop-types'
import SortedTable from './sorted-table'
import TabButton from './tab-button'
import { connectStore } from './utils'
import {
createGetObjectsOfType,
createFilter,
createSelector
} from './selectors'
import {
getHostMissingPatches,
installAllHostPatches,
installAllPatchesOnPool
} from './xo'
// ===================================================================
const MISSING_PATCHES_COLUMNS = [
{
name: _('srHost'),
itemRenderer: host => <Link to={`/hosts/${host.id}`}>{host.name_label}</Link>,
sortCriteria: host => host.name_label
},
{
name: _('hostDescription'),
itemRenderer: host => host.name_description,
sortCriteria: host => host.name_description
},
{
name: _('hostMissingPatches'),
itemRenderer: (host, { missingPatches }) => <Link to={`/hosts/${host.id}/patches`}>{missingPatches[host.id]}</Link>,
sortCriteria: (host, { missingPatches }) => missingPatches[host.id]
},
{
name: _('patchUpdateButton'),
itemRenderer: (host, { installAllHostPatches }) => (
<ActionButton
btnStyle='primary'
handler={installAllHostPatches}
handlerParam={host}
icon='host-patch-update'
/>
)
}
]
const POOLS_MISSING_PATCHES_COLUMNS = [{
name: _('srPool'),
itemRenderer: (host, { pools }) => {
const pool = pools[host.$pool]
return <Link to={`/pools/${pool.id}`}>{pool.name_label}</Link>
},
sortCriteria: (host, { pools }) => pools[host.$pool].name_label
}].concat(MISSING_PATCHES_COLUMNS)
// ===================================================================
class HostsPatchesTable extends Component {
constructor (props) {
super(props)
this.state.missingPatches = {}
}
_getHosts = createFilter(
() => this.props.hosts,
createSelector(
() => this.state.missingPatches,
missingPatches => host => missingPatches[host.id]
)
)
_refreshMissingPatches = () => (
Promise.all(
map(this.props.hosts, this._refreshHostMissingPatches)
)
)
_installAllMissingPatches = () => {
const pools = {}
forEach(this._getHosts(), host => {
pools[host.$pool] = true
})
return Promise.all(map(
keys(pools),
installAllPatchesOnPool
)).then(this._refreshMissingPatches)
}
_refreshHostMissingPatches = host => (
getHostMissingPatches(host).then(patches => {
this.setState({
missingPatches: {
...this.state.missingPatches,
[host.id]: patches.length
}
})
})
)
_installAllHostPatches = host => (
installAllHostPatches(host).then(() =>
this._refreshHostMissingPatches(host)
)
)
componentWillMount () {
this._refreshMissingPatches()
}
componentDidMount () {
// Force one Portal refresh.
// Because Portal cannot see the container reference at first rendering.
this.forceUpdate()
}
componentWillReceiveProps (nextProps) {
forEach(nextProps.hosts, host => {
const { id } = host
if (this.state.missingPatches[id] !== undefined) {
return
}
this.setState({
missingPatches: {
...this.state.missingPatches,
[id]: 0
}
})
this._refreshHostMissingPatches(host)
})
}
render () {
const hosts = this._getHosts()
const noPatches = isEmpty(hosts)
const { props } = this
const Container = props.container || 'div'
const Button = props.useTabButton ? TabButton : ActionButton
const Buttons = (
<Container>
<Button
btnStyle='secondary'
handler={this._refreshMissingPatches}
icon='refresh'
labelId='refreshPatches'
/>
<Button
btnStyle='primary'
disabled={noPatches}
handler={this._installAllMissingPatches}
icon='host-patch-update'
labelId='installPoolPatches'
/>
</Container>
)
return (
<div>
{!noPatches
? (
<SortedTable
collection={hosts}
columns={props.displayPools ? POOLS_MISSING_PATCHES_COLUMNS : MISSING_PATCHES_COLUMNS}
userData={{
installAllHostPatches: this._installAllHostPatches,
missingPatches: this.state.missingPatches,
pools: props.pools
}}
/>
) : <p>{_('patchNothing')}</p>
}
<Portal container={() => props.buttonsGroupContainer()}>
{Buttons}
</Portal>
</div>
)
}
}
// ===================================================================
@connectStore(() => {
const getPools = createGetObjectsOfType('pool')
return {
pools: getPools
}
})
class HostsPatchesTableByPool extends Component {
render () {
const { props } = this
return <HostsPatchesTable {...props} pools={props.pools} />
}
}
// ===================================================================
export default propTypes({
buttonsGroupContainer: propTypes.func.isRequired,
container: propTypes.any,
displayPools: propTypes.bool,
hosts: propTypes.oneOfType([
propTypes.arrayOf(propTypes.object),
propTypes.objectOf(propTypes.object)
]).isRequired,
useTabButton: propTypes.bool
})(props => props.displayPools
? <HostsPatchesTableByPool {...props} />
: <HostsPatchesTable {...props} />
)

21
src/common/icon.js Normal file
View File

@@ -0,0 +1,21 @@
import classNames from 'classnames'
import isInteger from 'lodash/isInteger'
import React, { PropTypes } from 'react'
const Icon = ({ className, icon, size = 1, fixedWidth }) => (
<i className={classNames(
className,
icon ? `xo-icon-${icon}` : 'fa', // Without icon prop, is a placeholder.
isInteger(size) ? `fa-${size}x` : `fa-${size}`,
fixedWidth && 'fa-fw'
)} />
)
Icon.propTypes = {
fixedWidth: PropTypes.bool,
icon: PropTypes.string,
size: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
])
}
export default Icon

83
src/common/intl/index.js Normal file
View File

@@ -0,0 +1,83 @@
import isFunction from 'lodash/isFunction'
import isString from 'lodash/isString'
import moment from 'moment'
import React, {
Component,
PropTypes
} from 'react'
import {
connect
} from 'react-redux'
import {
FormattedMessage,
IntlProvider as IntlProvider_
} from 'react-intl'
import messages from './messages'
import locales from './locales'
// ===================================================================
// Params:
//
// - props (optional): properties to add to the FormattedMessage
// - messageId: identifier of the message to format/translate
// - values (optional): values to pass to the message
// - render (optional): a function receiving the React nodes of the
// translated message and returning the React node to render
const getMessage = (props, messageId, values, render) => {
if (isString(props)) {
render = values
values = messageId
messageId = props
props = undefined
}
const message = messages[messageId]
if (process.env.NODE_ENV !== 'production' && !message) {
throw new Error(`no message defined for ${messageId}`)
}
if (isFunction(values)) {
render = values
values = undefined
}
return <FormattedMessage {...props} {...message} values={values}>
{render}
</FormattedMessage>
}
export { getMessage as default }
export { messages }
@connect(({ lang }) => ({ lang }))
export class IntlProvider extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
lang: PropTypes.string.isRequired
};
render () {
const { lang, children } = this.props
return <IntlProvider_
locale={lang}
messages={locales[lang]}
>
{children}
</IntlProvider_>
}
}
@connect(({ lang }) => ({ lang }))
export class FormattedDuration extends Component {
render () {
const {
duration,
lang
} = this.props
return <span>{moment.duration(duration).locale(lang).humanize()}</span>
}
}

1
src/common/intl/locales/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/index.js

View File

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1152
src/common/intl/messages.js Normal file

File diff suppressed because it is too large Load Diff

33
src/common/invoke.js Normal file
View File

@@ -0,0 +1,33 @@
// Invoke a function and returns it result.
// All parameters are forwarded.
//
// Why using `invoke()`?
// - avoid tedious IIFE syntax
// - avoid declaring variables in the common scope
// - monkey-patching
//
// ```js
// const sum = invoke(1, 2, (a, b) => a + b)
//
// eventEmitter.emit = invoke(eventEmitter.emit, emit => function (event) {
// if (event === 'foo') {
// throw new Error('event foo is disabled')
// }
//
// return emit.apply(this, arguments)
// })
// ```
export default function invoke (fn) {
const n = arguments.length - 1
if (!n) {
return fn()
}
fn = arguments[n]
const args = new Array(n)
for (let i = 0; i < n; ++i) {
args[i] = arguments[i]
}
return fn.apply(undefined, args)
}

126
src/common/ip.js Normal file
View File

@@ -0,0 +1,126 @@
import forEachRight from 'lodash/forEachRight'
import forEach from 'lodash/forEach'
import isArray from 'lodash/isArray'
import isIp from 'is-ip'
import some from 'lodash/some'
export { isIp }
export const isIpV4 = isIp.v4
export const isIpV6 = isIp.v6
// Source: https://github.com/ezpaarse-project/ip-range-generator/blob/master/index.js
const ipv4 = /^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?:\.(?!$)|$)){4}$/
function ip2hex (ip) {
let parts = ip.split('.').map(str => parseInt(str, 10))
let n = 0
n += parts[3]
n += parts[2] * 256 // 2^8
n += parts[1] * 65536 // 2^16
n += parts[0] * 16777216 // 2^24
return n
}
function assertIpv4 (str, msg) {
if (!ipv4.test(str)) { throw new Error(msg) }
}
function *range (ip1, ip2) {
assertIpv4(ip1, 'argument "ip1" must be a valid IPv4 address')
assertIpv4(ip2, 'argument "ip2" must be a valid IPv4 address')
let hex = ip2hex(ip1)
let hex2 = ip2hex(ip2)
if (hex > hex2) {
let tmp = hex
hex = hex2
hex2 = tmp
}
for (let i = hex; i <= hex2; i++) {
yield `${(i >> 24) & 0xff}.${(i >> 16) & 0xff}.${(i >> 8) & 0xff}.${i & 0xff}`
}
}
// -----------------------------------------------------------------------------
export const getNextIpV4 = ip => {
const splitIp = ip.split('.')
if (splitIp.length !== 4 || some(splitIp, value => value < 0 || value > 255)) {
return
}
let index
forEachRight(splitIp, (value, i) => {
if (value < 255) {
index = i
return false
}
splitIp[i] = 1
})
if (index === 0 && +splitIp[0] === 255) {
return 0
}
splitIp[index]++
return splitIp.join('.')
}
export const formatIps = ips => {
if (!isArray(ips)) {
throw new Error('ips must be an array')
}
if (ips.length === 0) {
return []
}
const sortedIps = ips.sort((ip1, ip2) => {
const splitIp1 = ip1.split('.')
const splitIp2 = ip2.split('.')
if (splitIp1.length !== 4) {
return 1
}
if (splitIp2.length !== 4) {
return -1
}
return splitIp1[3] - splitIp2[3] +
(splitIp1[2] - splitIp2[2]) * 256 +
(splitIp1[1] - splitIp2[1]) * 256 * 256 +
(splitIp1[0] - splitIp2[0]) * 256 * 256 * 256
})
const range = { first: '', last: '' }
const formattedIps = []
let index = 0
forEach(sortedIps, ip => {
if (ip !== getNextIpV4(range.last)) {
if (range.first) {
formattedIps[index] = range.first === range.last ? range.first : { ...range }
index++
}
range.first = range.last = ip
} else {
range.last = ip
}
})
formattedIps[index] = range.first === range.last ? range.first : range
return formattedIps
}
export const parseIpPattern = pattern => {
const ips = []
forEach(pattern.split(';'), rawIpRange => {
const ipRange = rawIpRange.split('-')
if (ipRange.length < 2) {
ips.push(ipRange[0])
} else if (!isIpV4(ipRange[0]) || !isIpV4(ipRange[1])) {
ips.push(rawIpRange)
} else {
ips.push(...range(ipRange[0], ipRange[1]))
}
})
return ips
}

84
src/common/iso-device.js Normal file
View File

@@ -0,0 +1,84 @@
import React from 'react'
import ActionButton from './action-button'
import Component from './base-component'
import propTypes from './prop-types'
import { connectStore } from './utils'
import { SelectVdi } from './select-objects'
import {
createGetObjectsOfType,
createFinder,
createGetObject,
createSelector
} from './selectors'
import {
ejectCd,
insertCd
} from './xo'
@propTypes({
vm: propTypes.object.isRequired
})
@connectStore(() => {
const getCdDrive = createFinder(
createGetObjectsOfType('VBD').pick(
(_, { vm }) => vm.$VBDs
),
[ vbd => vbd.is_cd_drive ]
)
const getMountedIso = createGetObject(
(state, props) => {
const cdDrive = getCdDrive(state, props)
if (cdDrive) {
return cdDrive.VDI
}
}
)
return {
cdDrive: getCdDrive,
mountedIso: getMountedIso
}
})
export default class IsoDevice extends Component {
_getPredicate = createSelector(
() => this.props.vm.$pool,
poolId => sr => sr.$pool === poolId && sr.SR_type === 'iso'
)
_handleInsert = iso => {
const { vm } = this.props
if (iso) {
insertCd(vm, iso.id, true)
} else {
ejectCd(vm)
}
}
_handleEject = () => ejectCd(this.props.vm)
render () {
const { mountedIso } = this.props
return (
<div className='input-group'>
<SelectVdi
srPredicate={this._getPredicate()}
onChange={this._handleInsert}
ref='selectIso'
value={mountedIso}
/>
<span className='input-group-btn'>
<ActionButton
btnStyle='secondary'
disabled={!mountedIso}
handler={this._handleEject}
icon='vm-eject'
/>
</span>
</div>
)
}
}

View File

@@ -0,0 +1,26 @@
import { Component } from 'react'
import propTypes from '../prop-types'
// ===================================================================
@propTypes({
disabled: propTypes.bool,
label: propTypes.any.isRequired,
onChange: propTypes.func,
placeholder: propTypes.string,
required: propTypes.bool,
schema: propTypes.object.isRequired,
uiSchema: propTypes.object,
defaultValue: propTypes.any
})
export default class AbstractInput extends Component {
set value (value) {
this.refs.input.value = value === undefined ? '' : String(value)
}
get value () {
const { value } = this.refs.input
return !value ? undefined : value
}
}

View File

@@ -0,0 +1,186 @@
import React, { Component, cloneElement } from 'react'
import map from 'lodash/map'
import filter from 'lodash/filter'
import _ from '../intl'
import propTypes from '../prop-types'
import { propsEqual } from '../utils'
import GenericInput from './generic-input'
import {
descriptionRender,
forceDisplayOptionalAttr
} from './helpers'
// ===================================================================
class ArrayItem extends Component {
get value () {
return this.refs.input.value
}
set value (value) {
this.refs.input.value = value
}
render () {
const { children } = this.props
return (
<li className='list-group-item clearfix'>
{cloneElement(children, {
ref: 'input'
})}
<button disabled={children.props.disabled} className='btn btn-danger pull-xs-right' type='button' onClick={this.props.onDelete}>
{_('remove')}
</button>
</li>
)
}
}
// ===================================================================
@propTypes({
depth: propTypes.number,
disabled: propTypes.bool,
label: propTypes.any.isRequired,
required: propTypes.bool,
schema: propTypes.object.isRequired,
uiSchema: propTypes.object,
defaultValue: propTypes.array
})
export default class ArrayInput extends Component {
constructor (props) {
super(props)
this.state = {
use: props.required || forceDisplayOptionalAttr(props),
children: this._makeChildren(props)
}
this._nextChildKey = 0
}
get value () {
if (this.state.use) {
return map(this.refs, 'value')
}
}
set value (value = []) {
this.setState({
children: this._makeChildren({ ...this.props, value })
})
}
_handleOptionalChange = event => {
this.setState({
use: event.target.checked
})
}
_handleAdd = () => {
const { children } = this.state
this.setState({
children: children.concat(this._makeChild(this.props))
})
}
_remove (key) {
this.setState({
children: filter(this.state.children, child => child.key !== key)
})
}
_makeChild (props) {
const key = String(this._nextChildKey++)
const {
schema: {
items
}
} = props
return (
<ArrayItem key={key} onDelete={() => { this._remove(key) }}>
<GenericInput
depth={props.depth}
disabled={props.disabled}
label={items.title || _('item')}
required
schema={items}
uiSchema={props.uiSchema.items}
defaultValue={props.defaultValue}
/>
</ArrayItem>
)
}
_makeChildren ({ defaultValue, ...props }) {
return map(defaultValue, defaultValue => {
return (
this._makeChild({
...props,
defaultValue
})
)
})
}
componentWillReceiveProps (props) {
if (
!propsEqual(
this.props,
props,
[ 'depth', 'disabled', 'label', 'required', 'schema', 'uiSchema' ]
)
) {
this.setState({
children: this._makeChildren(props)
})
}
}
render () {
const {
props,
state
} = this
const {
disabled,
schema
} = props
const { use } = state
const depth = props.depth || 0
return (
<div style={{'paddingLeft': `${depth}em`}}>
<legend>{props.label}</legend>
{descriptionRender(schema.description)}
<hr />
{!props.required &&
<div className='checkbox'>
<label>
<input
checked={use}
disabled={disabled}
onChange={this._handleOptionalChange}
type='checkbox'
/> {_('fillOptionalInformations')}
</label>
</div>
}
{use &&
<div className={'card-block'}>
<ul style={{'paddingLeft': 0}} >
{map(this.state.children, (child, index) =>
cloneElement(child, { ref: index })
)}
</ul>
<button disabled={disabled} className='btn btn-primary pull-xs-right m-t-1 m-r-1' type='button' onClick={this._handleAdd}>
{_('add')}
</button>
</div>
}
</div>
)
}
}

View File

@@ -0,0 +1,35 @@
import React from 'react'
import { Toggle } from 'form'
import AbstractInput from './abstract-input'
import { PrimitiveInputWrapper } from './helpers'
// ===================================================================
export default class BooleanInput extends AbstractInput {
get value () {
return this.refs.input.value
}
set value (value) {
this.refs.input.value = value
}
render () {
const { props } = this
return (
<PrimitiveInputWrapper {...props}>
<div className='checkbox form-control'>
<Toggle
defaultValue={props.defaultValue}
disabled={props.disabled}
onChange={props.onChange}
ref='input'
/>
</div>
</PrimitiveInputWrapper>
)
}
}

View File

@@ -0,0 +1,36 @@
import React from 'react'
import _ from 'intl'
import map from 'lodash/map'
import AbstractInput from './abstract-input'
import { PrimitiveInputWrapper } from './helpers'
// ===================================================================
export default class EnumInput extends AbstractInput {
render () {
const { props } = this
const {
onChange,
required
} = props
return (
<PrimitiveInputWrapper {...props}>
<select
className='form-control'
defaultValue={props.defaultValue || ''}
disabled={props.disabled}
onChange={onChange && (event => onChange(event.target.value))}
ref='input'
required={required}
>
{_('noSelectedValue', message => <option value=''>{message}</option>)}
{map(props.schema.enum, (value, index) =>
<option value={value} key={index}>{value}</option>
)}
</select>
</PrimitiveInputWrapper>
)
}
}

View File

@@ -0,0 +1,78 @@
import React, { Component } from 'react'
import propTypes from '../prop-types'
import { EMPTY_OBJECT } from '../utils'
import ArrayInput from './array-input'
import BooleanInput from './boolean-input'
import EnumInput from './enum-input'
import IntegerInput from './integer-input'
import NumberInput from './number-input'
import ObjectInput from './object-input'
import StringInput from './string-input'
import { getType } from './helpers'
// ===================================================================
const InputByType = {
array: ArrayInput,
boolean: BooleanInput,
integer: IntegerInput,
number: NumberInput,
object: ObjectInput,
string: StringInput
}
// ===================================================================
@propTypes({
depth: propTypes.number,
disabled: propTypes.bool,
label: propTypes.any.isRequired,
onChange: propTypes.func,
required: propTypes.bool,
schema: propTypes.object.isRequired,
uiSchema: propTypes.object,
defaultValue: propTypes.any
})
export default class GenericInput extends Component {
get value () {
return this.refs.input.value
}
set value (value) {
this.refs.input.value = value
}
render () {
const {
schema,
defaultValue = schema.default,
uiSchema = EMPTY_OBJECT,
...opts
} = this.props
const props = {
...opts,
defaultValue,
schema,
uiSchema,
ref: 'input'
}
// Enum, special case.
if (schema.enum) {
return <EnumInput {...props} />
}
const type = getType(schema)
const Input = uiSchema.widget || InputByType[type.toLowerCase()]
if (!Input) {
throw new Error(`Unsupported type: ${type}.`)
}
return <Input {...props} {...uiSchema.config} />
}
}

View File

@@ -0,0 +1,83 @@
import React from 'react'
import includes from 'lodash/includes'
import isArray from 'lodash/isArray'
import marked from 'marked'
import { Col, Row } from 'grid'
// ===================================================================
export const getType = schema => {
if (!schema) {
return
}
const type = schema.type
if (isArray(type)) {
if (includes(type, 'integer')) {
return 'integer'
}
if (includes(type, 'number')) {
return 'number'
}
return 'string'
}
return type
}
export const getXoType = schema => {
const type = schema && (schema['xo:type'] || schema.$type)
if (type) {
return type.toLowerCase()
}
}
// ===================================================================
export const descriptionRender = description =>
<span className='text-muted' dangerouslySetInnerHTML={{__html: marked(description || '')}} />
// ===================================================================
export const PrimitiveInputWrapper = ({ label, required = false, schema, children }) => (
<Row>
<Col mediumSize={6}>
<div className='input-group'>
<span className='input-group-addon'>
{label}
{required && <span className='text-warning'>*</span>}
</span>
{children}
</div>
</Col>
<Col mediumSize={6}>
{descriptionRender(schema.description)}
</Col>
</Row>
)
// ===================================================================
export const forceDisplayOptionalAttr = ({ schema, defaultValue }) => {
if (!schema || !defaultValue) {
return false
}
// Array
if (schema.items && Array.isArray(defaultValue)) {
return true
}
// Object
for (const key in schema.properties) {
if (defaultValue[key]) {
return true
}
}
return false
}

View File

@@ -0,0 +1 @@
export default from './generic-input'

View File

@@ -0,0 +1,42 @@
import React from 'react'
import AbstractInput from './abstract-input'
import Combobox from '../combobox'
import { PrimitiveInputWrapper } from './helpers'
// ===================================================================
export default class IntegerInput extends AbstractInput {
get value () {
const { value } = this.refs.input
return !value ? undefined : +value
}
set value (value) {
// Getter/Setter are always inherited together.
// `get value` is defined in the subclass, so `set value`
// must be defined too.
super.value = value
}
render () {
const { props } = this
const { schema } = props
return (
<PrimitiveInputWrapper {...props}>
<Combobox
defaultValue={props.defaultValue}
disabled={props.disabled}
onChange={props.onChange}
options={schema.defaults}
placeholder={props.placeholder || schema.default}
ref='input'
required={props.required}
step={1}
type='number'
/>
</PrimitiveInputWrapper>
)
}
}

View File

@@ -0,0 +1,42 @@
import React from 'react'
import AbstractInput from './abstract-input'
import Combobox from '../combobox'
import { PrimitiveInputWrapper } from './helpers'
// ===================================================================
export default class NumberInput extends AbstractInput {
get value () {
const { value } = this.refs.input
return !value ? undefined : +value
}
set value (value) {
// Getter/Setter are always inherited together.
// `get value` is defined in the subclass, so `set value`
// must be defined too.
super.value = value
}
render () {
const { props } = this
const { schema } = props
return (
<PrimitiveInputWrapper {...props}>
<Combobox
defaultValue={props.defaultValue}
disabled={props.disabled}
onChange={props.onChange}
options={schema.defaults}
placeholder={props.placeholder || schema.default}
ref='input'
required={props.required}
step='any'
type='number'
/>
</PrimitiveInputWrapper>
)
}
}

View File

@@ -0,0 +1,164 @@
import _ from 'intl'
import React, { Component, cloneElement } from 'react'
import forEach from 'lodash/forEach'
import includes from 'lodash/includes'
import map from 'lodash/map'
import propTypes from '../prop-types'
import { propsEqual } from '../utils'
import GenericInput from './generic-input'
import {
descriptionRender,
forceDisplayOptionalAttr
} from './helpers'
// ===================================================================
class ObjectItem extends Component {
get value () {
return this.refs.input.value
}
set value (value) {
this.refs.input.value = value
}
render () {
const { props } = this
return (
<div className='p-b-1'>
{cloneElement(props.children, {
ref: 'input'
})}
</div>
)
}
}
// ===================================================================
@propTypes({
depth: propTypes.number,
disabled: propTypes.bool,
label: propTypes.any.isRequired,
required: propTypes.bool,
schema: propTypes.object.isRequired,
uiSchema: propTypes.object,
defaultValue: propTypes.object
})
export default class ObjectInput extends Component {
constructor (props) {
super(props)
this.state = {
use: Boolean(props.required) || forceDisplayOptionalAttr(props),
children: this._makeChildren(props)
}
}
get value () {
if (!this.state.use) {
return
}
const obj = {}
forEach(this.refs, (instance, key) => {
obj[key] = instance.value
})
return obj
}
set value (value = {}) {
forEach(this.refs, (instance, id) => {
instance.value = value[id]
})
}
_handleOptionalChange = event => {
const { checked } = event.target
this.setState({
use: checked
})
}
_makeChildren (props) {
const {
depth = 0,
schema,
uiSchema = {},
defaultValue = {}
} = props
const obj = {}
const { properties } = uiSchema
forEach(schema.properties, (childSchema, key) => {
obj[key] = (
<ObjectItem key={key}>
<GenericInput
depth={depth + 2}
disabled={props.disabled}
label={childSchema.title || key}
required={includes(schema.required, key)}
schema={childSchema}
uiSchema={properties && properties[key]}
defaultValue={defaultValue[key]}
/>
</ObjectItem>
)
})
return obj
}
componentWillReceiveProps (props) {
if (
!propsEqual(
this.props,
props,
[ 'depth', 'disabled', 'label', 'required', 'schema', 'uiSchema' ]
)
) {
this.setState({
children: this._makeChildren(props)
})
}
}
render () {
const { props, state } = this
const { use } = state
const depth = props.depth || 0
return (
<div style={{'paddingLeft': `${depth}em`}}>
<legend>{props.label}</legend>
{descriptionRender(props.schema.description)}
<hr />
{!props.required &&
<div className='checkbox'>
<label>
<input
checked={use}
disabled={props.disabled}
onChange={this._handleOptionalChange}
type='checkbox'
/> {_('fillOptionalInformations')}
</label>
</div>
}
{use &&
<div className='card-block'>
{map(state.children, (child, index) =>
cloneElement(child, { ref: index })
)}
</div>
}
</div>
)
}
}

View File

@@ -0,0 +1,33 @@
import React from 'react'
import AbstractInput from './abstract-input'
import Combobox from '../combobox'
import propTypes from '../prop-types'
import { PrimitiveInputWrapper } from './helpers'
// ===================================================================
@propTypes({
password: propTypes.bool
})
export default class StringInput extends AbstractInput {
render () {
const { props } = this
const { schema } = props
return (
<PrimitiveInputWrapper {...props}>
<Combobox
defaultValue={props.defaultValue}
disabled={props.disabled}
onChange={props.onChange}
options={schema.defaults}
placeholder={props.placeholder || schema.default}
ref='input'
required={props.required}
type={props.password && 'password'}
/>
</PrimitiveInputWrapper>
)
}
}

59
src/common/link.js Normal file
View File

@@ -0,0 +1,59 @@
import Link from 'react-router/lib/Link'
import React from 'react'
import { routerShape } from 'react-router/lib/PropTypes'
import Component from './base-component'
import propTypes from './prop-types'
// ===================================================================
export { Link as default }
// -------------------------------------------------------------------
const _IGNORED_TAGNAMES = {
A: true,
BUTTON: true,
INPUT: true,
SELECT: true
}
@propTypes({
tagName: propTypes.string
})
export class BlockLink extends Component {
static contextTypes = {
router: routerShape
}
_style = { cursor: 'pointer' }
_onClickCapture = event => {
const { currentTarget } = event
let element = event.target
while (element !== currentTarget) {
if (_IGNORED_TAGNAMES[element.tagName]) {
return
}
element = element.parentNode
}
event.stopPropagation()
if (event.ctrlKey || event.button === 1) {
window.open(this.context.router.createHref(this.props.to))
} else {
this.context.router.push(this.props.to)
}
}
render () {
const { children, tagName = 'div' } = this.props
const Component = tagName
return (
<Component
style={this._style}
onClickCapture={this._onClickCapture}
>
{children}
</Component>
)
}
}

13
src/common/log-error.js Normal file
View File

@@ -0,0 +1,13 @@
// Logs an error properly, correctly use the source map for the stack.
//
// This is achieved by throwing the error asynchronously.
const logError = (error, ...args) => {
setTimeout(() => {
if (args.length) {
console.error(...args)
}
throw error
}, 0)
}
export { logError as default }

171
src/common/modal.js Normal file
View File

@@ -0,0 +1,171 @@
import _ from 'intl'
import Icon from 'icon'
import isArray from 'lodash/isArray'
import isString from 'lodash/isString'
import React, { Component, cloneElement } from 'react'
import { Button, Modal as ReactModal } from 'react-bootstrap-4/lib'
import propTypes from './prop-types'
let instance
const modal = (content, onClose) => {
if (!instance) {
throw new Error('No modal instance.')
} else if (instance.state.showModal) {
throw new Error('Other modal still open.')
}
instance.setState({ content, onClose, showModal: true })
}
export const alert = (title, body) => {
return new Promise(resolve => {
const { Body, Footer, Header, Title } = ReactModal
modal(
<div>
<Header closeButton>
<Title>{title}</Title>
</Header>
<Body>{body}</Body>
<Footer>
<Button bsStyle='primary' onClick={() => {
resolve()
instance.close()
}}>
{_('alertOk')}
</Button>
</Footer>
</div>,
resolve
)
})
}
const _addRef = (component, ref) => {
if (isString(component) || isArray(component)) {
return component
}
try {
return cloneElement(component, { ref })
} catch (_) {} // Stateless component.
return component
}
@propTypes({
children: propTypes.node.isRequired,
title: propTypes.node.isRequired,
icon: propTypes.string
})
class Confirm extends Component {
_resolve = () => {
const { body } = this.refs
this.props.resolve(body && (body.getWrappedInstance
? body.getWrappedInstance().value
: body.value
))
instance.close()
}
_reject = () => {
this.props.reject()
instance.close()
}
_style = { marginRight: '0.5em' }
render () {
const { Body, Footer, Header, Title } = ReactModal
const { title, icon } = this.props
const body = _addRef(this.props.children, 'body')
return <div>
<Header closeButton>
<Title>
{icon
? <span><Icon icon={icon} /> {title}</span>
: title
}
</Title>
</Header>
<Body>
{body}
</Body>
<Footer>
<Button
bsStyle='primary'
onClick={this._resolve}
style={this._style}
>
{_('confirmOk')}
</Button>
<Button
bsStyle='secondary'
onClick={this._reject}
>
{_('confirmCancel')}
</Button>
</Footer>
</div>
}
}
export const confirm = ({
body,
title,
icon = 'alarm'
}) => {
return new Promise((resolve, reject) => {
modal(
<Confirm
title={title}
resolve={resolve}
reject={reject}
icon={icon}
>
{body}
</Confirm>,
reject
)
})
}
export default class Modal extends Component {
constructor () {
super()
if (instance) {
throw new Error('Modal is a singleton!')
}
instance = this
}
componentWillMount () {
this.setState({ showModal: false })
}
close () {
this.setState({ showModal: false })
}
_onHide = () => {
this.close()
const { onClose } = this.state
onClose && onClose()
}
render () {
const { showModal } = this.state
/* TODO: remove this work-around and use
* ReactModal.Body, ReactModal.Header, ...
* after this issue has been fixed:
* https://phabricator.babeljs.io/T6976
*/
return (
<ReactModal show={showModal} onHide={this._onHide}>
{this.state.content}
</ReactModal>
)
}
}

18
src/common/nav.js Normal file
View File

@@ -0,0 +1,18 @@
import classNames from 'classnames'
import React from 'react'
import Link from './link'
export const NavLink = ({ children, to }) => (
<li className='nav-item' role='tab'>
<Link className='nav-link' activeClassName='active' to={to}>
{children}
</Link>
</li>
)
export const NavTabs = ({ children, className }) => (
<ul className={classNames(className, 'nav nav-tabs')} role='tablist'>
{children}
</ul>
)

View File

@@ -0,0 +1,55 @@
import React, { Component } from 'react'
import ReactNotify from 'react-notify'
let instance
export let error
export let info
export let success
export class Notification extends Component {
constructor () {
super()
if (instance) {
throw new Error('Notification is a singleton!')
}
instance = this
}
// This special component never have to rerender!
shouldComponentUpdate () {
return false
}
render () {
return <ReactNotify ref={notification => {
if (!notification) {
return
}
error = (title, body) => notification.error(title, body, 3e3)
info = (title, body) => notification.info(title, body, 3e3)
success = (title, body) => notification.success(title, body, 3e3)
}} />
}
}
export { info as default }
/* Example:
import info, { success, error } from 'notification'
<button onClick={() => info('Info', 'This is an info notification')}>
Info notification
</button>
<button onClick={() => success('Success', 'This is a success notification')}>
Success notification
</button>
<button onClick={() => error('Error', 'This is an error notification')}>
Error notification
</button>
*/

15
src/common/object-name.js Normal file
View File

@@ -0,0 +1,15 @@
/** EXPERIMENT: this is here to avoid a littel code dupplication, but is not admitted as a highly recommendable component */
import { connectStore } from 'utils'
import { createGetObject } from 'selectors'
import React, { Component } from 'react'
@connectStore(() => {
const object = createGetObject()
return (state, props) => ({object: object(state, props)})
})
export default class ObjectName extends Component {
render () {
const { object } = this.props
return <span>{object && object.name_label}</span>
}
}

22
src/common/prop-types.js Normal file
View File

@@ -0,0 +1,22 @@
import assign from 'lodash/assign'
import { PropTypes } from 'react'
// Decorators to help declaring on React components without using the
// tedious static properties syntax.
//
// ```js
// @propTypes({
// children: propTypes.node.isRequired
// })
// class MyComponent extends React.Component {}
// ```
const propTypes = types => target => {
target.propTypes = {
...target.propTypes,
...types
}
return target
}
assign(propTypes, PropTypes)
export { propTypes as default }

151
src/common/react-novnc.js vendored Normal file
View File

@@ -0,0 +1,151 @@
import React, { Component } from 'react'
import { createBackoff } from 'jsonrpc-websocket-client'
import { RFB } from 'novnc-node'
import {
format as formatUrl,
parse as parseUrl,
resolve as resolveUrl
} from 'url'
import { enable as enableShortcuts, disable as disableShortcuts } from 'shortcuts'
import propTypes from './prop-types'
const parseRelativeUrl = url => parseUrl(resolveUrl(String(window.location), url))
const PROTOCOL_ALIASES = {
'http:': 'ws:',
'https:': 'wss:'
}
const fixProtocol = url => {
const protocol = PROTOCOL_ALIASES[url.protocol]
if (protocol) {
url.protocol = protocol
}
}
@propTypes({
onClipboardChange: propTypes.func,
url: propTypes.string.isRequired
})
export default class NoVnc extends Component {
constructor (props) {
super(props)
this._rfb = null
this._retryGen = createBackoff(Infinity)
this._onUpdateState = (rfb, state) => {
if (state === 'normal') {
if (this._retryTimeout) {
clearTimeout(this._retryTimeout)
this._retryTimeout = undefined
this._retryGen = createBackoff(Infinity)
}
}
if (state !== 'disconnected') {
return
}
clearTimeout(this._retryTimeout)
this._retryTimeout = setTimeout(this._connect, this._retryGen.next().value)
}
}
sendCtrlAltDel () {
const rfb = this._rfb
if (rfb) {
rfb.sendCtrlAltDel()
}
}
setClipboard (text) {
const rfb = this._rfb
if (rfb) {
rfb.clipboardPasteFrom(text)
}
}
_clean () {
const rfb = this._rfb
if (rfb) {
this._rfb = null
rfb.disconnect()
}
enableShortcuts()
}
_connect = () => {
this._clean()
const url = parseRelativeUrl(this.props.url)
fixProtocol(url)
const isSecure = url.protocol === 'wss:'
const { onClipboardChange } = this.props
const rfb = this._rfb = new RFB({
encrypt: isSecure,
target: this.refs.canvas,
wsProtocols: [ 'chat' ],
onClipboard: onClipboardChange && ((_, text) => {
onClipboardChange(text)
}),
onUpdateState: this._onUpdateState
})
rfb.connect(formatUrl(url))
disableShortcuts()
}
componentDidMount () {
this._connect()
}
componentWillUnmount () {
this._clean()
}
componentWillReceiveProps (props) {
const rfb = this._rfb
if (rfb && this.props.scale !== props.scale) {
rfb.get_display().set_scale(props.scale || 1)
rfb.get_mouse().set_scale(props.scale || 1)
}
}
_focus = () => {
const rfb = this._rfb
if (rfb) {
const { activeElement } = document
if (activeElement) {
activeElement.blur()
}
rfb.get_keyboard().grab()
rfb.get_mouse().grab()
disableShortcuts()
}
}
_unfocus = () => {
const rfb = this._rfb
if (rfb) {
rfb.get_keyboard().ungrab()
rfb.get_mouse().ungrab()
enableShortcuts()
}
}
render () {
return <canvas
className='center-block'
height='480'
onMouseEnter={this._focus}
onMouseLeave={this._unfocus}
ref='canvas'
width='640'
/>
}
}

View File

@@ -0,0 +1,223 @@
import React from 'react'
import Icon from './icon'
import propTypes from './prop-types'
import { createGetObject } from './selectors'
import { isSrWritable } from './xo'
import {
connectStore,
formatSize
} from './utils'
// ===================================================================
const OBJECT_TYPE_TO_ICON = {
'VM-template': 'vm',
host: 'host',
network: 'network'
}
// Host, Network, VM-template.
export const PoolObjectItem = propTypes({
object: propTypes.object.isRequired
})(connectStore(() => {
const getPool = createGetObject(
(_, props) => props.object.$pool
)
return (state, props) => ({
pool: getPool(state, props)
})
})(({ object, pool }) => {
const icon = OBJECT_TYPE_TO_ICON[object.type]
const { id } = object
return (
<span>
<Icon icon={icon} /> {`${object.name_label || id} `}
{pool && `(${pool.name_label || pool.id})`}
</span>
)
}))
// SR.
export const SrItem = propTypes({
sr: propTypes.object.isRequired
})(connectStore(() => {
const getContainer = createGetObject(
(_, props) => props.sr.$container
)
return (state, props) => ({
container: getContainer(state, props)
})
})(({ sr, container }) => {
let label = `${sr.name_label || sr.id}`
if (isSrWritable(sr)) {
label += ` (${formatSize(sr.size - sr.physical_usage)} free)`
}
return (
<span>
<Icon icon='sr' /> {label}
</span>
)
}))
// VM.
export const VmItem = propTypes({
vm: propTypes.object.isRequired
})(connectStore(() => {
const getContainer = createGetObject(
(_, props) => props.vm.$container
)
return (state, props) => ({
container: getContainer(state, props)
})
})(({ vm, container }) => (
<span>
<Icon icon={`vm-${vm.power_state.toLowerCase()}`} /> {vm.name_label || vm.id}
{container && ` (${container.name_label || container.id})`}
</span>
)))
// ===================================================================
const xoItemToRender = {
// Subscription objects.
group: group => (
<span>
<Icon icon='group' /> {group.name}
</span>
),
remote: remote => (
<span>
<Icon icon='remote' /> {remote.value.name}
</span>
),
role: role => (
<span>
{role.name}
</span>
),
user: user => (
<span>
<Icon icon='user' /> {user.email}
</span>
),
resourceSet: resourceSet => (
<span>
<Icon icon='resource-set' /> {resourceSet.name}
</span>
),
sshKey: key => (
<span>
<Icon icon='ssh-key' /> {key.label}
</span>
),
ipPool: ipPool => (
<span>
<Icon icon='ip' /> {ipPool.name}
</span>
),
ipAddress: ({label, used}) => {
if (used) {
return <strong className='text-warning'>{label}</strong>
}
return <span>{label}</span>
},
// XO objects.
pool: pool => (
<span>
<Icon icon='pool' /> {pool.name_label || pool.id}
</span>
),
VDI: vdi => (
<span>
<Icon icon='disk' /> {vdi.name_label} {vdi.name_description && <span> ({vdi.name_description})</span>}
</span>
),
// Pool objects.
'VM-template': vmTemplate => <PoolObjectItem object={vmTemplate} />,
host: host => <PoolObjectItem object={host} />,
network: network => <PoolObjectItem object={network} />,
// SR.
SR: sr => <SrItem sr={sr} />,
// VM.
VM: vm => <VmItem vm={vm} />,
'VM-snapshot': vm => <VmItem vm={vm} />,
'VM-controller': vm => (
<span>
<Icon icon='host' />
{' '}
<VmItem vm={vm} />
</span>
),
// PIF.
PIF: pif => (
<span>
<Icon icon='network' /> {pif.device}
</span>
),
// Tags.
tag: tag => (
<span>
<Icon icon='tag' /> {tag.value}
</span>
)
}
const renderXoItem = (item, {
className
} = {}) => {
const { id, type, label } = item
if (!type) {
if (process.env.NODE_ENV !== 'production' && !label) {
throw new Error(`an item must have at least either a type or a label`)
}
return (
<span key={id} className={className}>
{label}
</span>
)
}
const Component = xoItemToRender[type]
if (process.env.NODE_ENV !== 'production' && !Component) {
throw new Error(`no available component for type ${type}`)
}
if (Component) {
return (
<span key={id} className={className}>
<Component {...item} />
</span>
)
}
}
export { renderXoItem as default }
const GenericXoItem = connectStore(() => {
const getObject = createGetObject()
return (state, props) => ({
xoItem: getObject(state, props)
})
})(({ xoItem, ...props }) => xoItem
? renderXoItem(xoItem, props)
: <span className='text-muted'>no such item</span>
)
export const renderXoItemFromId = (id, props) => <GenericXoItem {...props} id={id} />

491
src/common/scheduling.js Normal file
View File

@@ -0,0 +1,491 @@
import includes from 'lodash/includes'
import join from 'lodash/join'
import later from 'later'
import map from 'lodash/map'
import React from 'react'
import sortedIndex from 'lodash/sortedIndex'
import { FormattedDate, FormattedTime } from 'react-intl'
import {
Tab,
Tabs
} from 'react-bootstrap-4/lib'
import _ from './intl'
import Component from './base-component'
import propTypes from './prop-types'
import TimezonePicker from './timezone-picker'
import { Card, CardHeader, CardBlock } from './card'
import { Col, Row } from './grid'
import { Range } from './form'
// ===================================================================
// By default later use UTC but we use this line for futures versions.
later.date.UTC()
// ===================================================================
const NAV_EACH_SELECTED = 1
const NAV_EVERY_N = 2
const MIN_PREVIEWS = 5
const MAX_PREVIEWS = 20
const MONTHS = [
[ 0, 1, 2 ],
[ 3, 4, 5 ],
[ 6, 7, 8 ],
[ 9, 10, 11 ]
]
const DAYS = (() => {
const days = []
for (let i = 0; i < 4; i++) {
days[i] = []
for (let j = 1; j < 8; j++) {
days[i].push(7 * i + j)
}
}
days.push([29, 30, 31])
return days
})()
const WEEK_DAYS = [
[ 0, 1, 2 ],
[ 3, 4, 5 ],
[ 6 ]
]
const HOURS = (() => {
const hours = []
for (let i = 0; i < 3; i++) {
hours[i] = []
for (let j = 0; j < 8; j++) {
hours[i].push(8 * i + j)
}
}
return hours
})()
const MINS = (() => {
const minutes = []
for (let i = 0; i < 6; i++) {
minutes[i] = []
for (let j = 0; j < 10; j++) {
minutes[i].push(10 * i + j)
}
}
return minutes
})()
const PICKTIME_TO_ID = {
minute: 0,
hour: 1,
monthDay: 2,
month: 3,
weekDay: 4
}
const TIME_FORMAT = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
// The timezone is not significant for displaying the date previews
// as long as it is the same used to generate the next occurrences
// from the cron patterns.
// Therefore we can use UTC everywhere and say to the user that the
// previews are in the configured timezone.
timeZone: 'UTC'
}
// ===================================================================
// monthNum: [ 0 : 11 ]
const getMonthName = (monthNum) =>
<FormattedDate value={Date.UTC(1970, monthNum)} month='long' timeZone='UTC' />
// dayNum: [ 0 : 6 ]
const getDayName = (dayNum) =>
// January, 1970, 5th => Monday
<FormattedDate value={Date.UTC(1970, 0, 4 + dayNum)} weekday='long' timeZone='UTC' />
// ===================================================================
@propTypes({
cronPattern: propTypes.string.isRequired
})
export class SchedulePreview extends Component {
_handleChange = value => {
this.setState({
value
})
}
render () {
const { cronPattern } = this.props
const cronSched = later.parse.cron(cronPattern)
const dates = later.schedule(cronSched).next(this.state.value || MIN_PREVIEWS)
return (
<div>
<div className='alert alert-info' role='alert'>
{_('cronPattern')} <strong>{cronPattern}</strong>
</div>
<div className='form-inline p-b-1'>
<Range min={MIN_PREVIEWS} max={MAX_PREVIEWS} onChange={this._handleChange} />
</div>
<ul className='list-group'>
{map(dates, (date, id) => (
<li className='list-group-item' key={id}>
<FormattedTime value={date} {...TIME_FORMAT} />
</li>
))}
<li className='list-group-item'>...</li>
</ul>
</div>
)
}
}
// ===================================================================
@propTypes({
children: propTypes.any.isRequired,
onChange: propTypes.func.isRequired,
tdId: propTypes.number.isRequired,
value: propTypes.bool.isRequired
})
class ToggleTd extends Component {
_onClick = () => {
const { props } = this
props.onChange(props.tdId, !props.value)
}
render () {
const { props } = this
return (
<td style={{ cursor: 'pointer' }} className={props.value ? 'table-success' : ''} onClick={this._onClick}>
{props.children}
</td>
)
}
}
// ===================================================================
@propTypes({
options: propTypes.array.isRequired,
optionsRenderer: propTypes.func,
onChange: propTypes.func.isRequired,
value: propTypes.array.isRequired
})
class TableSelect extends Component {
static defaultProps = {
optionsRenderer: value => value
}
_reset = () => {
this.props.onChange([])
}
_handleChange = (tdId, tdValue) => {
const { props } = this
const newValue = props.value.slice()
const index = sortedIndex(newValue, tdId)
if (tdValue) {
// Add
if (newValue[index] !== tdId) {
newValue.splice(index, 0, tdId)
}
} else {
// Remove
if (newValue[index] === tdId) {
newValue.splice(index, 1)
}
}
props.onChange(newValue)
}
render () {
const {
options,
optionsRenderer,
value
} = this.props
const { length } = options[0]
return (
<div>
<table className='table table-bordered table-sm'>
<tbody>
{map(options, (line, i) => (
<tr key={i}>
{map(line, (tdOption, j) => {
const tdId = length * i + j
return (
<ToggleTd
children={optionsRenderer(tdOption)}
tdId={tdId}
key={tdId}
onChange={this._handleChange}
value={includes(value, tdId)}
/>
)
})}
</tr>
))}
</tbody>
</table>
<button className='btn btn-secondary pull-xs-right' onClick={this._reset}>
{_('selectTableReset')}
</button>
</div>
)
}
}
// ===================================================================
@propTypes({
optionsRenderer: propTypes.func,
onChange: propTypes.func.isRequired,
range: propTypes.array,
labelId: propTypes.string.isRequired,
value: propTypes.any.isRequired,
valueRenderer: propTypes.func
})
class TimePicker extends Component {
static defaultProps = {
valueRenderer: e => +e
}
constructor () {
super()
this.state = {
activeKey: NAV_EACH_SELECTED,
tableValue: []
}
}
_update (props) {
const { value, valueRenderer } = props
if (value.indexOf('/') === 1) {
this.setState({
activeKey: NAV_EVERY_N
}, () => { this.refs.range.value = value.split('/')[1] })
} else {
this.setState({
activeKey: NAV_EACH_SELECTED,
tableValue: value === '*'
? []
: map(value.split(','), valueRenderer)
})
}
}
componentWillMount () {
this._update(this.props)
}
componentWillReceiveProps (props) {
this._update(props)
}
_selectTab = activeKey => {
this.setState({
activeKey
}, () => {
const { activeKey, tableValue } = this.state
const { onChange } = this.props
const { refs } = this
if (activeKey === NAV_EACH_SELECTED) {
onChange(tableValue)
} else {
onChange(refs.range.value)
}
})
}
_handleTableValue = tableValue => {
this.setState({
tableValue
}, () => this.props.onChange(tableValue))
}
render () {
const {
onChange,
options,
optionsRenderer,
range,
labelId
} = this.props
const { tableValue } = this.state
const tableSelect = (
<TableSelect
onChange={this._handleTableValue}
options={options}
optionsRenderer={optionsRenderer}
value={tableValue}
/>
)
return (
<Card>
<CardHeader>
{_(`scheduling${labelId}`)}
</CardHeader>
<CardBlock>
{range
? (
<Tabs bsStyle='tabs' activeKey={this.state.activeKey} onSelect={this._selectTab}>
<Tab tabClassName='nav-item' eventKey={NAV_EACH_SELECTED} title={_(`schedulingEachSelected${labelId}`)}>
{tableSelect}
</Tab>
<Tab tabClassName='nav-item' eventKey={NAV_EVERY_N} title={_(`schedulingEveryN${labelId}`)}>
<Range ref='range' min={range[0]} max={range[1]} onChange={onChange} />
</Tab>
</Tabs>
) : tableSelect
}
</CardBlock>
</Card>
)
}
}
// ===================================================================
const HOURS_RANGE = [2, 12]
const MINUTES_RANGE = [2, 30]
const decrement = e => e - 1
@propTypes({
cronPattern: propTypes.string.isRequired,
onChange: propTypes.func,
timezone: propTypes.string
})
export default class Scheduler extends Component {
_update (type, value) {
if (Array.isArray(value)) {
if (!value.length) {
value = '*'
} else {
value = join(
(type === 'monthDay' || type === 'month')
? map(value, n => n + 1)
: value,
','
)
}
} else {
value = `*/${value}`
}
const { props } = this
const cronPattern = props.cronPattern.split(' ')
cronPattern[PICKTIME_TO_ID[type]] = value
this.props.onChange({
cronPattern: cronPattern.join(' '),
timezone: props.timezone
})
}
_onHourChange = value => this._update('hour', value)
_onMinuteChange = value => this._update('minute', value)
_onMonthChange = value => this._update('month', value)
_onMonthDayChange = value => this._update('monthDay', value)
_onWeekDayChange = value => this._update('weekDay', value)
_onTimezoneChange = timezone => {
const { props } = this
props.onChange({
cronPattern: props.cronPattern,
timezone
})
}
render () {
const {
cronPattern,
timezone
} = this.props
const cronPatternArr = cronPattern.split(' ')
return (
<div className='card-block'>
<Row>
<Col mediumSize={6}>
<TimePicker
labelId='Month'
optionsRenderer={getMonthName}
options={MONTHS}
onChange={this._onMonthChange}
value={cronPatternArr[PICKTIME_TO_ID['month']]}
valueRenderer={decrement}
/>
<TimePicker
labelId='MonthDay'
options={DAYS}
onChange={this._onMonthDayChange}
value={cronPatternArr[PICKTIME_TO_ID['monthDay']]}
valueRenderer={decrement}
/>
<TimePicker
labelId='WeekDay'
optionsRenderer={getDayName}
options={WEEK_DAYS}
onChange={this._onWeekDayChange}
value={cronPatternArr[PICKTIME_TO_ID['weekDay']]}
/>
</Col>
<Col mediumSize={6}>
<TimePicker
labelId='Hour'
options={HOURS}
range={HOURS_RANGE}
onChange={this._onHourChange}
value={cronPatternArr[PICKTIME_TO_ID['hour']]}
/>
<TimePicker
labelId='Minute'
options={MINS}
range={MINUTES_RANGE}
onChange={this._onMinuteChange}
value={cronPatternArr[PICKTIME_TO_ID['minute']]}
/>
</Col>
</Row>
<Row>
<Col>
<hr />
<TimezonePicker value={timezone} onChange={this._onTimezoneChange} />
</Col>
</Row>
</div>
)
}
}

View File

@@ -0,0 +1,874 @@
import React from 'react'
import assign from 'lodash/assign'
import classNames from 'classnames'
import filter from 'lodash/filter'
import flatten from 'lodash/flatten'
import forEach from 'lodash/forEach'
import groupBy from 'lodash/groupBy'
import isEmpty from 'lodash/isEmpty'
import keyBy from 'lodash/keyBy'
import keys from 'lodash/keys'
import map from 'lodash/map'
import mapValues from 'lodash/mapValues'
import pick from 'lodash/pick'
import sortBy from 'lodash/sortBy'
import store from 'store'
import { parse as parseRemote } from 'xo-remote-parser'
import _ from './intl'
import Component from './base-component'
import propTypes from './prop-types'
import renderXoItem from './render-xo-item'
import { Select } from './form'
import {
createFilter,
createGetObjectsOfType,
createGetTags,
createSelector,
getObject
} from './selectors'
import {
connectStore,
mapPlus,
resolveResourceSets
} from './utils'
import {
isSrWritable,
subscribeCurrentUser,
subscribeGroups,
subscribeIpPools,
subscribeRemotes,
subscribeResourceSets,
subscribeRoles,
subscribeUsers
} from './xo'
// ===================================================================
const getLabel = object =>
object.name_label ||
object.name ||
object.email ||
(object.value && object.value.name) ||
object.value ||
object.label
// ===================================================================
/*
* WITHOUT xoContainers :
*
* xoObjects: [
* { type: 'myType', id: 'abc', label: 'First object' },
* { type: 'myType', id: 'def', label: 'Second object' }
* ]
*
*
* WITH xoContainers :
*
* xoContainers: [
* { type: 'containerType', id: 'ghi', label: 'First container' },
* { type: 'containerType', id: 'jkl', label: 'Second container' }
* ]
*
* xoObjects: {
* ghi: [
* { type: 'objectType', id: 'mno', label: 'First object' }
* { type: 'objectType', id: 'pqr', label: 'Second object' }
* ],
* jkl: [
* { type: 'objectType', id: 'stu', label: 'Third object' }
* ]
* }
*/
@propTypes({
autoFocus: propTypes.bool,
clearable: propTypes.bool,
defaultValue: propTypes.any,
disabled: propTypes.bool,
multi: propTypes.bool,
onChange: propTypes.func,
placeholder: propTypes.any.isRequired,
predicate: propTypes.func,
required: propTypes.bool,
value: propTypes.any,
xoContainers: propTypes.array,
xoObjects: propTypes.oneOfType([
propTypes.array,
propTypes.objectOf(propTypes.array)
]).isRequired
})
export class GenericSelect extends Component {
constructor (props) {
super(props)
this.state = {
value: this._setValue(props.value || props.defaultValue, props)
}
}
_getValue (xoObjectsById = this.state.xoObjectsById, props = this.props) {
const { value } = this.state
if (props.multi) {
// Returns the values of the selected objects
// if they are contained in xoObjectsById.
return mapPlus(value, (value, push) => {
const o = xoObjectsById[value.value !== undefined ? value.value : value]
if (o) {
push(o)
}
})
}
return xoObjectsById[value.value || value] || ''
}
// Supports id strings and objects.
_setValue (value, props = this.props) {
if (props.multi) {
return map(value, object => object.id !== undefined ? object.id : object)
}
return (value != null)
? value.id !== undefined ? value.id : value
: ''
}
componentWillMount () {
const { props } = this
this.setState({
...this._computeOptions(props)
})
}
componentWillReceiveProps (newProps) {
const { props } = this
const { value, xoContainers, xoObjects } = newProps
if (
xoContainers !== props.xoContainers ||
xoObjects !== props.xoObjects
) {
const {
options,
xoObjectsById
} = this._computeOptions(newProps)
const value = this._getValue(xoObjectsById, newProps)
this.setState({
options,
value: this._setValue(value, newProps),
xoObjectsById
})
}
if (value !== props.value) {
this.setState({
value: this._setValue(value, newProps)
})
}
}
_computeOptions ({ xoContainers, xoObjects }) {
if (!xoContainers) {
if (process.env.NODE_ENV !== 'production') {
if (!Array.isArray(xoObjects)) {
throw new Error('without xoContainers, xoObjects must be an array')
}
}
return {
xoObjectsById: keyBy(xoObjects, 'id'),
options: map(xoObjects, object => ({
label: getLabel(object),
value: object.id,
xoItem: object
}))
}
}
if (process.env.NODE_ENV !== 'production') {
if (Array.isArray(xoObjects)) {
throw new Error('with xoContainers, xoObjects must be an object')
}
}
const options = []
const xoObjectsById = {}
forEach(xoContainers, container => {
const containerObjects = keyBy(xoObjects[container.id], 'id')
assign(xoObjectsById, containerObjects)
options.push({
disabled: true,
xoItem: container
})
options.push.apply(options, map(containerObjects, object => ({
label: `${getLabel(object)} ${getLabel(container)}`,
value: object.id,
xoItem: object
})))
})
return { xoObjectsById, options }
}
get value () {
return this._getValue()
}
set value (value) {
this.setState({
value: this._setValue(value)
})
}
_handleChange = value => {
const { onChange } = this.props
this.setState({
value: this._setValue(value)
}, onChange && (() => onChange(this.value)))
}
// GroupBy: Display option with margin if not disabled and containers exists.
_renderOption = option => (
<span
className={classNames(
!option.disabled && this.props.xoContainers && 'm-l-1'
)}
>
{renderXoItem(option.xoItem)}
</span>
)
render () {
const { props, state } = this
return (
<Select
autofocus={props.autoFocus}
clearable={props.clearable}
disabled={props.disabled}
multi={props.multi}
onChange={this._handleChange}
openOnFocus
optionRenderer={this._renderOption}
options={state.options}
placeholder={props.placeholder}
required={props.required}
value={state.value}
valueRenderer={this._renderOption}
/>
)
}
}
const makeStoreSelect = (createSelectors, props) => connectStore(
createSelectors,
{ withRef: true }
)(
class extends Component {
get value () {
return this.refs.select.value
}
set value (value) {
this.refs.select.value = value
}
render () {
return (
<GenericSelect
ref='select'
{...props}
{...this.props}
/>
)
}
}
)
const makeSubscriptionSelect = (subscribe, props) => (
class extends Component {
constructor (props) {
super(props)
this._getFilteredXoContainers = createFilter(
() => this.state.xoContainers,
() => this.props.containerPredicate
)
this._getFilteredXoObjects = createSelector(
() => this.state.xoObjects,
() => this.state.xoContainers && this._getFilteredXoContainers(),
() => this.props.predicate,
(xoObjects, xoContainers, predicate) => {
if (xoContainers == null) {
return filter(xoObjects, predicate)
} else {
// Filter xoObjects with `predicate`...
const filteredObjects = mapValues(xoObjects, xoObjectsGroup =>
filter(xoObjectsGroup, predicate)
)
// ...and keep only those whose xoContainer hasn't been filtered out
return pick(filteredObjects, map(xoContainers, container => container.id))
}
}
)
}
get value () {
return this.refs.select.value
}
set value (value) {
this.refs.select.value = value
}
componentWillMount () {
this.componentWillUnmount = subscribe(::this.setState)
}
render () {
return (
<GenericSelect
ref='select'
{...props}
{...this.props}
xoObjects={this._getFilteredXoObjects()}
xoContainers={this.state.xoContainers && this._getFilteredXoContainers()}
/>
)
}
}
)
// ===================================================================
// XO objects.
// ===================================================================
const getPredicate = (state, props) => props.predicate
// ===================================================================
export const SelectHost = makeStoreSelect(() => {
const getHostsByPool = createGetObjectsOfType('host').filter(
getPredicate
).sort()
return {
xoObjects: getHostsByPool
}
}, { placeholder: _('selectHosts') })
// ===================================================================
export const SelectPool = makeStoreSelect(() => ({
xoObjects: createGetObjectsOfType('pool').filter(getPredicate).sort()
}), { placeholder: _('selectPools') })
// ===================================================================
export const SelectSr = makeStoreSelect(() => {
const getSrsByContainer = createGetObjectsOfType('SR').filter(
(_, { predicate }) => predicate || isSrWritable
).sort().groupBy('$container')
const getContainerIds = createSelector(
getSrsByContainer,
srsByContainer => keys(srsByContainer)
)
const getPools = createGetObjectsOfType('pool').pick(getContainerIds).sort()
const getHosts = createGetObjectsOfType('host').pick(getContainerIds).sort()
const getContainers = createSelector(
getPools,
getHosts,
(pools, hosts) => pools.concat(hosts)
)
return {
xoObjects: getSrsByContainer,
xoContainers: getContainers
}
}, { placeholder: _('selectSrs') })
// ===================================================================
export const SelectVm = makeStoreSelect(() => {
const getVmsByContainer = createGetObjectsOfType('VM').filter(
getPredicate
).sort().groupBy('$container')
const getContainerIds = createSelector(
getVmsByContainer,
vmsByContainer => keys(vmsByContainer)
)
const getPools = createGetObjectsOfType('pool').pick(getContainerIds).sort()
const getHosts = createGetObjectsOfType('host').pick(getContainerIds).sort()
const getContainers = createSelector(
getPools,
getHosts,
(pools, hosts) => pools.concat(hosts)
)
return {
xoObjects: getVmsByContainer,
xoContainers: getContainers
}
}, { placeholder: _('selectVms') })
// ===================================================================
export const SelectHostVm = makeStoreSelect(() => {
const getHosts = createGetObjectsOfType('host').filter(
getPredicate
).sort()
const getVms = createGetObjectsOfType('VM').filter(
getPredicate
).sort()
const getObjects = createSelector(
getHosts,
getVms,
(hosts, vms) => hosts.concat(vms)
)
return {
xoObjects: getObjects
}
}, { placeholder: _('selectHostsVms') })
// ===================================================================
export const SelectVmTemplate = makeStoreSelect(() => {
const getVmTemplatesByPool = createGetObjectsOfType('VM-template').filter(
getPredicate
).sort().groupBy('$container')
const getPools = createGetObjectsOfType('pool').pick(
createSelector(
getVmTemplatesByPool,
vmTemplatesByPool => keys(vmTemplatesByPool)
)
).sort()
return {
xoObjects: getVmTemplatesByPool,
xoContainers: getPools
}
}, { placeholder: _('selectVmTemplates') })
// ===================================================================
export const SelectNetwork = makeStoreSelect(() => {
const getNetworksByPool = createGetObjectsOfType('network').filter(
getPredicate
).sort().groupBy('$pool')
const getPools = createGetObjectsOfType('pool').pick(
createSelector(
getNetworksByPool,
networksByPool => keys(networksByPool)
)
).sort()
return {
xoObjects: getNetworksByPool,
xoContainers: getPools
}
}, { placeholder: _('selectNetworks') })
// ===================================================================
export const SelectPif = makeStoreSelect(() => {
const getPifsByHost = createGetObjectsOfType('PIF').filter(
getPredicate
).sort().groupBy('$host')
const getHosts = createGetObjectsOfType('host').pick(
createSelector(
getPifsByHost,
networksByPool => keys(networksByPool)
)
).sort()
return {
xoObjects: getPifsByHost,
xoContainers: getHosts
}
}, { placeholder: _('selectPifs') })
// ===================================================================
export const SelectTag = makeStoreSelect((_, props) => ({
xoObjects: createSelector(
createGetTags(
'objects' in props
? (_, props) => props.objects
: undefined
).filter(getPredicate).sort(),
tags => map(tags, tag => ({ id: tag, type: 'tag', value: tag }))
)
}), { placeholder: _('selectTags') })
export const SelectHighLevelObject = makeStoreSelect(() => {
const getHosts = createGetObjectsOfType('host')
const getNetworks = createGetObjectsOfType('network')
const getPools = createGetObjectsOfType('pool')
const getSrs = createGetObjectsOfType('SR')
const getVms = createGetObjectsOfType('VM')
const getHighLevelObjects = createSelector(
getHosts,
getNetworks,
getPools,
getSrs,
getVms,
(hosts, networks, pools, srs, vms) => sortBy(assign({}, hosts, networks, pools, srs, vms), ['type', 'name_label'])
)
return {xoObjects: getHighLevelObjects}
}, { placeholder: _('selectObjects') })
// ===================================================================
export const SelectVdi = propTypes({
srPredicate: propTypes.func
})(makeStoreSelect(() => {
const getSrs = createGetObjectsOfType('SR').filter((_, props) => props.srPredicate)
const getVdis = createGetObjectsOfType('VDI').filter(createSelector(
getSrs,
getPredicate,
(srs, predicate) => predicate ? vdi => srs[vdi.$SR] && predicate(vdi) : vdi => srs[vdi.$SR]
)).sort().groupBy('$SR')
return {
xoObjects: getVdis,
xoContainers: getSrs.sort()
}
}, { placeholder: _('selectVdis') }))
// ===================================================================
// Objects from subscriptions.
// ===================================================================
export const SelectSubject = makeSubscriptionSelect(subscriber => {
let subjects = {}
let usersLoaded, groupsLoaded
const set = newSubjects => {
subjects = newSubjects
/* We must wait for groups AND users options to be loaded,
* or a previously setted value belonging to one type or another might be discarded
* by the internal <GenericSelect>
*/
if (usersLoaded && groupsLoaded) {
subscriber({
xoObjects: subjects
})
}
}
const unsubscribeGroups = subscribeGroups(groups => {
groupsLoaded = true
set([
...filter(subjects, subject => subject.type === 'user'),
...groups
])
})
const unsubscribeUsers = subscribeUsers(users => {
usersLoaded = true
set([
...filter(subjects, subject => subject.type === 'group'),
...users
])
})
return () => {
unsubscribeGroups()
unsubscribeUsers()
}
}, { placeholder: _('selectSubjects') })
// ===================================================================
export const SelectRole = makeSubscriptionSelect(subscriber => {
const unsubscribeRoles = subscribeRoles(roles => {
const xoObjects = map(sortBy(roles, 'name'), role => ({...role, type: 'role'}))
subscriber({xoObjects})
})
return unsubscribeRoles
}, { placeholder: _('selectRole') })
// ===================================================================
export const SelectRemote = makeSubscriptionSelect(subscriber => {
const unsubscribeRemotes = subscribeRemotes(remotes => {
const xoObjects = groupBy(
map(sortBy(remotes, 'name'), remote => {
remote = {...remote, ...parseRemote(remote.url)}
return { id: remote.id, type: 'remote', value: remote }
}),
remote => remote.value.type
)
subscriber({
xoObjects,
xoContainers: map(xoObjects, (remote, type) => ({
id: type,
label: type
}))
})
})
return unsubscribeRemotes
}, { placeholder: _('selectRemotes') })
// ===================================================================
export const SelectResourceSet = makeSubscriptionSelect(subscriber => {
const unsubscribeResourceSets = subscribeResourceSets(resourceSets => {
const xoObjects = map(sortBy(resolveResourceSets(resourceSets), 'name'), resourceSet => ({...resourceSet, type: 'resourceSet'}))
subscriber({xoObjects})
})
return unsubscribeResourceSets
}, { placeholder: _('selectResourceSets') })
// ===================================================================
export class SelectResourceSetsVmTemplate extends Component {
get value () {
return this.refs.select.value
}
set value (value) {
this.refs.select.value = value
}
componentWillMount () {
this.componentWillUnmount = subscribeResourceSets(resourceSets => {
this.setState({
resourceSets: resolveResourceSets(resourceSets)
})
})
}
_getTemplates = createSelector(
() => this.props.resourceSet,
({ objectsByType }) => {
const { predicate } = this.props
const templates = objectsByType['VM-template']
return sortBy(predicate ? filter(templates, predicate) : templates, 'name_label')
}
)
render () {
return (
<GenericSelect
ref='select'
placeholder={_('selectResourceSetsVmTemplate')}
{...this.props}
xoObjects={this._getTemplates()}
/>
)
}
}
// ===================================================================
export class SelectResourceSetsSr extends Component {
get value () {
return this.refs.select.value
}
set value (value) {
this.refs.select.value = value
}
componentWillMount () {
this.componentWillUnmount = subscribeResourceSets(resourceSets => {
this.setState({
resourceSets: resolveResourceSets(resourceSets)
})
})
}
_getSrs = createSelector(
() => this.props.resourceSet,
({ objectsByType }) => {
const { predicate } = this.props
const srs = objectsByType['SR']
return sortBy(predicate ? filter(srs, predicate) : srs, 'name_label')
}
)
render () {
return (
<GenericSelect
ref='select'
placeholder={_('selectResourceSetsSr')}
{...this.props}
xoObjects={this._getSrs()}
/>
)
}
}
// ===================================================================
export class SelectResourceSetsVdi extends Component {
get value () {
return this.refs.select.value
}
set value (value) {
this.refs.select.value = value
}
componentWillMount () {
this.componentWillUnmount = subscribeResourceSets(resourceSets => {
this.setState({
resourceSets: resolveResourceSets(resourceSets)
})
})
}
_getObject (id) {
return getObject(store.getState(), id, true)
}
_getSrs = createSelector(
() => this.props.resourceSet,
({ objectsByType }) => {
const { srPredicate } = this.props
const srs = objectsByType['SR']
return srPredicate ? filter(srs, srPredicate) : srs
}
)
_getVdis = createSelector(
this._getSrs,
srs => sortBy(map(flatten(map(srs, sr => sr.VDIs)), this._getObject), 'name_label')
)
render () {
return (
<GenericSelect
ref='select'
placeholder={_('selectResourceSetsVdi')}
{...this.props}
xoObjects={this._getVdis()}
/>
)
}
}
// ===================================================================
export class SelectResourceSetsNetwork extends Component {
get value () {
return this.refs.select.value
}
set value (value) {
this.refs.select.value = value
}
componentWillMount () {
this.componentWillUnmount = subscribeResourceSets(resourceSets => {
this.setState({
resourceSets: resolveResourceSets(resourceSets)
})
})
}
_getNetworks = createSelector(
() => this.props.resourceSet,
({ objectsByType }) => {
const { predicate } = this.props
const networks = objectsByType['network']
return sortBy(predicate ? filter(networks, predicate) : networks, 'name_label')
}
)
render () {
return (
<GenericSelect
ref='select'
placeholder={_('selectResourceSetsNetwork')}
{...this.props}
xoObjects={this._getNetworks()}
/>
)
}
}
// ===================================================================
export class SelectSshKey extends Component {
get value () {
return this.refs.select.value
}
set value (value) {
this.refs.select.value = value
}
componentWillMount () {
this.componentWillUnmount = subscribeCurrentUser(user => {
this.setState({
sshKeys: user && user.preferences && map(user.preferences.sshKeys, (key, id) => ({
id,
label: key.title,
type: 'sshKey'
}))
})
})
}
render () {
return (
<GenericSelect
ref='select'
placeholder={_('selectSshKey')}
{...this.props}
xoObjects={this.state.sshKeys || []}
/>
)
}
}
// ===================================================================
export const SelectIp = makeSubscriptionSelect(subscriber => {
const unsubscribeIpPools = subscribeIpPools(ipPools => {
const sortedIpPools = sortBy(ipPools, 'name')
const xoObjects = mapValues(
groupBy(sortedIpPools, 'id'),
ipPools => map(ipPools[0].addresses, (address, ip) => ({
...address,
id: ip,
label: ip,
type: 'ipAddress',
used: !isEmpty(address.vifs)
}))
)
const xoContainers = map(sortedIpPools, ipPool => ({
...ipPool,
type: 'ipPool'
}))
subscriber({ xoObjects, xoContainers })
})
return unsubscribeIpPools
}, { placeholder: _('selectIp') })

475
src/common/selectors.js Normal file
View File

@@ -0,0 +1,475 @@
import checkPermissions from 'xo-acl-resolver'
import filter from 'lodash/filter'
import find from 'lodash/find'
import forEach from 'lodash/forEach'
import groupBy from 'lodash/groupBy'
import isArray from 'lodash/isArray'
import isArrayLike from 'lodash/isArrayLike'
import isFunction from 'lodash/isFunction'
import keys from 'lodash/keys'
import map from 'lodash/map'
import orderBy from 'lodash/orderBy'
import pickBy from 'lodash/pickBy'
import size from 'lodash/size'
import slice from 'lodash/slice'
import { createSelector as create } from 'reselect'
import invoke from './invoke'
import shallowEqual from './shallow-equal'
import { EMPTY_ARRAY, EMPTY_OBJECT } from './utils'
// ===================================================================
export {
// That's usually the name we want to import.
createSelector,
// But selectors.create is nice too :)
createSelector as create
} from 'reselect'
// -------------------------------------------------------------------
// Wraps a function which returns a collection to returns the previous
// result if the collection has not really changed (ie still has the
// same items).
//
// Use case: in connect, to avoid rerendering a component where the
// objects are still the same.
const _createCollectionWrapper = selector => {
let cache
return (...args) => {
const value = selector(...args)
if (!shallowEqual(value, cache)) {
cache = value
}
return cache
}
}
export { _createCollectionWrapper as createCollectionWrapper }
const _SELECTOR_PLACEHOLDER = Symbol('selector placeholder')
// Experimental!
//
// Similar to reselect's createSelector() but inputs can be either
// selectors or plain values.
//
// To pass a function as a plain value, simply wrap it with an array.
const _create2 = (...inputs) => {
const resultFn = inputs.pop()
if (inputs.length === 1 && isArray(inputs[0])) {
inputs = inputs[0]
}
const n = inputs.length
const inputSelectors = []
for (let i = 0; i < n; ++i) {
const input = inputs[i]
if (isFunction(input)) {
inputSelectors.push(input)
inputs[i] = _SELECTOR_PLACEHOLDER
} else if (isArray(input) && input.length === 1) {
inputs[i] = input[0]
}
}
if (!inputSelectors.length) {
throw new Error('no input selectors')
}
return create(inputSelectors, function () {
const args = new Array(n)
for (let i = 0, j = 0; i < n; ++i) {
const input = inputs[i]
args[i] = input === _SELECTOR_PLACEHOLDER
? arguments[j++]
: input
}
return resultFn.apply(this, args)
})
}
// ===================================================================
// Generic selector creators.
export const createCounter = (collection, predicate) =>
_create2(
collection,
predicate,
(collection, predicate) => {
if (!predicate) {
return size(collection)
}
let count = 0
forEach(collection, item => {
if (predicate(item)) {
++count
}
})
return count
}
)
// Creates an object selector from an object selector and a properties
// selector.
//
// Should only be used with a reasonable number of properties.
export const createPicker = (object, props) =>
_createCollectionWrapper(
_create2(
object, props,
(object, props) => {
const values = {}
forEach(props, prop => {
const value = object[prop]
if (value) {
values[prop] = value
}
})
return values
}
)
)
// Special cases:
// - predicate == null → no filtering
// - predicate === false → everything is filtered out
export const createFilter = (collection, predicate) =>
_createCollectionWrapper(
_create2(
collection,
predicate,
(collection, predicate) => predicate === false
? (isArrayLike(collection) ? EMPTY_ARRAY : EMPTY_OBJECT)
: predicate
? (isArrayLike(collection) ? filter : pickBy)(collection, predicate)
: collection
)
)
export const createFinder = (collection, predicate) =>
_create2(
collection,
predicate,
find
)
export const createGroupBy = (collection, getter) =>
_create2(
collection,
getter,
groupBy
)
export const createPager = (array, page, n = 25) => _createCollectionWrapper(
_create2(
array,
page,
n,
(array, page, n) => {
const start = (page - 1) * n
return slice(array, start, start + n)
}
)
)
export const createSort = (
collection,
getter = 'name_label',
order = 'asc'
) => _create2(collection, getter, order, orderBy)
export const createTop = (collection, iteratee, n) =>
_createCollectionWrapper(
_create2(
collection,
iteratee,
n,
(objects, iteratee, n) => {
let results = orderBy(objects, iteratee, 'desc')
if (n < results.length) {
results.length = n
}
return results
}
)
)
// ===================================================================
// Root-ish selectors (no dependencies).
export const areObjectsFetched = state => state.objects.fetched
const _getId = (state, { routeParams, id }) => routeParams
? routeParams.id
: id
export const getLang = state => state.lang
export const getStatus = state => state.status
export const getUser = state => state.user
const _getPermissionsPredicate = invoke(() => {
const getPredicate = create(
state => state.permissions,
state => state.objects,
(permissions, objects) => {
objects = objects.all
const getObject = id => (objects[id] || EMPTY_OBJECT)
return id => checkPermissions(permissions, getObject, id.id || id, 'view')
}
)
return state => {
const user = getUser(state)
if (!user) {
return false
}
if (user.permission === 'admin') {
return // No predicate means no filtering.
}
return getPredicate(state)
}
})
export const isAdmin = (...args) => {
const user = getUser(...args)
return user && user.permission === 'admin'
}
// ===================================================================
// Common selector creators.
// Creates an object selector from an id selector.
export const createGetObject = (idSelector = _getId) =>
(state, props, useResourceSet) => {
const object = state.objects.all[idSelector(state, props)]
if (!object) {
return
}
if (useResourceSet) {
return object
}
const predicate = _getPermissionsPredicate(state)
if (!predicate) {
if (predicate == null) {
return object // no filtering
}
// predicate is false.
return
}
if (predicate(object)) {
return object
}
}
// Specialized createSort() configured for a given type.
export const createSortForType = invoke(() => {
const iterateesByType = {
message: message => message.time,
PIF: pif => pif.device,
pool: pool => pool.name_label,
pool_patch: patch => patch.name,
tag: tag => tag,
VBD: vbd => vbd.position,
'VDI-snapshot': snapshot => snapshot.snapshot_time,
'VM-snapshot': snapshot => snapshot.snapshot_time
}
const defaultIteratees = [
object => object.$pool,
object => object.name_label
]
const getIteratees = type => iterateesByType[type] || defaultIteratees
const ordersByType = {
message: 'desc',
'VDI-snapshot': 'desc',
'VM-snapshot': 'desc'
}
const getOrders = type => ordersByType[type]
const autoSelector = (type, fn) => isFunction(type)
? (state, props) => fn(type(state, props))
: [ fn(type) ]
return (type, collection) => createSort(
collection,
autoSelector(type, getIteratees),
autoSelector(type, getOrders),
)
})
// Add utility methods to a collection selector.
const _extendCollectionSelector = (selector, objectsType) => {
// Terminal methods.
const _addCount = selector => {
selector.count = predicate => createCounter(selector, predicate)
return selector
}
_addCount(selector)
const _addGroupBy = selector => {
selector.groupBy = getter => createGroupBy(selector, getter)
return selector
}
_addGroupBy(selector)
const _addFind = selector => {
selector.find = predicate => createFinder(selector, predicate)
return selector
}
_addFind(selector)
// groupBy can be chained.
const _addSort = selector => {
// TODO: maybe memoize when no idsSelector.
selector.sort = () => _addGroupBy(createSortForType(objectsType, selector))
return selector
}
_addSort(selector)
// count, groupBy and sort can be chained.
const _addFilter = selector => {
selector.filter = predicate => _addCount(_addGroupBy(_addSort(
createFilter(selector, predicate)
)))
return selector
}
_addFilter(selector)
// filter, groupBy and sort can be chained.
selector.pick = idsSelector => _addFind(_addFilter(_addGroupBy(_addSort(
createPicker(selector, idsSelector)
))))
return selector
}
// Creates a collection selector which returns all objects of a given
// type.
//
// The selector as the following methods:
//
// - count: returns a selector which returns the number of objects
// - filter: returns a selector which returns the objects filtered by
// a predicate (count, groupBy and sort can be chained)
// - find: returns a selector which returns the first object matching
// a predicate
// - groupBy: returns a selector which returns the objects grouped by
// a value determined by a getter selector
// - pick: returns a selector which returns only the objects with given
// ids (filter, find, groupBy and sort can be chained)
// - sort: returns a selector which returns the objects appropriately
// sorted (groupBy can be chained)
export const createGetObjectsOfType = type => {
const getObjects = isFunction(type)
? (state, props) => state.objects.byType[type(state, props)] || EMPTY_OBJECT
: state => state.objects.byType[type] || EMPTY_OBJECT
return _extendCollectionSelector(createFilter(
getObjects,
_getPermissionsPredicate
), type)
}
export const createGetTags = collectionSelectors => {
if (!collectionSelectors) {
collectionSelectors = [
createGetObjectsOfType('host'),
createGetObjectsOfType('pool'),
createGetObjectsOfType('VM')
]
}
const getTags = create(
collectionSelectors,
(...collections) => {
const tags = {}
const addTag = tag => { tags[tag] = null }
const addItemTags = item => { forEach(item.tags, addTag) }
const addCollectionTags = collection => { forEach(collection, addItemTags) }
forEach(collections, addCollectionTags)
return keys(tags)
}
)
return _extendCollectionSelector(getTags, 'tag')
}
export const createGetObjectMessages = objectSelector =>
createGetObjectsOfType('message').filter(
create(
(...args) => objectSelector(...args).id,
id => message => message.$object === id
)
).sort()
// Example of use:
// import store from 'store'
// const object = getObject(store.getState(), objectId)
// ...
export const getObject = createGetObject((_, id) => id)
export const createDoesHostNeedRestart = hostSelector => {
// Returns the first patch of the host which requires it to be
// restarted.
const restartPoolPatch = createGetObjectsOfType('pool_patch').pick(
create(
createGetObjectsOfType('host_patch').pick(
(state, props) => {
const host = hostSelector(state, props)
return host && host.patches
}
).filter(create(
(state, props) => {
const host = hostSelector(state, props)
return host && host.startTime
},
startTime => patch => patch.time > startTime
)),
hostPatches => map(hostPatches, hostPatch => hostPatch.pool_patch)
)
).find([ ({ guidance }) => find(guidance, action =>
action === 'restartHost' || action === 'restartXapi'
) ])
return (state, props) => restartPoolPatch(state, props) !== undefined
}
export const createGetHostMetrics = hostSelector => _createCollectionWrapper(
create(
hostSelector,
hosts => {
const metrics = {
count: 0,
cpus: 0,
memoryTotal: 0,
memoryUsage: 0
}
forEach(hosts, host => {
metrics.count++
metrics.cpus += host.cpus.cores
metrics.memoryTotal += host.memory.size
metrics.memoryUsage += host.memory.usage
})
return metrics
}
)
)

View File

@@ -0,0 +1,42 @@
// Tests that two collections (arrays or objects) have strictly equals
// values (items or properties)
const shallowEqual = (c1, c2) => {
if (c1 === c2) {
return true
}
const type = typeof c1
if (type !== typeof c2) {
return false
}
if (type === 'array') {
const { length } = c1
if (length !== c2.length) {
return false
}
for (let i = 0; i < length; ++i) {
if (c1[i] !== c2[i]) {
return false
}
}
return true
}
let n = 0
for (const _ in c2) { // eslint-disable-line no-unused-vars
++n
}
for (const key in c1) {
if (c1[key] !== c2[key]) {
return false
}
--n
}
return !n
}
export { shallowEqual as default }

35
src/common/shortcuts.js Normal file
View File

@@ -0,0 +1,35 @@
import Component from 'base-component'
import forEach from 'lodash/forEach'
import React from 'react'
import remove from 'lodash/remove'
import { Shortcuts as ReactShortcuts } from 'react-shortcuts'
let enabled = true
const instances = []
const updateInstances = () => {
forEach(instances, instance => instance.forceUpdate())
}
export const enable = () => {
enabled = true
updateInstances()
}
export const disable = () => {
enabled = false
updateInstances()
}
export default class Shortcuts extends Component {
componentDidMount () {
instances.push(this)
}
componentWillUnmount () {
remove(instances, this)
}
render () {
return enabled ? <ReactShortcuts {...this.props} /> : null
}
}

View File

@@ -0,0 +1,19 @@
import React, { cloneElement } from 'react'
import propTypes from './prop-types'
const SINGLE_LINE_STYLE = { display: 'flex' }
const COL_STYLE = { marginTop: 'auto', marginBottom: 'auto' }
const SingleLineRow = propTypes({
className: propTypes.string
})(({
children,
className
}) => <div
className={`${className || ''} row`}
style={SINGLE_LINE_STYLE}
>
{React.Children.map(children, child => child && cloneElement(child, { style: COL_STYLE }))}
</div>)
export { SingleLineRow as default }

View File

@@ -0,0 +1,8 @@
.clickableColumn {
cursor: pointer;
}
.clickableColumn:hover {
color: #fff;
background-color: #96b8d1;
}

View File

@@ -0,0 +1,360 @@
import _ from 'intl'
import ceil from 'lodash/ceil'
import debounce from 'lodash/debounce'
import findIndex from 'lodash/findIndex'
import isEmpty from 'lodash/isEmpty'
import isFunction from 'lodash/isFunction'
import map from 'lodash/map'
import React from 'react'
import { Dropdown, MenuItem, Pagination } from 'react-bootstrap-4/lib'
import DropdownMenu from 'react-bootstrap-4/lib/DropdownMenu' // https://phabricator.babeljs.io/T6662 so Dropdown.Menu won't work like https://react-bootstrap.github.io/components.html#btn-dropdowns-custom
import DropdownToggle from 'react-bootstrap-4/lib/DropdownToggle' // https://phabricator.babeljs.io/T6662 so Dropdown.Toggle won't work https://react-bootstrap.github.io/components.html#btn-dropdowns-custom
import { Portal } from 'react-overlays'
import Component from '../base-component'
import Icon from '../icon'
import propTypes from '../prop-types'
import SingleLineRow from '../single-line-row'
import { BlockLink } from '../link'
import { Container, Col } from '../grid'
import { create as createMatcher } from '../complex-matcher'
import {
createCounter,
createFilter,
createPager,
createSelector,
createSort
} from '../selectors'
import styles from './index.css'
// ===================================================================
@propTypes({
filters: propTypes.object,
nFilteredItems: propTypes.number.isRequired,
nItems: propTypes.number.isRequired,
onChange: propTypes.func.isRequired
})
class TableFilter extends Component {
_cleanFilter = () => this._setFilter('')
_setFilter = filterValue => {
const { filter } = this.refs
filter.value = filterValue
filter.focus()
this.props.onChange(filterValue)
}
_onChange = event => {
this.props.onChange(event.target.value)
}
render () {
const { props } = this
return (
<div className='input-group'>
<span className='input-group-addon'>{props.nFilteredItems} / {props.nItems}</span>
{isEmpty(props.filters)
? <span className='input-group-addon'><Icon icon='search' /></span>
: <div className='input-group-btn'>
<Dropdown id='filter'>
<DropdownToggle bsStyle='info'>
<Icon icon='search' />
</DropdownToggle>
<DropdownMenu>
{map(props.filters, (filter, label) =>
<MenuItem key={label} onClick={() => this._setFilter(filter)}>
{_(label)}
</MenuItem>
)}
</DropdownMenu>
</Dropdown>
</div>}
<input
type='text'
ref='filter'
onChange={this._onChange}
className='form-control'
/>
<div className='input-group-btn'>
<button className='btn btn-secondary' onClick={this._cleanFilter}>
<Icon icon='clear-search' />
</button>
</div>
</div>
)
}
}
// ===================================================================
@propTypes({
columnId: propTypes.number.isRequired,
name: propTypes.any.isRequired,
sort: propTypes.func,
sortIcon: propTypes.string
})
class ColumnHead extends Component {
_sort = () => {
const { props } = this
props.sort(props.columnId)
}
render () {
const { name, sortIcon } = this.props
if (!this.props.sort) {
return <th>{name}</th>
}
let className = styles.clickableColumn
if (sortIcon === 'asc' || sortIcon === 'desc') {
className += ' bg-info'
}
return (
<th
className={className}
onClick={this._sort}
>
{name}
<span className='pull-xs-right'>
<Icon icon={sortIcon} />
</span>
</th>
)
}
}
// ===================================================================
const DEFAULT_ITEMS_PER_PAGE = 10
@propTypes({
defaultColumn: propTypes.number,
collection: propTypes.oneOfType([
propTypes.array,
propTypes.object
]).isRequired,
columns: propTypes.arrayOf(propTypes.shape({
default: propTypes.bool,
name: propTypes.node.isRequired,
itemRenderer: propTypes.func.isRequired,
sortCriteria: propTypes.oneOfType([
propTypes.func,
propTypes.string
]),
sortOrder: propTypes.string
})).isRequired,
filterContainer: propTypes.func,
filters: propTypes.object,
itemsPerPage: propTypes.number,
paginationContainer: propTypes.func,
rowLink: propTypes.oneOfType([
propTypes.func,
propTypes.string
]),
userData: propTypes.any
})
export default class SortedTable extends Component {
constructor (props) {
super(props)
let selectedColumn = props.defaultColumn
if (selectedColumn == null) {
selectedColumn = findIndex(props.columns, 'default')
if (selectedColumn === -1) {
selectedColumn = 0
}
}
this.state = {
selectedColumn,
itemsPerPage: props.itemsPerPage || DEFAULT_ITEMS_PER_PAGE
}
this._getSelectedColumn = () =>
this.props.columns[this.state.selectedColumn]
this._getTotalNumberOfItems = createCounter(
() => this.props.collection
)
this._getAllItems = createSort(
createFilter(
() => this.props.collection,
createSelector(
() => this.state.filter || '',
createMatcher
)
),
createSelector(
() => this._getSelectedColumn().sortCriteria,
() => this.props.userData,
(sortCriteria, userData) =>
(typeof sortCriteria === 'function')
? object => sortCriteria(object, userData)
: sortCriteria
),
() => this.state.sortOrder
)
this.state.activePage = 1
this._getVisibleItems = createPager(
this._getAllItems,
() => this.state.activePage,
this.state.itemsPerPage
)
}
componentWillMount () {
this.setState({
sortOrder: this.props.columns[this.state.selectedColumn].sortOrder === 'desc' ? 'desc' : 'asc'
})
}
componentDidMount () {
// Force one Portal refresh.
// Because Portal cannot see the container reference at first rendering.
if (this.props.paginationContainer) {
this.forceUpdate()
}
}
_sort = columnId => {
const { state } = this
let sortOrder
if (state.selectedColumn === columnId) {
sortOrder = state.sortOrder === 'desc'
? 'asc'
: 'desc'
} else {
sortOrder = this.props.columns[columnId].sortOrder === 'desc'
? 'desc'
: 'asc'
}
this.setState({
selectedColumn: columnId,
sortOrder
})
}
_onPageSelection = (_, event) => this.setState({
activePage: event.eventKey
})
_onFilterChange = debounce(filter => {
this.setState({
filter,
activePage: 1
})
}, 500)
render () {
const { props, state } = this
const {
paginationContainer,
filterContainer,
filters,
rowLink,
userData
} = props
const nFilteredItems = this._getAllItems().length
const paginationInstance = (
<Pagination
first
last
prev
next
ellipsis
boundaryLinks
maxButtons={10}
items={ceil(nFilteredItems / state.itemsPerPage)}
activePage={this.state.activePage}
onSelect={this._onPageSelection}
/>
)
const filterInstance = (
<TableFilter
filters={filters}
nFilteredItems={nFilteredItems}
nItems={this._getTotalNumberOfItems()}
onChange={this._onFilterChange}
/>
)
return (
<div>
<table className='table'>
<thead className='thead-default'>
<tr>
{map(props.columns, (column, key) => (
<ColumnHead
columnId={key}
key={key}
name={column.name}
sort={column.sortCriteria && this._sort}
sortIcon={state.selectedColumn === key ? state.sortOrder : 'sort'}
/>
))}
</tr>
</thead>
<tbody>
{map(this._getVisibleItems(), (item, i) => {
const columns = map(props.columns, (column, key) => (
<td key={key}>
{column.itemRenderer(item, userData)}
</td>
))
const { id = i } = item
return rowLink
? <BlockLink
key={id}
tagName='tr'
to={isFunction(rowLink) ? rowLink(item, userData) : rowLink}
>{columns}</BlockLink>
: <tr key={id}>{columns}</tr>
})}
</tbody>
</table>
{(!paginationContainer || !filterContainer) && (
<Container>
<SingleLineRow>
<Col mediumSize={8}>
{paginationContainer
? (
// Rebuild container function to refresh Portal component.
<Portal container={() => paginationContainer()}>
{paginationInstance}
</Portal>
) : paginationInstance
}
</Col>
<Col mediumSize={4}>
{filterContainer
? (
<Portal container={() => filterContainer()}>
{filterInstance}
</Portal>
) : filterInstance
}
</Col>
</SingleLineRow>
</Container>
)}
</div>
)
}
}

View File

@@ -0,0 +1,52 @@
import isFunction from 'lodash/isFunction'
// ===================================================================
const createAction = (() => {
const { defineProperty } = Object
const noop = function () {
if (arguments.length) {
throw new Error('this action expects no payload!')
}
}
return (type, payloadCreator = noop) => {
const createActionObject = payload => {
// Thunks
if (isFunction(payload)) {
return payload
}
return payload === undefined
? { type }
: { type, payload }
}
return defineProperty(
(...args) => createActionObject(payloadCreator(...args)),
'toString',
{ value: () => type }
)
}
})()
// ===================================================================
export const selectLang = createAction('SELECT_LANG', lang => lang)
// ===================================================================
export const connected = createAction('CONNECTED')
export const disconnected = createAction('DISCONNECTED')
export const updateObjects = createAction('UPDATE_OBJECTS', updates => updates)
export const updatePermissions = createAction('UPDATE_PERMISSIONS', permissions => permissions)
export const signedIn = createAction('SIGNED_IN', user => user)
export const signedOut = createAction('SIGNED_OUT')
export const xoaUpdaterState = createAction('XOA_UPDATER_STATE', state => state)
export const xoaTrialState = createAction('XOA_TRIAL_STATE', state => state)
export const xoaUpdaterLog = createAction('XOA_UPDATER_LOG', log => log)
export const xoaRegisterState = createAction('XOA_REGISTER_STATE', registration => registration)
export const xoaConfiguration = createAction('XOA_CONFIGURATION', configuration => configuration)

View File

@@ -0,0 +1,13 @@
import DockMonitor from 'redux-devtools-dock-monitor'
import LogMonitor from 'redux-devtools-log-monitor'
import React from 'react'
import { createDevTools } from 'redux-devtools'
export default createDevTools(
<DockMonitor
changePositionKey='ctrl-q'
toggleVisibilityKey='ctrl-h'
>
<LogMonitor />
</DockMonitor>
)

View File

@@ -0,0 +1 @@
module.exports = false // process.env.NODE_ENV !== 'production' && require('./dev-tools.dev')

32
src/common/store/index.js Normal file
View File

@@ -0,0 +1,32 @@
import reduxThunk from 'redux-thunk'
import {
applyMiddleware,
combineReducers,
compose,
createStore
} from 'redux'
import { connectStore as connectXo } from '../xo'
import DevTools from './dev-tools'
import reducer from './reducer'
// ===================================================================
const enhancers = [
applyMiddleware(reduxThunk)
]
DevTools && enhancers.push(DevTools.instrument())
const store = createStore(
combineReducers(reducer),
compose.apply(null, enhancers)
)
connectXo(store)
if (process.env.XOA_PLAN < 5) {
require('xoa-updater').connectStore(store)
}
export default store

152
src/common/store/reducer.js Normal file
View File

@@ -0,0 +1,152 @@
import cookies from 'cookies-js'
import invoke from '../invoke'
import * as actions from './actions'
// ===================================================================
const createAsyncHandler = ({ error, next }) => (state, payload, action) => {
if (action.error) {
if (error) {
return error(state, payload, action)
}
} else {
if (next) {
return next(state, payload, action)
}
}
return state
}
// Action handlers are reducers but bound to a specific action.
const combineActionHandlers = invoke(
Object.hasOwnProperty,
obj => {
for (const prop in obj) {
return prop
}
},
(has, firstProp) => (initialState, handlers) => {
let n = 0
for (const actionType in handlers) {
if (has.call(handlers, actionType)) {
if (actionType === 'undefined') {
throw new Error('invalid action type: undefined')
}
++n
const handler = handlers[actionType]
if (typeof handler === 'object') {
handlers[actionType] = createAsyncHandler(handler)
}
}
}
if (!n) {
throw new Error('no action handlers declared')
}
// Optimization for this special case.
if (n === 1) {
const actionType = firstProp(handlers)
const handler = handlers[actionType]
return (state = initialState, action) => (
action.type === actionType
? handler(state, action.payload, action)
: state
)
}
return (state = initialState, action) => {
const handler = handlers[action.type]
return handler
? handler(state, action.payload, action)
: state
}
}
)
// ===================================================================
export default {
lang: combineActionHandlers(cookies.get('lang') || 'en', {
[actions.selectLang]: (_, lang) => {
cookies.set('lang', lang)
return lang
}
}),
permissions: combineActionHandlers({}, {
[actions.updatePermissions]: (_, permissions) => permissions
}),
objects: combineActionHandlers({
all: {}, // Mutable for performance!
byType: {}
}, {
[actions.updateObjects]: ({ all, byType: prevByType }, updates) => {
const byType = { ...prevByType }
const get = type => {
const curr = byType[type]
const prev = prevByType[type]
return curr === prev
? (byType[type] = { ...prev })
: curr
}
for (const id in updates) {
const object = updates[id]
if (object) {
all[id] = object
get(object.type)[id] = object
} else {
const previous = all[id]
if (previous) {
delete all[id]
delete get(previous.type)[id]
}
}
}
return { all, byType, fetched: true }
}
}),
user: combineActionHandlers(null, {
[actions.signedIn]: {
next: (_, user) => user
}
}),
status: combineActionHandlers('disconnected', {
[actions.connected]: () => 'connected',
[actions.disconnected]: () => 'disconnected'
}),
xoaUpdaterState: combineActionHandlers('disconnected', {
[actions.xoaUpdaterState]: (_, state) => state
}),
xoaTrialState: combineActionHandlers({}, {
[actions.xoaTrialState]: (_, state) => state
}),
xoaUpdaterLog: combineActionHandlers([], {
[actions.xoaUpdaterLog]: (_, log) => log
}),
xoaRegisterState: combineActionHandlers({state: '?'}, {
[actions.xoaRegisterState]: (_, registration) => registration
}),
xoaConfiguration: combineActionHandlers({proxyHost: '', proxyPort: '', proxyUser: ''}, { // defined values for controlled inputs
[actions.xoaConfiguration]: (_, configuration) => {
delete configuration.password
return configuration
}
})
}

45
src/common/tab-button.js Normal file
View File

@@ -0,0 +1,45 @@
import React from 'react'
import _ from './intl'
import ActionButton from './action-button'
import Icon from './icon'
import Link from './link'
const STYLE = {
marginBottom: '1em',
marginLeft: '1em'
}
const TabButton = ({
labelId,
...props
}) => (
<ActionButton
{...props}
size='large'
style={STYLE}
><span className='hidden-md-down'>{_(labelId)}</span></ActionButton>
)
export { TabButton as default }
export const TabButtonLink = ({
labelId,
icon,
...props
}) => (
<Link
{...props}
className='btn btn-lg btn-primary'
style={STYLE}
>
<span className='hidden-md-down'>
{icon && (
<span>
<Icon icon={icon} />
{' '}
</span>
)}
{_(labelId)}
</span>
</Link>
)

134
src/common/tags.js Normal file
View File

@@ -0,0 +1,134 @@
import filter from 'lodash/filter'
import includes from 'lodash/includes'
import map from 'lodash/map'
import React from 'react'
import Component from './base-component'
import Icon from './icon'
import propTypes from './prop-types'
const INPUT_STYLE = {
margin: '2px',
maxWidth: '4em'
}
const TAG_STYLE = {
backgroundColor: '#2598d9',
borderRadius: '0.5em',
color: 'white',
fontSize: '0.6em',
margin: '0.2em',
marginTop: '-0.1em',
padding: '0.3em',
verticalAlign: 'middle'
}
const ADD_TAG_STYLE = {
cursor: 'pointer',
fontSize: '0.8em',
marginLeft: '0.2em'
}
const REMOVE_TAG_STYLE = {
cursor: 'pointer'
}
@propTypes({
labels: propTypes.arrayOf(React.PropTypes.string).isRequired,
onChange: propTypes.func,
onDelete: propTypes.func,
onAdd: propTypes.func
})
export default class Tags extends Component {
componentWillMount () {
this.setState({editing: false})
}
_startEdit = () => {
this.setState({ editing: true })
}
_stopEdit = () => {
this.setState({ editing: false })
}
_addTag = newTag => {
const { labels, onAdd, onChange } = this.props
if (!includes(labels, newTag)) {
onAdd && onAdd(newTag)
onChange && onChange([ ...labels, newTag ])
}
}
_deleteTag = tag => {
const { onChange, onDelete } = this.props
onDelete && onDelete(tag)
onChange && onChange(filter(this.props.labels, t => t !== tag))
}
_onKeyDown = event => {
const { keyCode, target } = event
if (keyCode === 13) {
if (target.value) {
this._addTag(target.value)
target.value = ''
}
} else if (keyCode === 27) {
this._stopEdit()
} else {
return
}
event.preventDefault()
}
render () {
const {
labels,
onAdd,
onChange,
onDelete
} = this.props
const deleteTag = (onDelete || onChange) && this._deleteTag
return (
<span className='form-group' style={{ color: '#999' }}>
<Icon icon='tags' />
{' '}
<span>
{map(labels.sort(), (label, index) =>
<Tag label={label} onDelete={deleteTag} key={index} />
)}
</span>
{(onAdd || onChange) && !this.state.editing
? <span onClick={this._startEdit} style={ADD_TAG_STYLE}>
<Icon icon='add-tag' />
</span>
: <span>
<input
type='text'
autoFocus
style={INPUT_STYLE}
onKeyDown={this._onKeyDown}
onBlur={this._stopEdit}
/>
</span>
}
</span>
)
}
}
export const Tag = ({ label, onDelete }) => (
<span style={TAG_STYLE}>
{label}{' '}
{onDelete
? <span onClick={onDelete && (() => onDelete(label))} style={REMOVE_TAG_STYLE}>
<Icon icon='remove-tag' />
</span>
: []
}
</span>
)
Tag.propTypes = {
label: React.PropTypes.string.isRequired
}

View File

@@ -0,0 +1,106 @@
import ActionButton from 'action-button'
import map from 'lodash/map'
import moment from 'moment-timezone'
import React from 'react'
import _ from './intl'
import Component from './base-component'
import propTypes from './prop-types'
import { getXoServerTimezone } from './xo'
import { Select } from './form'
const XO_SERVER_TIMEZONE = 'xo-server'
@propTypes({
defaultValue: propTypes.string,
onChange: propTypes.func.isRequired,
value: propTypes.string
})
export default class TimezonePicker extends Component {
constructor (props) {
super(props)
this.state.options = map(moment.tz.names(), value => ({ label: value, value }))
}
get value () {
const value = this.refs.select.value
return (value === XO_SERVER_TIMEZONE) ? null : value
}
set value (value) {
this.refs.select.value = value || XO_SERVER_TIMEZONE
}
_updateTimezone (value) {
this.props.onChange(value)
}
_handleChange = option => {
return this._updateTimezone(
!option || option.value === XO_SERVER_TIMEZONE
? null
: option.value
)
}
_useServerTime = () => {
this._updateTimezone(null)
}
_useLocalTime = () => {
this._updateTimezone(moment.tz.guess())
}
componentWillMount () {
// Use local timezone (Web browser) if no default value.
if (this.props.value === undefined) {
this._useLocalTime()
}
getXoServerTimezone.then(serverTimezone => {
this.setState({
options: [{
label: _('serverTimezoneOption', {
value: serverTimezone
}),
value: XO_SERVER_TIMEZONE
}].concat(this.state.options),
serverTimezone
})
})
}
render () {
const { props, state } = this
return (
<div>
<div className='alert alert-info' role='alert'>
{_('timezonePickerServerValue')} <strong>{state.serverTimezone}</strong>
</div>
<Select
className='m-b-1'
defaultValue={props.defaultValue}
onChange={this._handleChange}
options={state.options}
placeholder={_('selectTimezone')}
ref='select'
value={props.value || XO_SERVER_TIMEZONE}
/>
<div className='pull-right'>
<ActionButton
btnStyle='primary'
className='m-r-1'
handler={this._useServerTime}
icon='time'
>
{_('timezonePickerUseServerTime')}
</ActionButton>
<ActionButton
btnStyle='secondary'
handler={this._useLocalTime}
icon='time'
>
{_('timezonePickerUseLocalTime')}
</ActionButton>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,287 @@
// Source: https://github.com/wwayne/react-tooltip/blob/master/src/utils/getPosition.js
/**
* Calculate the position of tooltip
*
* @params
* - `e` {Event} the event of current mouse
* - `target` {Element} the currentTarget of the event
* - `node` {DOM} the react-tooltip object
* - `place` {String} top / right / bottom / left
* - `effect` {String} float / solid
* - `offset` {Object} the offset to default position
*
* @return {Object
* - `isNewState` {Bool} required
* - `newState` {Object}
* - `position` {OBject} {left: {Number}, top: {Number}}
*/
export default function (e, target, node, place, effect, offset) {
const tipWidth = node.clientWidth
const tipHeight = node.clientHeight
const {mouseX, mouseY} = getCurrentOffset(e, target, effect)
const defaultOffset = getDefaultPosition(effect, target.clientWidth, target.clientHeight, tipWidth, tipHeight)
const {extraOffsetX, extraOffsetY} = calculateOffset(offset)
const windowWidth = window.innerWidth
const windowHeight = window.innerHeight
const {parentTop, parentLeft} = getParent(target)
// Get the edge offset of the tooltip
const getTipOffsetLeft = (place) => {
const offsetX = defaultOffset[place].l
return mouseX + offsetX + extraOffsetX
}
const getTipOffsetRight = (place) => {
const offsetX = defaultOffset[place].r
return mouseX + offsetX + extraOffsetX
}
const getTipOffsetTop = (place) => {
const offsetY = defaultOffset[place].t
return mouseY + offsetY + extraOffsetY
}
const getTipOffsetBottom = (place) => {
const offsetY = defaultOffset[place].b
return mouseY + offsetY + extraOffsetY
}
// Judge if the tooltip has over the window(screen)
const outsideVertical = () => {
let result = false
let newPlace
if (getTipOffsetTop('left') < 0 &&
getTipOffsetBottom('left') <= windowHeight &&
getTipOffsetBottom('bottom') <= windowHeight) {
result = true
newPlace = 'bottom'
} else if (getTipOffsetBottom('left') > windowHeight &&
getTipOffsetTop('left') >= 0 &&
getTipOffsetTop('top') >= 0) {
result = true
newPlace = 'top'
}
return {result, newPlace}
}
const outsideLeft = () => {
let {result, newPlace} = outsideVertical() // Deal with vertical as first priority
if (result && outsideHorizontal().result) {
return {result: false} // No need to change, if change to vertical will out of space
}
if (!result && getTipOffsetLeft('left') < 0 && getTipOffsetRight('right') <= windowWidth) {
result = true // If vertical ok, but let out of side and right won't out of side
newPlace = 'right'
}
return {result, newPlace}
}
const outsideRight = () => {
let {result, newPlace} = outsideVertical()
if (result && outsideHorizontal().result) {
return {result: false} // No need to change, if change to vertical will out of space
}
if (!result && getTipOffsetRight('right') > windowWidth && getTipOffsetLeft('left') >= 0) {
result = true
newPlace = 'left'
}
return {result, newPlace}
}
const outsideHorizontal = () => {
let result = false
let newPlace
if (getTipOffsetLeft('top') < 0 &&
getTipOffsetRight('top') <= windowWidth &&
getTipOffsetRight('right') <= windowWidth) {
result = true
newPlace = 'right'
} else if (getTipOffsetRight('top') > windowWidth &&
getTipOffsetLeft('top') >= 0 &&
getTipOffsetLeft('left') >= 0) {
result = true
newPlace = 'left'
}
return {result, newPlace}
}
const outsideTop = () => {
let {result, newPlace} = outsideHorizontal()
if (result && outsideVertical().result) {
return {result: false}
}
if (!result && getTipOffsetTop('top') < 0 && getTipOffsetBottom('bottom') <= windowHeight) {
result = true
newPlace = 'bottom'
}
return {result, newPlace}
}
const outsideBottom = () => {
let {result, newPlace} = outsideHorizontal()
if (result && outsideVertical().result) {
return {result: false}
}
if (!result && getTipOffsetBottom('bottom') > windowHeight && getTipOffsetTop('top') >= 0) {
result = true
newPlace = 'top'
}
return {result, newPlace}
}
// Return new state to change the placement to the reverse if possible
const outsideLeftResult = outsideLeft()
const outsideRightResult = outsideRight()
const outsideTopResult = outsideTop()
const outsideBottomResult = outsideBottom()
if (place === 'left' && outsideLeftResult.result) {
return {
isNewState: true,
newState: {place: outsideLeftResult.newPlace}
}
} else if (place === 'right' && outsideRightResult.result) {
return {
isNewState: true,
newState: {place: outsideRightResult.newPlace}
}
} else if (place === 'top' && outsideTopResult.result) {
return {
isNewState: true,
newState: {place: outsideTopResult.newPlace}
}
} else if (place === 'bottom' && outsideBottomResult.result) {
return {
isNewState: true,
newState: {place: outsideBottomResult.newPlace}
}
}
// Return tooltip offset position
return {
isNewState: false,
position: {
left: getTipOffsetLeft(place) - parentLeft,
top: getTipOffsetTop(place) - parentTop
}
}
}
// Get current mouse offset
const getCurrentOffset = (e, currentTarget, effect) => {
const boundingClientRect = currentTarget.getBoundingClientRect()
const targetTop = boundingClientRect.top
const targetLeft = boundingClientRect.left
const targetWidth = currentTarget.clientWidth
const targetHeight = currentTarget.clientHeight
if (effect === 'float') {
return {
mouseX: e.clientX,
mouseY: e.clientY
}
}
return {
mouseX: targetLeft + (targetWidth / 2),
mouseY: targetTop + (targetHeight / 2)
}
}
// List all possibility of tooltip final offset
// This is useful in judging if it is necessary for tooltip to switch position when out of window
const getDefaultPosition = (effect, targetWidth, targetHeight, tipWidth, tipHeight) => {
let top
let right
let bottom
let left
const disToMouse = 3
const triangleHeight = 2
const cursorHeight = 12 // Optimize for float bottom only, cause the cursor will hide the tooltip
if (effect === 'float') {
top = {
l: -(tipWidth / 2),
r: tipWidth / 2,
t: -(tipHeight + disToMouse + triangleHeight),
b: -disToMouse
}
bottom = {
l: -(tipWidth / 2),
r: tipWidth / 2,
t: disToMouse + cursorHeight,
b: tipHeight + disToMouse + triangleHeight + cursorHeight
}
left = {
l: -(tipWidth + disToMouse + triangleHeight),
r: -disToMouse,
t: -(tipHeight / 2),
b: tipHeight / 2
}
right = {
l: disToMouse,
r: tipWidth + disToMouse + triangleHeight,
t: -(tipHeight / 2),
b: tipHeight / 2
}
} else if (effect === 'solid') {
top = {
l: -(tipWidth / 2),
r: tipWidth / 2,
t: -(targetHeight / 2 + tipHeight + triangleHeight),
b: -(targetHeight / 2)
}
bottom = {
l: -(tipWidth / 2),
r: tipWidth / 2,
t: targetHeight / 2,
b: targetHeight / 2 + tipHeight + triangleHeight
}
left = {
l: -(tipWidth + targetWidth / 2 + triangleHeight),
r: -(targetWidth / 2),
t: -(tipHeight / 2),
b: tipHeight / 2
}
right = {
l: targetWidth / 2,
r: tipWidth + targetWidth / 2 + triangleHeight,
t: -(tipHeight / 2),
b: tipHeight / 2
}
}
return {top, bottom, left, right}
}
// Consider additional offset into position calculation
const calculateOffset = (offset) => {
let extraOffsetX = 0
let extraOffsetY = 0
if (Object.prototype.toString.apply(offset) === '[object String]') {
offset = JSON.parse(offset.toString().replace(/'/g, '"'))
}
for (let key in offset) {
if (key === 'top') {
extraOffsetY -= parseInt(offset[key], 10)
} else if (key === 'bottom') {
extraOffsetY += parseInt(offset[key], 10)
} else if (key === 'left') {
extraOffsetX -= parseInt(offset[key], 10)
} else if (key === 'right') {
extraOffsetX += parseInt(offset[key], 10)
}
}
return {extraOffsetX, extraOffsetY}
}
// Get the offset of the parent elements
const getParent = (currentTarget) => {
let currentParent = currentTarget
while (currentParent) {
if (currentParent.style.transform.length > 0) break
currentParent = currentParent.parentElement
}
const parentTop = currentParent && currentParent.getBoundingClientRect().top || 0
const parentLeft = currentParent && currentParent.getBoundingClientRect().left || 0
return {parentTop, parentLeft}
}

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