您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451
  1. /**
  2. *
  3. * @fileOverview Chooser (vm list) singleton. Provides vboxChooser
  4. * @author Ian Moore (imoore76 at yahoo dot com)
  5. * @version $Id: chooser.js 591 2015-04-11 22:40:47Z imoore76 $
  6. * @copyright Copyright (C) 2010-2015 Ian Moore (imoore76 at yahoo dot com)
  7. *
  8. */
  9. /**
  10. * Chooser selection mode constants
  11. */
  12. var vboxSelectionModeNone = 0;
  13. var vboxSelectionModeSingleVM = 1;
  14. var vboxSelectionModeMultiVM = 2;
  15. var vboxSelectionModeSingleGroup = 3;
  16. /**
  17. * @namespace vboxChooser
  18. *
  19. * Draws machine selection chooser and controls selection list
  20. * @see js/eventlistener.js
  21. */
  22. var vboxChooser = {
  23. // VM list
  24. vms : {},
  25. // VM tool tip
  26. _vmToolTip : '<nobr>%1<br></nobr><nobr>%2 since %3</nobr><br><nobr>Session %4</nobr>',
  27. // Anchor element
  28. _anchorid : null,
  29. _anchor : null,
  30. /* Internal list of all unique selected items */
  31. _selectedList : [],
  32. /* List of unique selected VMs */
  33. selectedVMs : [],
  34. /* Holds group definitions */
  35. _groupDefs : [],
  36. /* selection mode can be
  37. var vboxSelectionModeNone = 0,
  38. var vboxSelectionModeSingleVM = 1,
  39. var vboxSelectionModeMultiVM = 2,
  40. var vboxSelectionModeSingleGroup = 3,
  41. */
  42. selectionMode : vboxSelectionModeNone,
  43. /* Check phpVirtualBox version and VirtualBox
  44. * version compatibility.
  45. */
  46. _versionChecked : false,
  47. /* Some items are not editable while vmGroup
  48. * definitions are being written
  49. */
  50. _editable : true,
  51. /* Context menus */
  52. _vmContextMenuObj : null,
  53. _vmGroupContextMenuObj : null,
  54. /* Holds history of showing only single groups */
  55. _showOnlyGroupHistory : [],
  56. /* Group definition extra value key */
  57. _groupDefinitionKey : '',
  58. /* Whether chooser is in compact mode or not */
  59. _compact : false,
  60. /**
  61. * Set anchor id to draw to
  62. */
  63. setAnchorId : function(aid) {
  64. vboxChooser._anchorid = aid;
  65. vboxChooser._anchor = $('#'+aid);
  66. vboxChooser._anchor.html("<div id='vboxChooserSpinner' style='text-align: center'><div><img src='images/spinner.gif' /></div></div>");
  67. vboxChooser._anchor.hover(function(){
  68. $(this).addClass('vboxChooserDropTargetHoverRoot');
  69. },function() {
  70. $(this).removeClass('vboxChooserDropTargetHoverRoot');
  71. });
  72. $(window).resize(function(){
  73. // Get anchor id and add / remove class
  74. var w = parseInt($(vboxChooser._anchor).innerWidth());
  75. if(w < 120) {
  76. $(vboxChooser._anchor).addClass('vboxChooserMini');
  77. vboxChooser._compact = true;
  78. } else {
  79. $(vboxChooser._anchor).removeClass('vboxChooserMini');
  80. vboxChooser._compact = false;
  81. }
  82. vboxChooser._resizeElements(true);
  83. });
  84. },
  85. /**
  86. * Set context menus
  87. *
  88. */
  89. setContextMenu : function(target, menuitems) {
  90. switch(target) {
  91. // Group menu
  92. case 'group':
  93. vboxChooser._vmGroupContextMenuObj = new vboxMenu({'name': vboxChooser._anchorid+'vmgroups',
  94. 'menuItems': menuitems,
  95. 'language_context': 'UIActionPool'});
  96. vboxChooser._vmGroupContextMenuObj.update();
  97. break;
  98. // VM Menu
  99. case 'vm':
  100. vboxChooser._vmContextMenuObj = new vboxMenu({'name': vboxChooser._anchorid+'vms',
  101. 'menuItems': menuitems,
  102. 'language_context': 'UIActionPool'});
  103. vboxChooser._vmContextMenuObj.update();
  104. break;
  105. // Main list menu
  106. case 'anchor':
  107. var vboxChooserPaneMenu = new vboxMenu({'name': vboxChooser._anchorid+'Pane',
  108. 'menuItems': menuitems,
  109. 'language_context': 'UIActionPool'});
  110. $('#'+vboxChooser._anchorid).parent().contextMenu({
  111. menu: vboxChooserPaneMenu.menuId()
  112. },
  113. vboxChooserPaneMenu.menuClickCallback
  114. );
  115. break;
  116. default:
  117. vboxAlert('vboxChooser::setContextMenu: unknown context menu type (' + target + ')');
  118. }
  119. },
  120. /*
  121. * Return true if a selected VM is in the given state
  122. */
  123. isSelectedInState : function(state) {
  124. for(var i = 0; i < vboxChooser.selectedVMs.length; i++) {
  125. if(vboxVMStates['is'+state](vboxVMDataMediator.getVMData(vboxChooser.selectedVMs[i])))
  126. return true;
  127. }
  128. return false;
  129. },
  130. /**
  131. * Return true if the passed VM is selected
  132. */
  133. isVMSelected : function(vmid) {
  134. return (jQuery.inArray(vmid,vboxChooser.selectedVMs) > -1);
  135. },
  136. /**
  137. * Return selected VM data in array
  138. */
  139. getSelectedVMsData : function() {
  140. var vms = [];
  141. for(var i = 0; i < vboxChooser.selectedVMs.length; i++) {
  142. vms[vms.length] = vboxVMDataMediator.getVMData(vboxChooser.selectedVMs[i]);
  143. }
  144. return vms;
  145. },
  146. /**
  147. * Triggered when selection list has changed
  148. */
  149. selectionListChanged : function(selectionList) {
  150. if(!selectionList) selectionList = [];
  151. selectionMode = vboxSelectionModeNone;
  152. // Hold unique selected VMs
  153. var vmListUnique = {};
  154. for(var i = 0; i < selectionList.length; i++) {
  155. if(selectionList[i].type == 'group') {
  156. vboxChooser.getGroupElement(selectionList[i].groupPath, true).find('table.vboxChooserVM:not(.ui-draggable-dragging)').each(function(idx,elm){
  157. if(elm) {
  158. var vmid = $(elm).data('vmid');
  159. if(vmid)
  160. vmListUnique[vmid] = vmid;
  161. }
  162. });
  163. switch(selectionMode) {
  164. case vboxSelectionModeSingleGroup:
  165. case vboxSelectionModeSingleVM:
  166. selectionMode = vboxSelectionModeMultiVM;
  167. break;
  168. case vboxSelectionModeNone:
  169. selectionMode = vboxSelectionModeSingleGroup;
  170. }
  171. } else {
  172. switch(selectionMode) {
  173. case vboxSelectionModeNone:
  174. selectionMode = vboxSelectionModeSingleVM;
  175. break;
  176. default:
  177. selectionMode = vboxSelectionModeMultiVM;
  178. }
  179. vmListUnique[selectionList[i].id] = selectionList[i].id;
  180. }
  181. }
  182. // Change selection list
  183. var selectedVMs = [];
  184. for(var i in vmListUnique) {
  185. selectedVMs[selectedVMs.length] = i;
  186. }
  187. vboxChooser.selectedVMs = selectedVMs;
  188. // If there is only one unique vm selected,
  189. // selection mode becomes single VM if the
  190. // current selection mode is not singleGroup
  191. if(vboxChooser.selectedVMs.length == 1 && selectionMode != vboxSelectionModeSingleGroup)
  192. selectionMode = vboxSelectionModeSingleVM;
  193. vboxChooser.selectionMode = selectionMode;
  194. vboxChooser._selectedList = selectionList;
  195. $('#vboxPane').trigger('vmSelectionListChanged',[vboxChooser]);
  196. },
  197. /**
  198. * Return the single selected VM's id if
  199. * only one vm is selected. Else null.
  200. */
  201. getSingleSelectedId : function() {
  202. if(vboxChooser.selectedVMs.length == 1) {
  203. return vboxChooser.selectedVMs[0];
  204. }
  205. return null;
  206. },
  207. /*
  208. * Return a single vm if only one is selected.
  209. * Else null.
  210. */
  211. getSingleSelected : function() {
  212. if(vboxChooser.selectedVMs.length == 1) {
  213. return vboxVMDataMediator.getVMData(vboxChooser.selectedVMs[0]);
  214. }
  215. return null;
  216. },
  217. /*
  218. * Update list of VMs from data received
  219. * from ajax query
  220. */
  221. updateList : function(vmlist) {
  222. // We were stopped before the request returned data
  223. if(!vboxChooser._running) return;
  224. // No list? Something is wrong
  225. if(!vmlist) {
  226. phpVirtualBoxFailure();
  227. vboxChooser.stop();
  228. vboxChooser._anchor.children().remove();
  229. return;
  230. }
  231. // Remove spinner
  232. vboxChooser._anchor.children().remove();
  233. // Render host
  234. vboxChooser._anchor.append(vboxChooser.vmHTML(
  235. {
  236. 'id':'host',
  237. 'state':'Hosting',
  238. 'owner':'',
  239. 'name':$('#vboxPane').data('vboxConfig').name,
  240. 'OSTypeId':'VirtualBox_Host'
  241. }
  242. ));
  243. // Render root group
  244. vboxChooser._anchor.append(vboxChooser.groupHTML("/"));
  245. // Enforce VM ownership
  246. if($('#vboxPane').data('vboxConfig').enforceVMOwnership && !$('#vboxPane').data('vboxSession').admin) {
  247. vmlist = jQuery.grep(vmlist,function(vm,i){
  248. return (vm.owner == $('#vboxPane').data('vboxSession').user);
  249. });
  250. }
  251. var groups = [];
  252. // Each item in list
  253. for(var i = 0; i < vmlist.length; i++) {
  254. // Update
  255. vboxChooser.updateVMElement(vmlist[i], true);
  256. groups = groups.concat(vmlist[i].groups);
  257. }
  258. // Sort groups
  259. var groupsSorted = {};
  260. for(var i = 0; i < groups.length; i++) {
  261. if(groupsSorted[groups[i]]) continue;
  262. groupsSorted[groups[i]] = true;
  263. var gElm = vboxChooser.getGroupElement(groups[i], true);
  264. if(gElm[0]) vboxChooser.sortGroup(gElm);
  265. }
  266. // compose group definitions
  267. vboxChooser.composeGroupDef();
  268. // Set initial resize
  269. vboxChooser._initialResize = true;
  270. vboxChooser._resizeElements(true);
  271. },
  272. /*
  273. * Save collapsed group list
  274. */
  275. _collapsedGroups : [],
  276. _saveCollapsedGroups : function(){
  277. // Write out collapsed group list
  278. var cGroupList = [];
  279. vboxChooser._anchor.find('div.vboxVMGroupCollapsed:not(.ui-draggable-dragging)').each(function(idx,elm) {
  280. cGroupList[cGroupList.length] = $(elm).data('vmGroupPath');
  281. });
  282. var groupListKey = $('#vboxPane').data('vboxConfig').key+'-collapsedGroups';
  283. vboxSetLocalDataItem(groupListKey, cGroupList.join(','), true);
  284. // Cache instead of using local storage
  285. vboxChooser._collapsedGroups = cGroupList;
  286. },
  287. /*
  288. * Return true if group is collapsed
  289. */
  290. _isGroupCollapsed : function(gpath) {
  291. return(jQuery.inArray(gpath,vboxChooser._collapsedGroups) > -1);
  292. },
  293. /*
  294. * Resize group and VM titles
  295. */
  296. _scrollbarWidth : 0,
  297. _scrollbarWasVisible: false,
  298. _initialResize: false,
  299. _resizeElements : function(forceResize) {
  300. // Haven't completed our initial resizing yet
  301. if(!vboxChooser._initialResize) {
  302. return;
  303. }
  304. var sbVisible = (vboxChooser._anchor.get(0).scrollHeight > vboxChooser._anchor.height());
  305. // Nothing changed since resize
  306. if(!forceResize && (sbVisible == vboxChooser._scrollbarWasVisible)) {
  307. return;
  308. }
  309. vboxChooser._scrollbarWasVisible = sbVisible;
  310. var groupTitleWidth = vboxChooser._anchor.width() - (vboxChooser._compact ? 22 : 32) - (sbVisible ? vboxChooser._scrollbarWidth : 0);
  311. var vmTitleWidth = groupTitleWidth - (vboxChooser._compact ? -12 : 18); // (2px padding on .vboxChooserGroupVMs +
  312. // 2px border on table + 4px margin on icon) * 2
  313. var groupLevelOffset = (vboxChooser._compact ? 8 : 8); // (2px margin + 2px border) * 2
  314. // Now that we have sizes, we can inject styles
  315. $('#vboxChooserStyle').empty().remove();
  316. var styleRules = [];
  317. var path = ['div.vboxChooserGroupRootLevel'];
  318. // Special case for root level VM list
  319. styleRules[styleRules.length] = 'div.vboxChooserGroupRootLevel > div.vboxChooserGroupVMs table.vboxChooserVM div.vboxFitToContainer { width: ' + vmTitleWidth + 'px; }';
  320. // Special case for group header when only showing one group
  321. styleRules[styleRules.length] = 'div.vboxChooserGroupShowOnly.vboxChooserGroupRootLevel > div.vboxChooserGroupHeader span.vboxChooserGroupName { max-width: ' + (groupTitleWidth - 4) + 'px; }';
  322. // Bottom group resize bars
  323. styleRules[styleRules.length] = 'div.vboxChooserGroupRootLevel > div.vboxChooserDropTargetBottom { width: ' + (groupTitleWidth) + 30 + 'px; }';
  324. for(var i = 1; i < 11; i++) {
  325. // Group titles at this level
  326. styleRules[styleRules.length] = path.join(' > ') + ' > div.vboxChooserGroup > div.vboxChooserGroupHeader span.vboxChooserGroupName { max-width: ' + (groupTitleWidth - (i*groupLevelOffset)) + 'px; }';
  327. // VM titles at this level
  328. styleRules[styleRules.length] = path.join(' > ') + ' > div.vboxChooserGroup > div.vboxChooserGroupVMs table.vboxChooserVM div.vboxFitToContainer { width: ' + (vmTitleWidth - (i*(groupLevelOffset))) + 'px; }';
  329. // Bottom group resize bars
  330. styleRules[styleRules.length] = path.join(' > ') +' > div.vboxChooserGroup > div.vboxChooserDropTargetBottom { width: ' + (groupTitleWidth + 30 - (i*groupLevelOffset)) + 'px; }';
  331. path[path.length] = 'div.vboxChooserGroup';
  332. }
  333. // Style for minified vmlist
  334. if(vboxChooser._compact) {
  335. // Title moves left
  336. styleRules[styleRules.length] = 'div.vboxChooserGroup > div.vboxChooserGroupVMs table.vboxChooserVM div.vboxVMName { position: relative; left: -20px; }';
  337. // Icon moves down
  338. styleRules[styleRules.length] = 'div.vboxChooserGroup > div.vboxChooserGroupVMs table.vboxChooserVM img.vboxVMIcon { position: relative; top: 8px; }';
  339. // State text goes away
  340. styleRules[styleRules.length] = 'div.vboxChooserGroup > div.vboxChooserGroupVMs table.vboxChooserVM span.vboxVMState { display: none; }';
  341. // Less padding
  342. styleRules[styleRules.length] = 'div.vboxChooserGroup > div.vboxChooserGroupVMs table.vboxChooserVM td { padding: 0px; }';
  343. // Some group header items and drop targets go away
  344. styleRules[styleRules.length] = 'div.vboxChooserGroup > div.vboxChooserGroupHeader > .vboxChooserGroupNameArrowCollapse, #' +vboxChooser._anchorid + ' div.vboxChooserGroup .vboxChooserDropTarget { display: none; }';
  345. styleRules[styleRules.length] = 'div.vboxChooserGroup { overflow: hidden; }';
  346. // host
  347. styleRules[styleRules.length] = '#vboxChooserVMHost .vboxVMState { display: none; }';
  348. // group header
  349. styleRules[styleRules.length] = 'div.vboxChooserGroup div.vboxChooserGroupHeader { height: auto; padding: 2px; }';
  350. }
  351. $('head').append('<style type="text/css" id="vboxChooserStyle">#'+vboxChooser._anchorid + ' ' + styleRules.join("\n#"+vboxChooser._anchorid + " ") + '</style>');
  352. },
  353. /*
  354. * Get group element by path
  355. */
  356. getGroupElement : function(gpath, noCreate) {
  357. if(!gpath) gpath = '/';
  358. var gnames = gpath.split('/');
  359. var groot = vboxChooser._anchor.children('div.vboxChooserGroup:not(.ui-draggable-dragging)');
  360. for(var i = 1; i < gnames.length; i++) {
  361. if(!gnames[i]) continue;
  362. var group = groot.children('div.vboxChooserGroup:not(.ui-draggable-dragging)').children('div.vboxChooserGroupIdentifier[title="'+gnames[i]+'"]').parent();
  363. // If it does not exist, create it
  364. if(!group[0]) {
  365. if(noCreate) return null;
  366. var gpath = '/';
  367. for(var a = 1; a <= i; a++) {
  368. gpath = gpath + '/' + gnames[a];
  369. }
  370. gpath = gpath.replace('//','/');
  371. vboxChooser.groupHTML(gpath).insertBefore(groot.children('div.vboxChooserGroupVMs'));
  372. vboxChooser.sortGroup(groot);
  373. // Resize chooser elements
  374. vboxChooser._initialResize = true;
  375. vboxChooser._resizeElements();
  376. groot = groot.children('div.vboxChooserGroup:not(.ui-draggable-dragging)').children('div.vboxChooserGroupIdentifier[title="'+gnames[i]+'"]').parent();
  377. } else {
  378. groot = group;
  379. }
  380. }
  381. return groot;
  382. },
  383. /*
  384. *
  385. * Update VM elements
  386. *
  387. */
  388. updateVMElement : function(vmUpdate, newVM) {
  389. // Not running.. don't do anything
  390. if(!vboxChooser._running) return;
  391. // Stale event after vm was removed
  392. if(!vmUpdate) return;
  393. // New VM
  394. if(newVM) {
  395. // New VM.. add it to groups..
  396. if(!vmUpdate.groups || vmUpdate.groups.length == 0)
  397. vmUpdate.groups = ['/'];
  398. for(var i = 0; i < vmUpdate.groups.length; i++) {
  399. var gElm = $(vboxChooser.getGroupElement(vmUpdate.groups[i]));
  400. vboxChooser.vmHTML(vmUpdate).appendTo(
  401. gElm.children('div.vboxChooserGroupVMs')
  402. );
  403. }
  404. // Existing VM. Replace existing elements
  405. } else {
  406. $('#'+vboxChooser._anchorid).find('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+vmUpdate.id).each(function(i,elm){
  407. var newHTML = vboxChooser.vmHTML(vmUpdate);
  408. if($(elm).hasClass('vboxListItemSelected')) {
  409. $(newHTML).addClass('vboxListItemSelected').removeClass('vboxHover');
  410. }
  411. $(elm).children().replaceWith(newHTML.children());
  412. });
  413. }
  414. },
  415. /*
  416. * Returns true if there are VMs with ID vmid that are not selected
  417. */
  418. vmHasUnselectedCopy : function (vmid) {
  419. return ($(vboxChooser._anchor).find('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+vmid+':not(.vboxListItemSelected)').length > 0);
  420. },
  421. /*
  422. * Remove selected VMs from the list and rewrite group definitions
  423. * this assumes that there are other copies of these VMs that are not
  424. * selected.
  425. */
  426. removeVMs : function(vmids) {
  427. for(var i = 0; i < vmids.length; i++) {
  428. $(vboxChooser._anchor).find('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+vmids[i]+'.vboxListItemSelected').remove();
  429. }
  430. // Update selection list
  431. vboxChooser._selectedList = vboxChooser._selectedList.filter(function(v){
  432. return (v.type == 'group' || (jQuery.inArray(v.id, vmids) == -1));
  433. });
  434. // Tell interface that selection list has changed
  435. vboxChooser.selectionListChanged(vboxChooser._selectedList);
  436. // compose and save group definitions
  437. vboxChooser.composeGroupDef(true);
  438. // Possible resize needed
  439. vboxChooser._resizeElements(true);
  440. },
  441. /*
  442. * Generate HTML from VM definition
  443. */
  444. vmHTML : function (vmn) {
  445. var tbl = $('<table />').attr({'class':'vboxChooserItem-'+vboxChooser._anchorid+'-'+vmn.id + " vboxChooserVM"})
  446. .on('mousedown',vboxChooser.selectItem)
  447. .hoverClass('vboxHover').data('vmid',vmn.id);
  448. // Drag-and-drop functionality
  449. /////////////////////////////////
  450. if(vmn.id != 'host' && $('#vboxPane').data('vboxSession').admin) {
  451. $(tbl).draggable({'cursorAt':{left: -10, top: -10},'helper':function(){
  452. return $(this).clone().css({'width':($(this).width()+2)+'px','display':'inline','background':'#fff','border-color':'#69f'}).removeClass('vboxHover');
  453. // drag start
  454. },'start':function(e) {
  455. if(!vboxChooser._editable) return false;
  456. $(vboxChooser._anchor).disableSelection();
  457. vboxChooser._dragging = vmn.id;
  458. $(vboxChooser._anchor).find('table.vboxHover').removeClass('vboxHover');
  459. // drag stop
  460. },'stop':function(e) {
  461. vboxChooser.vmDropped(e, $(this));
  462. }});
  463. }
  464. // Functionality to drop above / below VM
  465. /////////////////////////////////////////////
  466. var td = $('<td />').attr({'colspan':'2'}).addClass('vboxChooserDropTarget vboxDropTargetTop');
  467. if(vmn.id != 'host') {
  468. td.hover(function(){
  469. if(vboxChooser._dragging && vboxChooser._dragging != vmn.id)
  470. $(this).addClass('vboxChooserDropTargetHover');
  471. },function(){
  472. $(this).removeClass('vboxChooserDropTargetHover');
  473. }
  474. );
  475. }
  476. $('<tr />').append(td).appendTo(tbl);
  477. // VM OS type icon
  478. var tr = $('<tr />');
  479. if($('#vboxPane').data('vboxConfig').enableCustomIcons && vmn.customIcon) {
  480. $('<td />').attr({'rowspan':'2'}).html("<img src='" + vmn.customIcon + "' class='vboxVMIcon' />").appendTo(tr);
  481. } else {
  482. $('<td />').attr({'rowspan':'2'}).html("<img src='images/vbox/" + vboxGuestOSTypeIcon(vmn.OSTypeId) + "' class='vboxVMIcon" + (vmn.id == 'host' ? " vboxHostIcon" : "") + "' />").appendTo(tr);
  483. }
  484. // VM Name
  485. var td = $('<td />').attr({'class':'vboxVMTitle'});
  486. // Host will have HTML in name and unique id
  487. if(vmn.id == 'host') {
  488. $(tbl).attr('id', 'vboxChooserVMHost');
  489. // Check for multiple server config
  490. if($('#vboxPane').data('vboxConfig').servers.length) {
  491. // If there are multiple servers configured, setup menu
  492. if(!$('#vboxServerMenu')[0]) {
  493. var servers = $('#vboxPane').data('vboxConfig').servers;
  494. var ul = $('<ul />').attr({'id':'vboxServerMenu','style':'display: none','class':'contextMenu'});
  495. for(var i = 0; i < servers.length; i++) {
  496. $('<li />').html("<a href='#" + $('<div />').html(servers[i].name).text() + "' style='background-image: url(images/vbox/OSE/VirtualBox_16px.png);'>"+$('<div />').html(servers[i].name).text()+"</a>").appendTo(ul);
  497. }
  498. $('#vboxPane').append(ul);
  499. }
  500. var span = $('<span />').attr({'class':'vboxServerLink'}).text('('+$('#vboxPane').data('vboxConfig').name+')').contextMenu({
  501. menu: 'vboxServerMenu',
  502. button: 0,
  503. mode: 'menu'
  504. },
  505. function(a) {
  506. if(a == $('#vboxPane').data('vboxConfig').name) return;
  507. // Show loading screen
  508. var l = new vboxLoader();
  509. l.showLoading();
  510. // Empty selection list
  511. vboxChooser.selectionListChanged();
  512. // Unsubscribe from events
  513. $.when(vboxEventListener.stop()).done(function() {
  514. // Expire data mediator data
  515. vboxVMDataMediator.expireAll();
  516. // Trigger host change
  517. vboxSetCookie("vboxServer",a);
  518. $('#vboxPane').trigger('hostChange',[a]);
  519. }).always(function(){
  520. // remove loading screen
  521. l.removeLoading();
  522. });
  523. }
  524. );
  525. $(td).html('<span class="vboxVMName">VirtualBox</span> ').append(span);
  526. } else {
  527. $(td).html('<span class="vboxVMName">VirtualBox</span> ('+vmn.name+')');
  528. }
  529. // Not rendering host
  530. } else {
  531. $(td).append('<div class="vboxFitToContainer vboxVMName"><span class="vboxVMName">'+$('<span />').text(vmn.name).html()+'</span>'+ (vmn.currentSnapshotName ? '<span class="vboxVMChooserSnapshotName"> (' + $('<span />').text(vmn.currentSnapshotName).html() + ')</span>' : '')+'</div>');
  532. // Table gets tool tips
  533. tip = trans(vboxChooser._vmToolTip, 'UIVMListView').replace('%1',('<b>'+$('<span />')
  534. .text(vmn.name).html()+'</b>'+(vmn.currentSnapshotName ? ' (' + $('<span />')
  535. .text(vmn.currentSnapshotName).html() + ')' : '')))
  536. .replace('%2',trans(vboxVMStates.convert(vmn.state),'VBoxGlobal'))
  537. .replace('%3',vboxDateTimeString(vmn.lastStateChange))
  538. .replace('%4',trans(vmn.sessionState,'VBoxGlobal').toLowerCase());
  539. $(tbl).tipped({'source':tip,'position':'mouse','delay':1500});
  540. }
  541. $(tr).append(td).appendTo(tbl);
  542. // VM state row
  543. var tr = $('<tr />');
  544. var td = $('<td />').attr({'class':(vmn.id != 'host' && vmn.sessionState != 'Unlocked' ? 'vboxVMSessionOpen' : '')});
  545. // Add VirtualBox version if hosting
  546. if(vmn.id == 'host') {
  547. $(td).html("<div class='vboxFitToContainer vboxVMState'><img src='images/vbox/" + vboxMachineStateIcon(vmn.state) +"' /><span class='vboxVMState'>" + trans(vboxVMStates.convert(vmn.state),'VBoxGlobal') + ' - ' + $('#vboxPane').data('vboxConfig').version.string+'</span></div>');
  548. // Check for version mismatches?
  549. if(!vboxChooser._versionChecked) {
  550. vboxChooser._versionChecked = true;
  551. var vStr = $('#vboxPane').data('vboxConfig').phpvboxver.substring(0,$('#vboxPane').data('vboxConfig').phpvboxver.indexOf('-'));
  552. var vers = $('#vboxPane').data('vboxConfig').version.string.replace('_OSE','').split('.');
  553. if(vers[0]+'.'+vers[1] != vStr) {
  554. vboxAlert('This version of phpVirtualBox ('+$('#vboxPane').data('vboxConfig').phpvboxver+') is incompatible with VirtualBox ' + $('#vboxPane').data('vboxConfig').version.string + ". You probably need to <a href='http://sourceforge.net/projects/phpvirtualbox/files/' target=_blank>download the latest phpVirtualBox " + vers[0]+'.'+vers[1] + "-x</a>.<p>See the Versioning section below the file list in the link for more information</p>",{'width':'auto'});
  555. }
  556. }
  557. } else {
  558. $(td).html("<div class='vboxFitToContainer vboxVMState'><img src='images/vbox/" + vboxMachineStateIcon(vmn.state) +"' /><span class='vboxVMState'>" + trans(vboxVMStates.convert(vmn.state),'VBoxGlobal') + '</span></div>');
  559. }
  560. $(tr).append(td).appendTo(tbl);
  561. // Droppable targets
  562. var td = $('<td />').attr({'colspan':'2'}).addClass('vboxChooserDropTarget vboxDropTargetBottom');
  563. if(vmn.id != 'host') {
  564. td.hover(function(){
  565. if(vboxChooser._dragging && vboxChooser._dragging != vmn.id)
  566. $(this).addClass('vboxChooserDropTargetHover');
  567. },function(){
  568. $(this).removeClass('vboxChooserDropTargetHover');
  569. }
  570. );
  571. }
  572. $('<tr />').addClass('vboxChooserDropTarget').css({'height':'4px'}).append(td).appendTo(tbl);
  573. // Context menus?
  574. if(vboxChooser._vmContextMenuObj) {
  575. $(tbl).contextMenu({
  576. menu: vboxChooser._vmContextMenuObj.menuId(),
  577. menusetup : function(el) {
  578. if(!$(el).hasClass('vboxListItemSelected')) $(el).trigger('click');
  579. }
  580. },function(act,el,pos,d,e){
  581. vboxChooser._vmContextMenuObj.menuClickCallback(act);
  582. });
  583. // Open settings on dblclick
  584. $(tbl).dblclick(function(){
  585. if(vboxChooser._vmContextMenuObj.menuItems['settings'].enabled())
  586. vboxChooser._vmContextMenuObj.menuItems['settings'].click();
  587. });
  588. }
  589. return tbl;
  590. },
  591. /*
  592. * VM Group Dropped
  593. */
  594. vmGroupDropped : function(e, droppedGroup) {
  595. $(vboxChooser._anchor).enableSelection();
  596. var vmGroupPath = vboxChooser._draggingGroup;
  597. vboxChooser._draggingGroup = false;
  598. $(droppedGroup).removeClass('vboxHover');
  599. if(!vboxChooser._editable) return false;
  600. // Cannot drag a group that contains a VM without
  601. // an unlocked session state if it will modify VM
  602. // Groups
  603. var sessionLocked = false;
  604. if($(droppedGroup).find('td.vboxVMSessionOpen')[0])
  605. sessionLocked=true;
  606. // Check for above/below group first
  607. var dropTarget = vboxChooser._anchor.find('div.vboxChooserDropTargetHover').first();
  608. if(dropTarget[0]) {
  609. // Make sure that this wasn't dropped onto a sub-group or itself
  610. if(
  611. !dropTarget.closest('div.vboxChooserGroup')[0]
  612. ||
  613. vmGroupPath == dropTarget.closest('div.vboxChooserGroup').data('vmGroupPath')
  614. ||
  615. dropTarget.closest('div.vboxChooserGroup').data('vmGroupPath').indexOf(vmGroupPath + '/') == 0
  616. ) {
  617. return;
  618. }
  619. // If we are not still in the same group, check for name conflict
  620. var currParentGroupPath = $(droppedGroup).closest('div.vboxChooserGroup').parent().closest('div.vboxChooserGroup').data('vmGroupPath');
  621. if(dropTarget.closest('div.vboxChooserGroup').parent().closest('div.vboxChooserGroup').data('vmGroupPath') != currParentGroupPath) {
  622. // Do not allow to be dragged into another group
  623. // if there is a Vm with a locked session in this one
  624. if(sessionLocked && !$('#vboxPane').data('vboxConfig')['phpVboxGroups']) return;
  625. // Make sure there are no conflicts
  626. var groupName = $(droppedGroup).children('div.vboxChooserGroupIdentifier').attr('title');
  627. var newGroupName = groupName;
  628. var i = 2;
  629. while(vboxChooser.groupNameConflicts(dropTarget.closest('div.vboxChooserGroup').parent(), newGroupName)) {
  630. newGroupName = groupName + ' (' + (i++) + ')';
  631. }
  632. $(droppedGroup).children('div.vboxChooserGroupIdentifier').attr({'title':newGroupName})
  633. .siblings('div.vboxChooserGroupHeader')
  634. .children('span.vboxChooserGroupName').text(newGroupName);
  635. }
  636. // Insert before or insert after?
  637. if(dropTarget.hasClass('vboxDropTargetTop')) {
  638. $(droppedGroup).detach().insertBefore(dropTarget.closest('div.vboxChooserGroup'));
  639. } else {
  640. $(droppedGroup).detach().insertAfter(dropTarget.closest('div.vboxChooserGroup'));
  641. }
  642. // Dropped onto a group or main VM list
  643. } else {
  644. // Will not do this if this group contains
  645. // a VM with a locked session
  646. if(sessionLocked && !$('#vboxPane').data('vboxConfig')['phpVboxGroups']) return;
  647. var dropTarget = vboxChooser._anchor.find('div.vboxHover').first();
  648. // Dropped onto a group
  649. if(dropTarget[0] && dropTarget.parent().hasClass('vboxChooserGroup')) {
  650. dropTarget = dropTarget.parent();
  651. // Make sure that this wasn't dropped onto a sub-group or itself
  652. if(
  653. vmGroupPath == dropTarget.data('vmGroupPath')
  654. ||
  655. dropTarget.closest('div.vboxChooserGroup').data('vmGroupPath').indexOf(vmGroupPath + '/') == 0
  656. ) {
  657. return;
  658. }
  659. // Dropped onto main vm list
  660. } else if($(vboxChooser._anchor).find('div.vboxGroupHover').length == 0 && $(vboxChooser._anchor).hasClass('vboxChooserDropTargetHoverRoot')) {
  661. dropTarget = null;
  662. // Only showing one group?
  663. if(vboxChooser._showOnlyGroupHistory.length > 0) {
  664. dropTarget = $(vboxChooser._showOnlyGroupHistory[vboxChooser._showOnlyGroupHistory.length-1]);
  665. }
  666. if(!$(dropTarget)[0])
  667. dropTarget = vboxChooser._anchor.children('div.vboxChooserGroup');
  668. } else {
  669. return;
  670. }
  671. // Make sure there are no conflicts
  672. var newElm = $(droppedGroup).detach();
  673. var groupName = $(droppedGroup).children('div.vboxChooserGroupIdentifier').attr('title');
  674. var newGroupName = groupName;
  675. var i = 2;
  676. while(vboxChooser.groupNameConflicts(dropTarget, newGroupName, $(newElm).data('vmGroupPath'))) {
  677. newGroupName = groupName + ' (' + (i++) + ')';
  678. }
  679. $(newElm)
  680. .children('div.vboxChooserGroupIdentifier').attr({'title':newGroupName})
  681. .siblings('div.vboxChooserGroupHeader')
  682. .children('span.vboxChooserGroupName').text(newGroupName);
  683. $(newElm).insertBefore(dropTarget.children('div.vboxChooserGroupVMs'));
  684. }
  685. // vmGroup dropped - compose and save group definitions
  686. vboxChooser.composeGroupDef(true);
  687. // Hide group info
  688. vboxChooser._anchor.find('div.vboxChooserGroupHeader').trigger('mouseout');
  689. // Resize chooser elements
  690. vboxChooser._resizeElements();
  691. vboxChooser.selectionListChanged(vboxChooser._selectedList);
  692. },
  693. /*
  694. * VM dropped
  695. */
  696. vmDropped : function (e, droppedVM){
  697. $(vboxChooser._anchor).enableSelection();
  698. vboxChooser._dragging = null;
  699. if(!vboxChooser._editable) return false;
  700. // Cannot drag if this VM's session is not open
  701. var thisSessionLocked = false;
  702. var vmData = vboxVMDataMediator.getVMData($(droppedVM).data('vmid'));
  703. if(vmData.sessionState != 'Unlocked')
  704. thisSessionLocked = true;
  705. // Where was this dropped?
  706. var dropTarget = $('#'+vboxChooser._anchorid).find('td.vboxChooserDropTargetHover');
  707. // Dropped above / below a VM
  708. if(dropTarget[0]) {
  709. // Dropped from another group into this one,
  710. // but this group already has this VM
  711. if((dropTarget.closest('table').closest('div.vboxChooserGroup').data('vmGroupPath') != $(droppedVM).closest('div.vboxChooserGroup').data('vmGroupPath'))
  712. && dropTarget.closest('table').siblings('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+$(droppedVM).data('vmid'))[0]) {
  713. return true;
  714. }
  715. // If session of this VM is locked, don't allow it to be
  716. // dragged out of current group
  717. if(thisSessionLocked && !$('#vboxPane').data('vboxConfig')['phpVboxGroups'] && ($(droppedVM).closest('div.vboxChooserGroup').data('vmGroupPath') != dropTarget.closest('div.vboxChooserGroup').data('vmGroupPath'))) {
  718. return
  719. }
  720. // Get VM from target's parent table
  721. if(dropTarget.hasClass('vboxDropTargetTop')) {
  722. if(!e.ctrlKey && !e.metaKey) {
  723. $(droppedVM).detach().insertBefore($(dropTarget).closest('table'));
  724. } else {
  725. // Copy
  726. if($(dropTarget).closest('table').parent().children('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+vmData.id)[0])
  727. return;
  728. vboxChooser.vmHTML(vmData).insertBefore($(dropTarget).closest('table'));
  729. }
  730. } else {
  731. if(!e.ctrlKey && !e.metaKey) {
  732. $(droppedVM).detach().insertAfter($(dropTarget).closest('table'));
  733. } else {
  734. // Copy - Don't allow if it already exists
  735. if($(dropTarget).closest('table').parent().children('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+vmData.id)[0])
  736. return;
  737. vboxChooser.vmHTML(vmData).insertAfter($(dropTarget).closest('table'));
  738. }
  739. }
  740. // Not dropped above / below vm
  741. } else {
  742. // Don't allow this if sessoin is locked
  743. if(thisSessionLocked && !$('#vboxPane').data('vboxConfig')['phpVboxGroups']) return;
  744. // Dropped ON a vm?
  745. dropTarget = $('#'+vboxChooser._anchorid).find('table.vboxHover:not(.ui-draggable-dragging)').first();
  746. if($(dropTarget).data('vmid')) {
  747. // Create a group?
  748. dropTarget = $('#'+vboxChooser._anchorid).find('table.vboxHover').first();
  749. // Nothing to do. Not dropped on valid target
  750. if(!dropTarget[0] || ($(dropTarget).data('vmid') == $(droppedVM).data('vmid'))) return true;
  751. // Dont' allow this if target VM's session is locked
  752. if($(dropTarget).find('td.vboxVMSessionOpen')[0])
  753. return;
  754. // Where to drop vboxChooser..
  755. var p = dropTarget.closest('div.vboxChooserGroup').children('div.vboxChooserGroupVMs');
  756. // assume root?
  757. if(!p[0]) p = vboxChooser._anchor.children('div.vboxChooserGroupVMs');
  758. // Determine group name
  759. var gname = trans('New group','UIGChooserModel');
  760. var tgname = gname;
  761. var i = 2;
  762. while(vboxChooser.groupNameConflicts($(p).parent(), tgname)) {
  763. tgname = gname + ' ' + (i++);
  764. }
  765. // New position is below target
  766. var ghtml = vboxChooser.groupHTML(String(dropTarget.closest('div.vboxChooserGroup').data('vmGroupPath')+'/'+tgname).replace('//','/'));
  767. if(!e.ctrlKey && !e.metaKey) {
  768. ghtml.children('div.vboxChooserGroupVMs').append($(droppedVM).detach());
  769. } else {
  770. ghtml.children('div.vboxChooserGroupVMs').append(vboxChooser.vmHTML(vmData));
  771. }
  772. ghtml.children('div.vboxChooserGroupVMs').append(dropTarget.detach());
  773. ghtml.insertBefore(p);
  774. // Dropped in the main VM list or group header?
  775. } else {
  776. dropTarget = $(vboxChooser._anchor).find('div.vboxHover').first();
  777. if(dropTarget[0] && dropTarget.hasClass('vboxChooserGroupHeader')) {
  778. // Group already has this dragging VM?
  779. if(dropTarget.siblings('div.vboxChooserGroupVMs').children('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+$(droppedVM).data('vmid'))[0]) {
  780. return;
  781. }
  782. if(!e.ctrlKey && !e.metaKey)
  783. $(droppedVM).detach().appendTo(dropTarget.siblings('div.vboxChooserGroupVMs').first());
  784. else
  785. vboxChooser.vmHTML(vmData).appendTo(dropTarget.siblings('div.vboxChooserGroupVMs').first());
  786. // Main VM list
  787. } else if($(vboxChooser._anchor).find('div.vboxGroupHover').length == 0 && $(vboxChooser._anchor).hasClass('vboxChooserDropTargetHoverRoot')) {
  788. dropTarget = null;
  789. // Only showing one group?
  790. if(vboxChooser._showOnlyGroupHistory.length > 0) {
  791. dropTarget = $(vboxChooser._showOnlyGroupHistory[vboxChooser._showOnlyGroupHistory.length-1]);
  792. }
  793. if(!$(dropTarget)[0])
  794. dropTarget = vboxChooser._anchor.children('div.vboxChooserGroup');
  795. // Already in this list?
  796. if(dropTarget.children('div.vboxChooserGroupVMs').children('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+$(droppedVM).data('vmid'))[0]) {
  797. return true;
  798. }
  799. if(!e.ctrlKey && !e.metaKey) {
  800. $(droppedVM).detach().appendTo(dropTarget.children('div.vboxChooserGroupVMs').first());
  801. } else {
  802. vboxChooser.vmHTML(vmData).appendTo(dropTarget.children('div.vboxChooserGroupVMs').first());
  803. }
  804. }
  805. }
  806. }
  807. // vm dropped - compose and save group definitions
  808. vboxChooser.composeGroupDef(true);
  809. // Resize chooser elements
  810. vboxChooser._resizeElements();
  811. vboxChooser.selectionListChanged(vboxChooser._selectedList);
  812. },
  813. /*
  814. * Group selected items into a new group
  815. */
  816. groupSelectedItems : function() {
  817. // Get all group paths to determine new group target
  818. var groupPaths = {};
  819. vboxChooser._anchor.find('div.vboxVMGroupSelected').closest('div.vboxChooserGroup').each(function(idx,elm) {
  820. groupPaths[$(elm).data('vmGroupPath')] = 1;
  821. });
  822. vboxChooser._anchor.find('table.vboxListItemSelected').closest('div.vboxChooserGroup').each(function(idx,elm) {
  823. groupPaths[$(elm).data('vmGroupPath')] = 1;
  824. });
  825. // The group clsest to the root group will be the target
  826. var groupPathTarget = null;
  827. for(var i in groupPaths) {
  828. if(typeof(i) != 'string') continue;
  829. // Already at root group. Nothing to do
  830. if(groupPathTarget == '/') break;
  831. // No target set yet or equal targets, or this group is the root
  832. if(!groupPathTarget || groupPathTarget == i || i == '/') {
  833. groupPathTarget = i;
  834. continue;
  835. }
  836. var t1 = groupPathTarget.split("/");
  837. var t2 = i.split("/");
  838. for(var i = 0; i < Math.min(t1.length,t2.length); i++) {
  839. if(t1[i] != t2[i]) {
  840. groupPathTarget = '';
  841. for(var a = 0; a < i; a++) {
  842. groupPathTarget += "/" + t1[a];
  843. }
  844. groupPathTarget = groupPathTarget.replace('//','/');
  845. break;
  846. }
  847. }
  848. }
  849. var target = vboxChooser.getGroupElement(groupPathTarget, true);
  850. if(!$(target)[0]) return;
  851. // Determine group name
  852. var gname = trans('New group','UIGChooserModel');
  853. var tgname = gname;
  854. var i = 2;
  855. while(vboxChooser.groupNameConflicts($(target), tgname)) {
  856. tgname = gname + ' ' + (i++);
  857. }
  858. var gHTML = vboxChooser.groupHTML('/'+tgname);
  859. // Append group and vm elements
  860. vboxChooser._anchor.find('div.vboxVMGroupSelected').detach().insertAfter(gHTML.children('div.vboxChooserGroupHeader'));
  861. vboxChooser._anchor.find('table.vboxListItemSelected').detach().appendTo(gHTML.children('div.vboxChooserGroupVMs'));
  862. gHTML.insertBefore($(target).children('div.vboxChooserGroupVMs'));
  863. // group selected items,
  864. // Compose and save group definitions
  865. vboxChooser.composeGroupDef(true);
  866. // Resize chooser elements
  867. vboxChooser._resizeElements();
  868. },
  869. /**
  870. * Compose group data from GUI and optionally save it
  871. *
  872. * @param save - save group definitions to vbox
  873. */
  874. composeGroupDef : function(save) {
  875. var allGroups = [];
  876. var groupsResolved = false;
  877. // Keep looping through group definitions until
  878. // there are no groups removed
  879. while(!groupsResolved) {
  880. allGroups = [];
  881. groupsResolved = true;
  882. vboxChooser._anchor.find('div.vboxChooserGroup:not(.ui-draggable-dragging)').each(function(idx,elm) {
  883. // Group element was removed
  884. if(!$(elm)[0]) return;
  885. // Compose group path
  886. var myPath = $(elm).children('div.vboxChooserGroupIdentifier').attr('title');
  887. if(!myPath) myPath = '/';
  888. $(elm).parents('div.vboxChooserGroup:not(.ui-draggable-dragging)').each(function(idx2,elm2){
  889. var pName = $(elm2).children('div.vboxChooserGroupIdentifier').attr('title');
  890. if(!pName) pName = '/';
  891. myPath = String(pName + '/' + myPath).replace('//','/');
  892. });
  893. // Groups
  894. var gList = [];
  895. $(elm).children('div.vboxChooserGroup:not(.ui-draggable-dragging)').each(function(idx2,elm2){
  896. // If this group is selected, we'll have to update its path
  897. // in the selection list
  898. var selected = $(elm2).hasClass('vboxVMGroupSelected');
  899. var oldPath = $(elm2).data('vmGroupPath');
  900. var newPath = String(myPath + '/' + $(elm2).children('div.vboxChooserGroupIdentifier').attr('title')).replace('//','/');
  901. gList[gList.length] = $(elm2).children('div.vboxChooserGroupIdentifier').attr('title');
  902. // set / correct group path data
  903. $(elm2).data('vmGroupPath', newPath);
  904. // Group's path changed?
  905. if(selected && (oldPath != newPath)) {
  906. for(var i = 0; i < vboxChooser._selectedList.length; i++) {
  907. if(vboxChooser._selectedList[i].type == 'group' && vboxChooser._selectedList[i].groupPath == oldPath) {
  908. vboxChooser._selectedList[i].groupPath = String(myPath + '/' + $(elm2).children('div.vboxChooserGroupIdentifier').attr('title')).replace('//','/');
  909. break;
  910. }
  911. }
  912. }
  913. });
  914. // VMs
  915. var vmList = [];
  916. $(elm).children('div.vboxChooserGroupVMs').children('table.vboxChooserVM:not(.ui-draggable-dragging)').each(function(idx3,elm3){
  917. vmList[vmList.length] = $(elm3).data('vmid');
  918. });
  919. // Skip and remove if there are no VMs or subgroups
  920. // And it is not the parent group
  921. if(gList.length + vmList.length == 0 && !$(elm).hasClass('vboxChooserGroupRoot')) {
  922. // remove from selected list?
  923. if(elm && $(elm).hasClass('vboxVMGroupSelected')) {
  924. var myPath = $(elm).data('vmGroupPath');
  925. // Deselect item
  926. vboxChooser._selectedList = vboxChooser._selectedList.filter(function(v){
  927. return (v.type != 'group' || (v.groupPath != myPath));
  928. });
  929. }
  930. $(elm).empty().remove();
  931. groupsResolved = false;
  932. return false;
  933. }
  934. // append to all groups list
  935. gorder = [];
  936. if(gList.length) gorder[0] = 'go='+gList.join(',go=');
  937. if(vmList.length) gorder[gorder.length] = 'm='+vmList.join(',m=');
  938. allGroups[allGroups.length] = {
  939. path: $(elm).data('vmGroupPath'),
  940. order: gorder.join(',')
  941. };
  942. // Update counts span
  943. $(elm).children('div.vboxChooserGroupVMs').css({'display':(vmList.length || $(elm).data('vmGroupPath') == '/' ? '' : 'none')})
  944. .siblings('div.vboxChooserGroupHeader')
  945. .each(function(hidx,header) {
  946. var staticTip = '<strong>'+$(header).siblings('div.vboxChooserGroupIdentifier').attr('title')+'</strong>'+
  947. (gList.length ? ('<br />' + trans('%n group(s)','UIGChooserItemGroup',gList.length).replace('%n',gList.length)) : '') +
  948. (vmList.length ? ('<br />' + trans('%n machine(s)','UIGChooserItemGroup',vmList.length).replace('%n',vmList.length)) : '');
  949. $(header).tipped({'source':function() {
  950. // find number of running VMs
  951. var runningVMs = 0;
  952. if(vmList.length) {
  953. $(header).siblings('div.vboxChooserGroupVMs').find('td.vboxVMSessionOpen').each(function(idx,elm3) {
  954. if(vboxVMStates.isRunning(vboxVMDataMediator.getVMData($(elm3).closest('table').data('vmid'))))
  955. runningVMs++;
  956. });
  957. }
  958. return staticTip + (runningVMs > 0 ? ' ' + trans('(%n running)','UIGChooserItemGroup',runningVMs).replace('%n', runningVMs) : '');
  959. }
  960. ,'position':'mouse','delay':1500});
  961. })
  962. .children('span.vboxChooserGroupInfo')
  963. .children('span.vboxChooserGroupCounts').html(
  964. (gList.length ? ('<span style="background-image:url(images/vbox/group_abstract_16px.png);" />'+gList.length) : '') +
  965. (vmList.length ? ('<span style="background-image:url(images/vbox/machine_abstract_16px.png);" />'+vmList.length) : '')
  966. );
  967. });
  968. }
  969. // Save GUI group definition?
  970. if(!save) return;
  971. // Tell the interface we're about to save groups
  972. vboxChooser._editable = false;
  973. $('#vboxPane').trigger('vmGroupDefsSaving');
  974. vboxChooser._groupDefs = allGroups;
  975. // Save machine groups and trigger change
  976. var vms = [];
  977. var vmList = vboxVMDataMediator.getVMList();
  978. for(var i = 0; i < vmList.length; i++) {
  979. if(!vmList[i] || vmList[i].id == 'host') continue;
  980. /* If a VM's groups have changed, add it to the list */
  981. var eGroups = vmList[i].groups;
  982. var nGroups = vboxChooser.getGroupsForVM(vmList[i].id);
  983. if($(nGroups).not(eGroups).length || $(eGroups).not(nGroups).length) {
  984. vms[vms.length] = {
  985. 'id' : vmList[i].id,
  986. 'groups' : nGroups
  987. };
  988. }
  989. }
  990. // Save machines groups?
  991. if(vms.length) {
  992. // Reload VMs and group definitions
  993. var reloadAll = function() {
  994. var ml = new vboxLoader();
  995. ml.add('vboxGetMedia',function(d){$('#vboxPane').data('vboxMedia',d.responseData);});
  996. ml.add('vboxGroupDefinitionsGet',function(d){vboxChooser._groupDefs = d.responseData;});
  997. // Reload VM list and group definitions, something went wrong
  998. ml.onLoad = function() {
  999. // Stop vmlist from refreshing..
  1000. vboxChooser.stop();
  1001. // reset selections
  1002. $('#vboxPane').trigger('vmSelectionListChanged',[vboxChooser]);
  1003. // ask for new one
  1004. vboxChooser.start();
  1005. };
  1006. ml.run();
  1007. };
  1008. $.when(vboxAjaxRequest('machinesSaveGroups',{'vms':vms})).done(function(res){
  1009. if(res.responseData.errored) {
  1010. reloadAll();
  1011. vboxChooser._editable = true;
  1012. $('#vboxPane').trigger('vmGroupDefsSaved');
  1013. } else {
  1014. $.when(vboxAjaxRequest('vboxGroupDefinitionsSet',{'groupDefinitions':allGroups})).always(function(){
  1015. vboxChooser._editable = true;
  1016. $('#vboxPane').trigger('vmGroupDefsSaved');
  1017. });
  1018. }
  1019. }).fail(reloadAll);
  1020. } else {
  1021. $.when(vboxAjaxRequest('vboxGroupDefinitionsSet',{'groupDefinitions':allGroups})).always(function(){
  1022. vboxChooser._editable = true;
  1023. $('#vboxPane').trigger('vmGroupDefsSaved');
  1024. });
  1025. }
  1026. return allGroups;
  1027. },
  1028. /*
  1029. * Return a list of groups that VM is a member of
  1030. */
  1031. getGroupsForVM : function(vmid) {
  1032. var gPathList = [];
  1033. vboxChooser._anchor.find('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+vmid+':not(.ui-draggable-dragging)').each(function(idx,elm){
  1034. var gParent = $(elm).closest('div.vboxChooserGroup');
  1035. if(!gParent.hasClass('ui-draggable-dragging'))
  1036. gPathList[gPathList.length] = gParent.data('vmGroupPath');
  1037. });
  1038. return gPathList;
  1039. },
  1040. /*
  1041. * Determine whether or not a group name conflicts
  1042. * with another group in parent
  1043. */
  1044. groupNameConflicts : function(parentGroup, name, ignoreGroupAtPath) {
  1045. var found = false;
  1046. parentGroup.children('div.vboxChooserGroup:not(.ui-draggable-dragging)').children('div.vboxChooserGroupIdentifier[title="'+name+'"]').parent().each(function(i,elm){
  1047. if(ignoreGroupAtPath && (ignoreGroupAtPath == $(elm).data('vmGroupPath')))
  1048. return true;
  1049. found=true;
  1050. return false;
  1051. });
  1052. return found;
  1053. },
  1054. /*
  1055. * Ungroup selected group
  1056. */
  1057. unGroupSelectedGroup : function() {
  1058. var target = $(vboxChooser.getSelectedGroupElements()[0]).siblings('div.vboxChooserGroupVMs');
  1059. // Groups
  1060. // - ignore group at path we are currently ungrouping
  1061. var ignoreGroup = $(vboxChooser.getSelectedGroupElements()[0]).data('vmGroupPath');
  1062. $(vboxChooser.getSelectedGroupElements()[0]).children('div.vboxChooserGroup').each(function(i,elm) {
  1063. // Make sure there are no conflicts
  1064. var newElm = $(elm).detach();
  1065. var groupName = $(elm).children('div.vboxChooserGroupIdentifier').attr('title');
  1066. var newGroupName = groupName;
  1067. var i = 2;
  1068. while(vboxChooser.groupNameConflicts($(target).parent(), newGroupName, ignoreGroup)) {
  1069. newGroupName = groupName + ' (' + (i++) + ')';
  1070. }
  1071. $(newElm).children('div.vboxChooserGroupIdentifier').attr({'title':newGroupName})
  1072. .siblings('div.vboxChooserGroupHeader')
  1073. .children('span.vboxChooserGroupName').text(newGroupName);
  1074. $(newElm).insertBefore(target);
  1075. });
  1076. // VMs
  1077. $(vboxChooser.getSelectedGroupElements()[0]).children('div.vboxChooserGroupVMs').children().each(function(i,elm){
  1078. $(elm).detach();
  1079. if(!target.children('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+$(elm).data('vmid'))[0])
  1080. target.append(elm);
  1081. });
  1082. // ungroup selected items
  1083. // compose and save group definitions
  1084. vboxChooser.composeGroupDef(true);
  1085. // Resize chooser elements
  1086. vboxChooser._resizeElements();
  1087. vboxChooser.selectionListChanged();
  1088. },
  1089. /*
  1090. * Sort group sub-elements based on VirtualBox
  1091. * group definitions
  1092. */
  1093. sortGroup : function(gElm) {
  1094. // Find group orders
  1095. var gPath = $(gElm).data('vmGroupPath');
  1096. var groupList = vboxChooser._groupDefs;
  1097. if(!(gPath && groupList)) return;
  1098. var machineOrder = [];
  1099. var groupOrder = [];
  1100. // Get correct order
  1101. for(var i = 0; i < groupList.length; i++) {
  1102. if(groupList[i].path == gPath) {
  1103. order = groupList[i].order.split(',');
  1104. for(var a = 0; a < order.length; a++) {
  1105. kv = order[a].split('=',2);
  1106. if(kv[0] == 'm') machineOrder[machineOrder.length] = kv[1];
  1107. else groupOrder[groupOrder.length] = kv[1];
  1108. }
  1109. }
  1110. }
  1111. // sort groups
  1112. var groups = $(gElm).children('div.vboxChooserGroup').get();
  1113. var maxPos = groups.length;
  1114. groups.sort(function(a,b){
  1115. var Pos1 = jQuery.inArray($(a).children('div.vboxChooserGroupIdentifier').attr('title'), groupOrder);
  1116. var Pos2 = jQuery.inArray($(b).children('div.vboxChooserGroupIdentifier').attr('title'), groupOrder);
  1117. if(Pos1==-1) Pos1 = maxPos;
  1118. if(Pos2==-1) Pos2 = maxPos;
  1119. return (Pos1 > Pos2 || Pos1 == -1 ? -1 : (Pos2 == Pos1 ? 0 : 1));
  1120. });
  1121. $.each(groups, function(idx,itm) {
  1122. $(itm).insertAfter($(gElm).children('div.vboxChooserGroupHeader'));
  1123. });
  1124. // sort VMs
  1125. var vms = $(gElm).children('div.vboxChooserGroupVMs').children('table.vboxChooserVM').get();
  1126. var maxPos = vms.length;
  1127. vms.sort(function(a,b) {
  1128. var Pos1 = jQuery.inArray($(a).data('vmid'), machineOrder);
  1129. var Pos2 = jQuery.inArray($(b).data('vmid'), machineOrder);
  1130. if(Pos1==-1) Pos1 = maxPos;
  1131. if(Pos2==-1) Pos2 = maxPos;
  1132. return (Pos1 > Pos2 ? 1 : (Pos2 == Pos1 ? 0 : -1));
  1133. });
  1134. $.each(vms, function(idx,itm) {
  1135. $(gElm).children('div.vboxChooserGroupVMs').append(itm);
  1136. });
  1137. },
  1138. /*
  1139. * Sort selected group by item names
  1140. */
  1141. sortSelectedGroup : function(rootElm) {
  1142. var el = $(vboxChooser.getSelectedGroupElements()[0]);
  1143. if(rootElm || !el) {
  1144. el = vboxChooser._anchor.children('div.vboxChooserGroup');
  1145. }
  1146. // sort groups
  1147. var groups = $(el).children('div.vboxChooserGroup').get();
  1148. groups.sort(function(a,b){
  1149. return $(b).children('div.vboxChooserGroupIdentifier').attr('title').localeCompare($(a).children('div.vboxChooserGroupIdentifier').attr('title'));
  1150. });
  1151. $.each(groups, function(idx,itm) {
  1152. $(itm).insertAfter($(el).children('div.vboxChooserGroupHeader'));
  1153. });
  1154. // sort VMs
  1155. var vms = $(el).children('div.vboxChooserGroupVMs').children('table.vboxChooserVM').get();
  1156. vms.sort(function(a,b) {
  1157. return $(a).find('span.vboxVMName').text().localeCompare($(b).find('span.vboxVMName').text());
  1158. });
  1159. $.each(vms, function(idx,itm) {
  1160. $(el).children('div.vboxChooserGroupVMs').append(itm);
  1161. });
  1162. // compose and save group definitions
  1163. vboxChooser.composeGroupDef(true);
  1164. },
  1165. /*
  1166. * Rename selected group
  1167. */
  1168. renameSelectedGroup : function() {
  1169. var el = $(vboxChooser.getSelectedGroupElements()[0]);
  1170. // Function to rename group
  1171. var renameGroup = function(e, textbox) {
  1172. if(!textbox) textbox = $(this);
  1173. var newName = $(textbox).val().replace(/[\\\/:*?"<>,]/g,'_');
  1174. if(newName && newName != $(textbox).closest('div.vboxChooserGroup').children('div.vboxChooserGroupIdentifier').attr('title')) {
  1175. // Do not rename if it conflicts
  1176. var noConflict = newName;
  1177. var i = 2;
  1178. while(vboxChooser.groupNameConflicts($(textbox).parent().parent().parent().parent(), noConflict)) {
  1179. noConflict = newName + ' (' + (i++) + ')';
  1180. }
  1181. newName = noConflict;
  1182. $(textbox).closest('div.vboxChooserGroup')
  1183. .children('div.vboxChooserGroupIdentifier').attr({'title':newName})
  1184. .siblings('div.vboxChooserGroupHeader')
  1185. .children('span.vboxChooserGroupName').html(newName);
  1186. // group renamed, compose and save groups
  1187. vboxChooser.composeGroupDef(true);
  1188. // Write out collapsed group list
  1189. vboxChooser._saveCollapsedGroups();
  1190. }
  1191. $(textbox).parent().parent().children().css({'display':''});
  1192. $(textbox).parent().empty().remove();
  1193. };
  1194. $(el).children('div.vboxChooserGroupHeader').children().css({'display':'none'});
  1195. $(el).children('div.vboxChooserGroupHeader').append(
  1196. $('<form />').append(
  1197. $('<input />').attr({'type':'text','value':$(el).children('div.vboxChooserGroupIdentifier').attr('title')}).css({'width':'90%','padding':'0px','margin':'0px'}).on('keypress',function(e){
  1198. if (e.which == 13) {
  1199. $(this).off('blur', renameGroup);
  1200. renameGroup(e,this);
  1201. e.stopPropagation();
  1202. e.preventDefault();
  1203. $(this).trigger('blur');
  1204. return false;
  1205. }
  1206. })
  1207. )
  1208. );
  1209. $(el).children('div.vboxChooserGroupHeader').children('form').children('input').focus().select().blur(renameGroup);
  1210. },
  1211. /*
  1212. * Select a single group
  1213. */
  1214. _selectGroup : function(gelm) {
  1215. $(gelm).addClass('vboxVMGroupSelected');
  1216. },
  1217. /*
  1218. * Deselect a single group
  1219. */
  1220. _deselectGroup : function(gelm) {
  1221. $(gelm).removeClass('vboxVMGroupSelected');
  1222. },
  1223. /*
  1224. * Select (or unselect) an item in our list. Called onmousedown or onCLick
  1225. */
  1226. selectItem : function(e) {
  1227. // Right click selects item if it is not selected
  1228. if(e.which != 1) {
  1229. // Right click on group header and group is selected
  1230. // just return and show context menu
  1231. if($(this).hasClass('vboxChooserGroupHeader') && $(this).parent().hasClass('vboxVMGroupSelected')) {
  1232. return true;
  1233. // Right click on VM and VM is already selected
  1234. // just return and show context menu
  1235. } else if($(this).hasClass('vboxListItemSelected')) {
  1236. return true;
  1237. }
  1238. }
  1239. var selectedList = [];
  1240. var item = $(this);
  1241. // Group?
  1242. if($(item).hasClass('vboxChooserGroupHeader')) {
  1243. // No control key. Exclusive selection
  1244. if(!e.ctrlKey && !e.metaKey) {
  1245. // already selected
  1246. if(vboxChooser._selectedList.length == 1 && vboxChooser._selectedList[0].type == 'group' &&
  1247. vboxChooser._selectedList[0].groupPath == $(item).parent().data('vmGroupPath'))
  1248. return true;
  1249. vboxChooser._anchor.find('.vboxListItemSelected').removeClass('vboxListItemSelected');
  1250. vboxChooser._anchor.find('div.vboxVMGroupSelected')
  1251. .each(function(idx,gelm) {
  1252. vboxChooser._deselectGroup(gelm);
  1253. });
  1254. // select current group
  1255. vboxChooser._selectGroup($(item).parent());
  1256. selectedList = [{
  1257. type: 'group',
  1258. groupPath: $(item).parent().data('vmGroupPath')
  1259. }];
  1260. // Already selected, and ctrl key
  1261. } else if($(item).parent().hasClass('vboxVMGroupSelected')){
  1262. // Deselect item
  1263. selectedList = vboxChooser._selectedList.filter(function(v){
  1264. return (v.type != 'group' || (v.groupPath != $(item).parent().data('vmGroupPath')));
  1265. });
  1266. vboxChooser._deselectGroup($(item).parent());
  1267. // Not already selected, and ctrl key
  1268. } else {
  1269. vboxChooser._selectGroup($(item).parent());
  1270. selectedList = vboxChooser._selectedList;
  1271. selectedList[selectedList.length] = {
  1272. type: 'group',
  1273. groupPath: $(item).parent().data('vmGroupPath')
  1274. };
  1275. }
  1276. // VM
  1277. } else {
  1278. // No ctrl key or selection is host. Exclusive selection
  1279. if((!e.ctrlKey && !e.metaKey) || $(item).data('vmid') == 'host') {
  1280. vboxChooser._anchor.find('.vboxListItemSelected').removeClass('vboxListItemSelected');
  1281. vboxChooser._anchor.find('div.vboxVMGroupSelected').removeClass('vboxVMGroupSelected')
  1282. .each(function(idx,gelm){
  1283. vboxChooser._deselectGroup(gelm);
  1284. });
  1285. // Select current VM
  1286. $(item).addClass('vboxListItemSelected').removeClass('vboxHover');
  1287. // already selected
  1288. if(vboxChooser._selectedList.length == 1 && vboxChooser._selectedList[0].type == 'vm' &&
  1289. vboxChooser._selectedList[0].id == $(item).data('vmid'))
  1290. return true;
  1291. selectedList = [{
  1292. type: 'vm',
  1293. id: $(item).data('vmid'),
  1294. groupPath: $(item).parent().data('vmGroupPath')
  1295. }];
  1296. // Already selected, and ctrl key
  1297. } else if($(item).hasClass('vboxListItemSelected')) {
  1298. // Deselect item
  1299. selectedList = vboxChooser._selectedList.filter(function(v){
  1300. return (v.type == 'group' || (v.id != $(item).data('vmid')));
  1301. });
  1302. $(item).removeClass('vboxListItemSelected');
  1303. // ctrl key, but not already selected
  1304. } else {
  1305. $(item).addClass('vboxListItemSelected').removeClass('vboxHover');
  1306. selectedList = vboxChooser._selectedList;
  1307. selectedList[selectedList.length] = {
  1308. type: 'vm',
  1309. id: $(item).data('vmid'),
  1310. groupPath: $(item).parent().data('vmGroupPath')
  1311. };
  1312. }
  1313. }
  1314. // Remove host?
  1315. if(selectedList.length > 1) {
  1316. // Deselect host
  1317. selectedList = selectedList.filter(function(v){
  1318. return (v.type == 'group' || (v.id != 'host'));
  1319. });
  1320. vboxChooser._anchor.children('table.vboxChooserItem-'+vboxChooser._anchorid+'-host').removeClass('vboxListItemSelected');
  1321. }
  1322. vboxChooser.selectionListChanged(selectedList);
  1323. return true;
  1324. },
  1325. /*
  1326. * Show only single group element identified by gelm
  1327. */
  1328. showOnlyGroupElm : function(gelm) {
  1329. // Going backwards affects animations
  1330. var back = false;
  1331. // gelm is null if we're going backwards
  1332. if(!gelm) {
  1333. if(vboxChooser._showOnlyGroupHistory.length > 1) {
  1334. // this gets rid of current
  1335. vboxChooser._showOnlyGroupHistory.pop();
  1336. // selects previous
  1337. gelm = vboxChooser._showOnlyGroupHistory.pop();
  1338. back = true;
  1339. } else {
  1340. gelm = null;
  1341. }
  1342. } else {
  1343. // Hold history
  1344. vboxChooser._showOnlyGroupHistory[vboxChooser._showOnlyGroupHistory.length] = gelm;
  1345. }
  1346. // No scrolling
  1347. vboxChooser._anchor.css({'overflow-y':'hidden'});
  1348. if($(gelm)[0]) {
  1349. // Slide over or back
  1350. $.when(vboxChooser._anchor.hide('slide', {direction: (back ? 'right' : 'left'), distance: (vboxChooser._anchor.outerWidth()/1.5)}, 200)).always(function() {
  1351. /* hide host when showing only a group */
  1352. $('table.vboxChooserItem-'+vboxChooser._anchorid+'-host').hide();
  1353. /* Undo anything previously performed by this */
  1354. vboxChooser._anchor.find('div.vboxChooserGroupHide').removeClass('vboxChooserGroupHide vboxChooserGroupHideShowContainer');
  1355. vboxChooser._anchor.find('div.vboxChooserGroupShowOnly').removeClass('vboxChooserGroupShowOnly');
  1356. $(gelm).parents('div.vboxChooserGroup').addClass('vboxChooserGroupHide vboxChooserGroupHideShowContainer').siblings().addClass('vboxChooserGroupHide');
  1357. vboxChooser._anchor.find('div.vboxChooserGroupRootLevel').removeClass('vboxChooserGroupRootLevel');
  1358. $(gelm).addClass('vboxChooserGroupShowOnly vboxChooserGroupRootLevel').siblings().addClass('vboxChooserGroupHide');
  1359. $.when(vboxChooser._anchor.show('slide', {direction: (back ? 'left' : 'right'), distance: (vboxChooser._anchor.outerWidth()/1.5)}, 200))
  1360. .done(function(){
  1361. // Restore scrolling
  1362. vboxChooser._anchor.css({'overflow-y':'auto'});
  1363. // Hide group info
  1364. $(gelm).find('div.vboxChooserGroupHeader').trigger('mouseout');
  1365. // Reset title sizes
  1366. vboxChooser._resizeElements();
  1367. // force redraw of these
  1368. $(gelm).find('.vboxFitToContainer').css({'display':'none'}).css({'width':'','display':''});
  1369. });
  1370. });
  1371. } else {
  1372. vboxChooser._showOnlyGroupHistory = [];
  1373. // Slide back to anchor
  1374. $.when(vboxChooser._anchor.hide('slide', {direction: 'right', distance: (vboxChooser._anchor.outerWidth()/1.5)}, 200)).always(function() {
  1375. /* show host when going back to main list */
  1376. $('table.vboxChooserItem-'+vboxChooser._anchorid+'-host').show();
  1377. vboxChooser._anchor.find('div.vboxChooserGroupHide').removeClass('vboxChooserGroupHide vboxChooserGroupHideShowContainer');
  1378. vboxChooser._anchor.find('div.vboxChooserGroupShowOnly').removeClass('vboxChooserGroupShowOnly ');
  1379. vboxChooser._anchor.find('div.vboxChooserGroupRootLevel').removeClass('vboxChooserGroupRootLevel');
  1380. vboxChooser._anchor.children('div.vboxChooserGroupRoot').addClass('vboxChooserGroupRootLevel');
  1381. $.when(vboxChooser._anchor.show('slide', {direction: 'left', distance: (vboxChooser._anchor.outerWidth()/1.5)}, 200))
  1382. .done(function(){
  1383. // Restore scrolling
  1384. vboxChooser._anchor.css({'overflow-y':'auto'});
  1385. // Hide group info
  1386. vboxChooser._anchor.find('div.vboxChooserGroupHeader').trigger('mouseout');
  1387. // Reset title sizes
  1388. vboxChooser._resizeElements();
  1389. // force redraw of these
  1390. vboxChooser._anchor.find('.vboxFitToContainer').css({'display':'none','width':''}).css({'display':''});
  1391. });
  1392. });
  1393. }
  1394. },
  1395. /*
  1396. * Return HTML for group
  1397. */
  1398. groupHTML : function(gpath) {
  1399. if(!gpath) gpath = '/';
  1400. var first = gpath == '/';
  1401. var gname = gpath.substring(gpath.lastIndexOf('/')+1);
  1402. var collapsed = vboxChooser._isGroupCollapsed(gpath);
  1403. var gHTML = $('<div />').append(
  1404. $('<div />').addClass('vboxChooserGroupIdentifier').css({'display':'none'}).attr({'title':gname})
  1405. ).append(
  1406. $('<div />').addClass('vboxChooserGroupHeader').css({'display':(first ? 'none' : '')})
  1407. .attr({'title':gname})
  1408. .dblclick(function() {
  1409. // Already collapsed?
  1410. var collapsed = $(this).closest('div.vboxChooserGroup').hasClass('vboxVMGroupCollapsed');
  1411. // Button rotation function
  1412. var rotateButton = function(){return true;};
  1413. var vboxArrowImage = $(this).find('span.vboxChooserGroupNameArrowCollapse');
  1414. if(!($.browser.msie && $.browser.version.substring(0,1) < 9)) {
  1415. rotateButton = function() {
  1416. return $('<div />').animate({left:90},{
  1417. duration: 300,
  1418. step: function(currentStep) {
  1419. if(!collapsed) currentStep = (90 - currentStep);
  1420. vboxArrowImage.css({
  1421. 'transform':'rotate('+currentStep+'deg)',
  1422. '-moz-transform': 'rotate('+currentStep+'deg)',
  1423. '-webkit-transform': 'rotate('+currentStep+'deg)',
  1424. '-o-transform': 'rotate('+currentStep+'deg)',
  1425. '-ms-transform': 'rotate('+currentStep+'deg)'
  1426. });
  1427. },
  1428. queue: true,
  1429. complete: function() {
  1430. vboxArrowImage.css({
  1431. 'transform':'',
  1432. '-moz-transform': '',
  1433. '-webkit-transform': '',
  1434. '-o-transform': '',
  1435. '-ms-transform': ''
  1436. });
  1437. }
  1438. });
  1439. };
  1440. }
  1441. // Run button rotation and toggle class
  1442. $.when(rotateButton(), $(this).closest('div.vboxChooserGroup').toggleClass('vboxVMGroupCollapsed', ($.browser.msie && $.browser.version.substring(0,1) < 9) ? undefined : 300)).always(function(){
  1443. // Write out collapsed group list
  1444. vboxChooser._saveCollapsedGroups();
  1445. // Reset title sizes
  1446. vboxChooser._resizeElements();
  1447. });
  1448. })
  1449. .append(
  1450. $('<div />').addClass('vboxChooserDropTarget')
  1451. .addClass('vboxDropTargetTop').hover(function(){
  1452. if(vboxChooser._draggingGroup)
  1453. $(this).addClass('vboxChooserDropTargetHover' + (first ? 'ignore' : ''));
  1454. }, function(){
  1455. $(this).removeClass('vboxChooserDropTargetHover');
  1456. })
  1457. )
  1458. .append(
  1459. $('<span />').addClass('vboxChooserGroupNameArrowLeft vboxChooserGroupNameArrowCollapse vboxArrowImage')
  1460. .mousedown(function(e){
  1461. e.stopPropagation();
  1462. e.preventDefault();
  1463. return false;
  1464. }).mouseup(function(){
  1465. $(this).closest('div.vboxChooserGroupHeader').trigger('dblclick');
  1466. })
  1467. ).append(
  1468. $('<span />').addClass('vboxChooserGroupNameArrowLeft vboxChooserGroupShowOnlyBack vboxArrowImage')
  1469. .click(function(e) {
  1470. e.stopPropagation();
  1471. e.preventDefault();
  1472. vboxChooser.showOnlyGroupElm();
  1473. return false;
  1474. })
  1475. )
  1476. .append($('<span />').addClass('vboxChooserGroupInfo').html(
  1477. "<span class='vboxChooserGroupCounts' />"
  1478. ).append(
  1479. $('<span />').addClass('vboxChooserGroupShowOnly vboxArrowImage')
  1480. .click(function(e){
  1481. e.stopPropagation();
  1482. e.preventDefault();
  1483. vboxChooser.showOnlyGroupElm($(this).closest('div.vboxChooserGroup'));
  1484. return false;
  1485. })
  1486. ))
  1487. .append($('<span />').html(gname).addClass('vboxChooserGroupName vboxFitToContainer'))
  1488. .append(
  1489. $('<div />').addClass('vboxChooserDropTarget vboxChooserDropTargetBottom')
  1490. .hover(function(){
  1491. if(vboxChooser._draggingGroup)
  1492. $(this).addClass('vboxChooserDropTargetHover' + (first ? 'ignore' : ''));
  1493. }, function(){
  1494. $(this).removeClass('vboxChooserDropTargetHover');
  1495. })
  1496. )
  1497. .hover(function(){
  1498. if(vboxChooser._compact) return;
  1499. $(this).addClass('vboxHover');
  1500. // Resize title and add hover class?
  1501. if(!$(this).parent().hasClass('vboxChooserGroupRoot')) {
  1502. // Set width of title to -group info span width
  1503. var infoWidth = $(this).children('span.vboxChooserGroupInfo').width();
  1504. var pWidth = $(this).width();
  1505. $(this).children('span.vboxChooserGroupName').css({'max-width':(pWidth-infoWidth-20)+'px'});
  1506. }
  1507. },function(){
  1508. // Resize title and remove hover class
  1509. $(this).removeClass('vboxHover');
  1510. if(!$(this).parent().hasClass('vboxChooserGroupRoot'))
  1511. $(this).children('span.vboxChooserGroupName').css({'max-width':''});
  1512. }).on('mousedown',vboxChooser.selectItem)
  1513. ).addClass((first ? 'vboxChooserGroupRoot vboxChooserGroupRootLevel ' : (collapsed ? 'vboxVMGroupCollapsed ' : '')) + 'vboxChooserGroup')
  1514. .data({'vmGroupPath':gpath})
  1515. .draggable({'cursorAt':{left: -10, top: -10},'helper':function(){
  1516. return $(this).clone().addClass('vboxVMGroupCollapsed vboxVMGroupSelected')
  1517. .children('div.vboxChooserGroupHeader').removeClass('vboxHover').children('.vboxChooserGroupNameArrowCollapse')
  1518. .hide().closest('div.vboxChooserGroup').css({'width':$(this).width()+'px'});
  1519. },'start':function() {
  1520. if(!$('#vboxPane').data('vboxSession').admin) return false;
  1521. if(!vboxChooser._editable) return false;
  1522. vboxChooser._draggingGroup = $(this).data('vmGroupPath');
  1523. $(vboxChooser._anchor).disableSelection();
  1524. },'stop':function(e) {
  1525. vboxChooser.vmGroupDropped(e,$(this));
  1526. }}).append($('<div />').addClass('vboxChooserGroupVMs'));
  1527. // Bottom drop target
  1528. if(!first) {
  1529. gHTML.hover(function(){
  1530. $(this).addClass('vboxGroupHover'); }, function() {
  1531. $(this).removeClass('vboxGroupHover');
  1532. }).append(
  1533. $('<div />').addClass('vboxChooserDropTarget vboxChooserDropTargetBottom')
  1534. .hover(function(){
  1535. if(vboxChooser._draggingGroup)
  1536. $(this).addClass('vboxChooserDropTargetHover');
  1537. }, function(){
  1538. $(this).removeClass('vboxChooserDropTargetHover');
  1539. })
  1540. );
  1541. }
  1542. // Group context menu
  1543. $(gHTML).contextMenu({
  1544. menu: vboxChooser._vmGroupContextMenuObj.menuId(),
  1545. menusetup: function(el) {
  1546. $(el).children('div.vboxChooserGroupHeader').trigger('click');
  1547. }
  1548. },function(act,el,pos,d,e){
  1549. vboxChooser._vmGroupContextMenuObj.menuClickCallback(act, el);
  1550. });
  1551. return gHTML;
  1552. },
  1553. /*
  1554. * Return selected VM elements
  1555. */
  1556. getSelectedVMElements : function() {
  1557. return vboxChooser._anchor.find('table.vboxSelected');
  1558. },
  1559. /*
  1560. * Return selected group elements
  1561. */
  1562. getSelectedGroupElements : function() {
  1563. return vboxChooser._anchor.find('div.vboxVMGroupSelected');
  1564. },
  1565. /*
  1566. * Start VM list update
  1567. */
  1568. start : function(anchorid) {
  1569. // already running?
  1570. if(vboxChooser._running) return;
  1571. vboxChooser._running = true;
  1572. // Where are we drawn?
  1573. if(anchorid) {
  1574. vboxChooser._anchorid = anchorid;
  1575. vboxChooser._anchor = $('#'+anchorid);
  1576. }
  1577. // Set group definition key
  1578. vboxChooser._groupDefinitionKey = $('#vboxPane').data('vboxConfig')['groupDefinitionKey'];
  1579. // Get collapsed group list
  1580. vboxChooser._collapsedGroups = vboxGetLocalDataItem($('#vboxPane').data('vboxConfig').key+'-collapsedGroups', true);
  1581. if(!vboxChooser._collapsedGroups) vboxChooser._collapsedGroups = [];
  1582. else vboxChooser._collapsedGroups = vboxChooser._collapsedGroups.split(',');
  1583. // Get groups and machine list. datamediator will start listener
  1584. $.when(vboxAjaxRequest('vboxGroupDefinitionsGet')).done(function(g) {
  1585. vboxChooser._groupDefs = g.responseData;
  1586. $.when(vboxVMDataMediator.getVMList()).done(function(d) {
  1587. vboxChooser.updateList(d);
  1588. });
  1589. });
  1590. },
  1591. /*
  1592. * Stop VM list updates and clear list
  1593. */
  1594. stop : function() {
  1595. if(!vboxChooser._running) return;
  1596. vboxChooser._running = false;
  1597. vboxChooser._anchor.html("<div id='vboxChooserSpinner' style='text-align: center'><div><img src='images/spinner.gif' /></div></div>");
  1598. // reset vars
  1599. vboxChooser._versionChecked = false;
  1600. vboxChooser._selectedList = [];
  1601. vboxChooser.selectedVMs = [];
  1602. vboxChooser.selectionMode = vboxSelectionModeNone;
  1603. }
  1604. };
  1605. $(document).ready(function(){
  1606. // Calculate scrollbar width
  1607. vboxChooser._scrollbarWidth = getScrollbarWidth();
  1608. // "Stop" chooser
  1609. $('#vboxPane').on('hostChange',function(){
  1610. vboxChooser.stop();
  1611. }).on('hostChanged',function(){
  1612. vboxChooser.start();
  1613. // Refresh menus
  1614. }).on('vmGroupDefsSaving vmGroupDefsSaved vmSelectionListChanged', function() {
  1615. if(vboxChooser._vmGroupContextMenuObj)
  1616. vboxChooser._vmGroupContextMenuObj.update(vboxChooser);
  1617. if(vboxChooser._vmContextMenuObj)
  1618. vboxChooser._vmContextMenuObj.update(vboxChooser);
  1619. // Event list queue
  1620. }).on('vboxEvents',function(e, eventList) {
  1621. var redrawVMs = [];
  1622. var sortGroups = [];
  1623. var groupsChanged = false;
  1624. var selectedChanged = false;
  1625. var resizeElements = false;
  1626. for(var i = 0; i < eventList.length; i++) {
  1627. switch(eventList[i].eventType) {
  1628. ////////////////////////////////
  1629. //
  1630. // Machine data changed
  1631. //
  1632. ////////////////////////////////
  1633. case 'OnMachineDataChanged':
  1634. // Shorthand
  1635. var vmid = eventList[i].machineId;
  1636. var data = vboxVMDataMediator.getVMData(vmid);
  1637. // Update VM in list
  1638. if(data) {
  1639. // Enforce VM ownership
  1640. if($('#vboxPane').data('vboxConfig').enforceVMOwnership && !$('#vboxPane').data('vboxSession').admin && data.owner != $('#vboxPane').data('vboxSession').user) {
  1641. break;
  1642. }
  1643. redrawVMs[redrawVMs.length] = vmid;
  1644. // Make sure VM has root group at least
  1645. if(data.groups.length == 0) data.groups = ['/'];
  1646. // Remove from groups if they have changed
  1647. var currGroups = vboxChooser.getGroupsForVM(vmid);
  1648. var groupDiff = $(currGroups).not(data.groups);
  1649. groupsChanged = groupDiff.length;
  1650. for(var a = 0; a < groupDiff.length; a++) {
  1651. var gElm = vboxChooser.getGroupElement(groupDiff[a], false);
  1652. if(!$(gElm)[0]) return;
  1653. selectedChanged = (selectedChanged || $(gElm).children('div.vboxChooserGroupVMs').closest('div.vboxVMGroupSelected').length);
  1654. $(gElm).children('div.vboxChooserGroupVMs')
  1655. .children('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+data.id).empty().remove();
  1656. }
  1657. // Add to other groups
  1658. var groupDiff = $(data.groups).not(currGroups);
  1659. groupsChanged = (groupsChanged || groupDiff.length);
  1660. for(var a = 0; a < groupDiff.length; a++) {
  1661. var gElm = vboxChooser.getGroupElement(groupDiff[a]);
  1662. // Skip it if it is already there
  1663. if($(gElm).children('div.vboxChooserGroupVMs').children('table.vboxChooserItem-'+vboxChooser._anchorid+'-'+data.id)[0])
  1664. continue;
  1665. $(gElm).children('div.vboxChooserGroupVMs')
  1666. .append(
  1667. vboxChooser.vmHTML(data)
  1668. );
  1669. selectedChanged = (selectedChanged || $(gElm).children('div.vboxChooserGroupVMs').closest('div.vboxVMGroupSelected').length);
  1670. // Sort the group this machine was added to
  1671. sortGroups = sortGroups.concat(data.groups);
  1672. }
  1673. resizeElements = (resizeElements || groupsChanged);
  1674. }
  1675. break;
  1676. /////////////////////////////////
  1677. //
  1678. // Snapshot taken / deleted / restored
  1679. //
  1680. /////////////////////////////////
  1681. case 'OnSnapshotDeleted':
  1682. case 'OnSnapshotTaken':
  1683. case 'OnSnapshotRestored':
  1684. case 'OnSnapshotChanged':
  1685. redrawVMs[redrawVMs.length] = eventList[i].machineId;
  1686. break;
  1687. /////////////////////////////////////
  1688. //
  1689. // Machine registered or unregistered
  1690. //
  1691. //////////////////////////////////////
  1692. case 'OnMachineRegistered':
  1693. // Shorthand
  1694. var vmid = eventList[i].machineId;
  1695. // Unregistered
  1696. if(!eventList[i].registered) {
  1697. var wasSelected = vboxChooser.isVMSelected(vmid);
  1698. $('#'+vboxChooser._anchorid +' table.vboxChooserItem-'+vboxChooser._anchorid+'-'+vmid).remove();
  1699. groupsChanged = true;
  1700. // See if VM was selected
  1701. if(wasSelected) {
  1702. selectedChanged = true;
  1703. vboxChooser._selectedList = vboxChooser._selectedList.filter(function(v){
  1704. return (v.type == 'group' || (v.id != vmid));
  1705. });
  1706. }
  1707. resizeElements = true;
  1708. break;
  1709. }
  1710. // Registered
  1711. // Enforce VM ownership
  1712. if($('#vboxPane').data('vboxConfig').enforceVMOwnership && !$('#vboxPane').data('vboxSession').admin && eventList[i].enrichmentData.owner != $('#vboxPane').data('vboxSession').user) {
  1713. break;
  1714. }
  1715. // Add to list
  1716. vboxChooser.updateVMElement(eventList[i].enrichmentData, true);
  1717. resizeElements = true;
  1718. break;
  1719. ///////////////////////////////////
  1720. //
  1721. // Extra data changed
  1722. //
  1723. ////////////////////////////////////
  1724. case 'OnExtraDataChanged':
  1725. if(!eventList[i].machineId && eventList[i].key.indexOf(vboxChooser._groupDefinitionKey) === 0) {
  1726. var path = eventList[i].key.substring(vboxChooser._groupDefinitionKey.length);
  1727. if(!path) path = "/";
  1728. var name = path.substring(path.lastIndexOf('/')+1);
  1729. var vboxVMGroups = vboxChooser._groupDefs;
  1730. var found = false;
  1731. // No current group definitions?
  1732. if(!vboxVMGroups) break;
  1733. // Step through each group, comparing
  1734. for(var a = 0; a < vboxVMGroups.length; a++) {
  1735. if(vboxVMGroups[a].path == path) {
  1736. // Sort this group if it is different
  1737. if(vboxVMGroups[a].order != eventList[i].value)
  1738. sortGroups[sortGroups.length] = path;
  1739. found = true;
  1740. vboxVMGroups[a] = {'path':path,'name':name,'order':eventList[i].value};
  1741. break;
  1742. }
  1743. }
  1744. // Add to group if not found
  1745. if(!found) {
  1746. vboxVMGroups[vboxVMGroups.length] = {'path':path,'name':name,'order':eventList[i].value};
  1747. sortGroups[sortGroups.length] = path; // sort when added
  1748. resizeElements = true;
  1749. }
  1750. } else {
  1751. switch(eventList[i].key) {
  1752. // redraw when custom icon changes
  1753. case 'phpvb/icon':
  1754. redrawVMs[redrawVMs.length] = eventList[i].machineId;
  1755. break;
  1756. }
  1757. }
  1758. break;
  1759. ////////////////////////////////////////
  1760. //
  1761. // Session or state change gets redrawn
  1762. //
  1763. ///////////////////////////////////////
  1764. case 'OnSessionStateChanged':
  1765. case 'OnMachineStateChanged':
  1766. redrawVMs[redrawVMs.length] = eventList[i].machineId;
  1767. break;
  1768. } // </ switch eventType >>
  1769. } // </ for each event >
  1770. // Now redraw each VM
  1771. ///////////////////////////
  1772. var redrawn = {};
  1773. var updateMenus = false;
  1774. for(var i = 0; i < redrawVMs.length; i++) {
  1775. if(redrawn[redrawVMs[i]]) continue;
  1776. redrawn[redrawVMs[i]] = true;
  1777. vboxChooser.updateVMElement(vboxVMDataMediator.getVMData(redrawVMs[i]));
  1778. // Update menus if the VM is selected
  1779. updateMenus = (updateMenus || vboxChooser.isVMSelected(redrawVMs[i]));
  1780. }
  1781. // Sort groups
  1782. var groupsSorted = {};
  1783. for(var i = 0; i < sortGroups.length; i++) {
  1784. if(groupsSorted[sortGroups[i]]) continue;
  1785. groupsSorted[sortGroups[i]] = true;
  1786. var gElm = $(vboxChooser.getGroupElement(sortGroups[i]),false);
  1787. if(gElm[0])
  1788. vboxChooser.sortGroup(gElm);
  1789. }
  1790. // Groups changed
  1791. if(groupsChanged || sortGroups.length) {
  1792. vboxChooser.composeGroupDef();
  1793. }
  1794. // update selection list
  1795. if(selectedChanged) {
  1796. vboxChooser.selectionListChanged(vboxChooser._selectedList);
  1797. } else if(updateMenus) {
  1798. if(vboxChooser._vmGroupContextMenuObj)
  1799. vboxChooser._vmGroupContextMenuObj.update(vboxChooser);
  1800. if(vboxChooser._vmContextMenuObj)
  1801. vboxChooser._vmContextMenuObj.update(vboxChooser);
  1802. }
  1803. if(resizeElements) vboxChooser._resizeElements(true);
  1804. });
  1805. });