listdir_components.mako 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  1. ## Licensed to Cloudera, Inc. under one
  2. ## or more contributor license agreements. See the NOTICE file
  3. ## distributed with this work for additional information
  4. ## regarding copyright ownership. Cloudera, Inc. licenses this file
  5. ## to you under the Apache License, Version 2.0 (the
  6. ## "License"); you may not use this file except in compliance
  7. ## with the License. You may obtain a copy of the License at
  8. ##
  9. ## http://www.apache.org/licenses/LICENSE-2.0
  10. ##
  11. ## Unless required by applicable law or agreed to in writing, software
  12. ## distributed under the License is distributed on an "AS IS" BASIS,
  13. ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. ## See the License for the specific language governing permissions and
  15. ## limitations under the License.
  16. <%!
  17. import datetime
  18. import md5
  19. from django.template.defaultfilters import urlencode, stringformat, filesizeformat, date, time, escape
  20. from desktop.lib.django_util import reverse_with_get, extract_field_data
  21. from django.utils.encoding import smart_str
  22. from django.utils.translation import ugettext as _
  23. %>
  24. <%namespace name="edit" file="editor_components.mako" />
  25. <%def name="list_table_chooser(files, path, current_request_path)">
  26. ${_table(files, path, current_request_path, 'chooser')}
  27. </%def>
  28. <%def name="list_table_browser(files, path, current_request_path, cwd_set=True)">
  29. ${_table(files, path, current_request_path, 'view')}
  30. </%def>
  31. <%def name="_table(files, path, current_request_path, view)">
  32. <script src="/static/ext/js/knockout-2.1.0.js" type="text/javascript" charset="utf-8"></script>
  33. <script src="/static/ext/js/moment.min.js" type="text/javascript" charset="utf-8"></script>
  34. <script src="/static/ext/js/datatables-paging-0.1.js" type="text/javascript" charset="utf-8"></script>
  35. <style type="text/css">
  36. .fixed {
  37. position: fixed;
  38. top: 40px;
  39. filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);
  40. -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
  41. -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
  42. box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
  43. }
  44. .pull-right {
  45. margin: 4px;
  46. }
  47. .sortable {
  48. cursor: pointer;
  49. }
  50. .file-row {
  51. height:37px;
  52. }
  53. .fileChooserModal {
  54. padding: 14px;
  55. }
  56. .fileChooserModal .hueBreadcrumb {
  57. margin: 0 0 18px!important;
  58. }
  59. </style>
  60. <table class="table table-striped table-condensed datatables">
  61. <thead>
  62. <tr>
  63. <th width="1%"><div data-bind="click: selectAll, css: {hueCheckbox: true, 'icon-ok': allSelected}"></div></th>
  64. <th class="sortable sorting" data-sort="type" width="4%" data-bind="click: sort">Type</th>
  65. <th class="sortable sorting" data-sort="name" data-bind="click: sort">${_('Name')}</th>
  66. <th class="sortable sorting" data-sort="size" width="10%" data-bind="click: sort">${_('Size')}</th>
  67. <th class="sortable sorting" data-sort="user" width="10%" data-bind="click: sort">${_('User')}</th>
  68. <th class="sortable sorting" data-sort="group" width="10%" data-bind="click: sort">${_('Group')}</th>
  69. <th width="10%">${_('Permissions')}</th>
  70. <th class="sortable sorting" data-sort="mtime" width="15%" data-bind="click: sort">${_('Date')}</th>
  71. </tr>
  72. </thead>
  73. <tbody id="files" data-bind="template: {name: 'fileTemplate', foreach: files}">
  74. </tbody>
  75. <tfoot>
  76. <tr data-bind="visible: isLoading()">
  77. <td colspan="8" class="left">
  78. <img src="/static/art/spinner.gif" />
  79. </td>
  80. </tr>
  81. <tr data-bind="visible: files().length == 0 && !isLoading()">
  82. <td colspan="8">
  83. <div class="alert">
  84. There are no files matching the search criteria.
  85. </div>
  86. </td>
  87. </tr>
  88. </tfoot>
  89. </table>
  90. <script id="fileTemplate" type="text/html">
  91. <tr style="cursor: pointer">
  92. <td class="center" data-bind="click: handleSelect" style="cursor: default">
  93. <div data-bind="visible: name != '..', css: {hueCheckbox: name != '..', 'icon-ok': selected}"></div>
  94. </td>
  95. <td data-bind="click: $root.viewFile" class="left"><i data-bind="css: {'icon-file': type == 'file', 'icon-folder-close': type != 'file'}"></i></td>
  96. <td data-bind="click: $root.viewFile">
  97. <strong><a href="#" data-bind="click: $root.viewFile, text: name"></a></strong>
  98. </td>
  99. <td data-bind="click: $root.viewFile">
  100. <span data-bind="visible: type=='file', text: stats.size"></span>
  101. </td>
  102. <td data-bind="click: $root.viewFile, text: stats.user"></td>
  103. <td data-bind="click: $root.viewFile, text: stats.group"></td>
  104. <td data-bind="click: $root.viewFile, text: permissions"></td>
  105. <td data-bind="click: $root.viewFile, text: stats.mtime" style="white-space: nowrap;"></td>
  106. </tr>
  107. </script>
  108. <div class="pagination" data-bind="visible: !isLoading()">
  109. <ul class="pull-right">
  110. <li class="prev" data-bind="visible: page().number > 1"><a href="#" data-bind="click: firstPage" title="${_('Beginning of List')}">&larr; ${_('Beginning of List')}</a></li>
  111. <li data-bind="visible: page().number > 1"><a href="#" data-bind="click: previousPage" title="${_('Previous Page')}">${_('Previous Page')}</a></li>
  112. <li data-bind="visible: page().number < page().num_pages"><a href="#" data-bind="click: nextPage" title="${_('Next page')}">${_('Next Page')}</a></li>
  113. <li class="next" data-bind="visible: page().number < page().num_pages"><a href="#" data-bind="click: lastPage" title="${_('End of List')}">${_('End of List')} &rarr;</a></li>
  114. </ul>
  115. <p>${_('Show')}
  116. <select class="input-mini" data-bind="options: recordsPerPageChoices, value: recordsPerPage"></select>
  117. ${_('items per page')}.
  118. ${_('Showing')} <span data-bind="text: page().start_index"></span> ${_('to')} <span data-bind="text: page().end_index"></span> ${_('of')} <span data-bind="text: page().total_count"></span> ${_('items, page')}
  119. <span data-bind="text: page().number"></span> ${_('of')} <span data-bind="text: page().num_pages"></span></p>
  120. </div>
  121. <!-- delete modal -->
  122. <div id="deleteModal" class="modal hide fade">
  123. <div class="modal-header">
  124. <a href="#" class="close" data-dismiss="modal">&times;</a>
  125. <h3>${_('Confirm Delete')}</h3>
  126. </div>
  127. <div class="modal-body">
  128. <p>${_('Are you sure you want to delete these files?')}</p>
  129. </div>
  130. <div class="modal-footer">
  131. <form id="deleteForm" action="/filebrowser/rmtree" method="POST" enctype="multipart/form-data" class="form-stacked">
  132. <a id="cancelDeleteBtn" class="btn">${_('No')}</a>
  133. <input type="submit" value="${_('Yes')}" class="btn btn-danger" />
  134. </form>
  135. </div>
  136. </div>
  137. <!-- rename modal -->
  138. <div id="renameModal" class="modal hide fade">
  139. <form id="renameForm" action="/filebrowser/rename?next=${current_request_path}" method="POST" enctype="multipart/form-data" class="form-inline form-padding-fix">
  140. <div class="modal-header">
  141. <a href="#" class="close" data-dismiss="modal">&times;</a>
  142. <h3>${_('Renaming:')} <span id="renameFileName">file name</span></h3>
  143. </div>
  144. <div class="modal-body">
  145. <label>${_('New name')} <input id="newNameInput" name="dest_path" value="" type="text" class="input-xlarge"/></label>
  146. </div>
  147. <div class="modal-footer">
  148. <div id="renameNameRequiredAlert" class="hide" style="position: absolute; left: 10;">
  149. <span class="label label-important">${_('Name is required.')}</span>
  150. </div>
  151. <input id="renameSrcPath" type="hidden" name="src_path" type="text">
  152. <a id="cancelRenameBtn" class="btn">${_('Cancel')}</a>
  153. <input type="submit" value="${_('Submit')}" class="btn btn-primary" />
  154. </div>
  155. </form>
  156. </div>
  157. <!-- chown modal -->
  158. % if is_superuser:
  159. <div id="changeOwnerModal" class="modal hide fade">
  160. <%
  161. select_filter = is_superuser and 'SelectWithOther' or ''
  162. %>
  163. <form id="chownForm" action="/filebrowser/chown" method="POST" enctype="multipart/form-data" class="form-stacked form-padding-fix">
  164. <div class="modal-header">
  165. <a href="#" class="close" data-dismiss="modal">&times;</a>
  166. <h3>${_('Change Owner / Group')}</h3>
  167. </div>
  168. <div class="modal-body change-owner-modal-body clearfix" >
  169. <div class="alert alert-message block-message info">${_('Note: Only the Hadoop superuser, "%(superuser)s" on this file system, may change the owner of a file.') % dict(superuser=superuser)}</div>
  170. <div style="padding-left: 15px; padding-bottom: 10px;">
  171. <label>${_('User')}</label>
  172. ${ edit.selection("user", users, user.username, "user_other") }
  173. <label>${_('Group')}</label>
  174. ${ edit.selection("group", groups, 'supergroup', "group_other") }
  175. <br />
  176. <label style="display: inline;">${_('Recursive')}</label><input type="checkbox" name="recursive">
  177. </div>
  178. </div>
  179. <div class="modal-footer" style="padding-top: 10px;">
  180. <div id="chownRequired" class="hide" style="position: absolute; left: 10;">
  181. <span class="label label-important">${_('Name is required.')}</span>
  182. </div>
  183. <a class="btn" onclick="$('#changeOwnerModal').modal('hide');">${_('Cancel')}</a>
  184. <input class="btn btn-primary" type="submit" value="${_('Submit')}" />
  185. </div>
  186. </form>
  187. </div>
  188. % endif
  189. <!-- chmod modal -->
  190. <div id="changePermissionModal" class="modal hide fade">
  191. <form action="/filebrowser/chmod" method="POST" enctype="multipart/form-data" class="form-inline form-padding-fix" id="chmodForm">
  192. <div class="modal-header">
  193. <a href="#" class="close" data-dismiss="modal">&times;</a>
  194. <h3>${_('Change Permissions:')} </h3>
  195. </div>
  196. <div class="modal-body table-margin">
  197. <table class="table table-striped">
  198. <thead>
  199. <tr>
  200. <th>&nbsp;</th>
  201. <th class="center">${_('User')}</th>
  202. <th class="center">${_('Group')}</th>
  203. <th class="center">${_('Other')}</th>
  204. <th class="center">&nbsp;</th>
  205. <th width="120">&nbsp</th>
  206. </tr>
  207. </thead>
  208. <tbody>
  209. <tr>
  210. <td><strong>${_('Read')}</strong></td>
  211. <td class="center"><input type="checkbox" data-bind="attr: {checked: selectedFile.mode }" checked="" name="user_read"></td>
  212. <td class="center"><input type="checkbox" data-bind="attr: {checked: selectedFile.mode }" checked="" name="group_read"></td>
  213. <td class="center"><input type="checkbox" data-bind="attr: {checked: selectedFile.mode }" checked="" name="other_read"></td>
  214. <td colspan="2">&nbsp;</td>
  215. </tr>
  216. <tr>
  217. <td><strong>${_('Write')}</strong></td>
  218. <td class="center"><input type="checkbox" data-bind="attr: {checked: selectedFile.mode }" checked="" name="user_write"></td>
  219. <td class="center"><input type="checkbox" data-bind="attr: {checked: selectedFile.mode }" checked="" name="group_write"></td>
  220. <td class="center"><input type="checkbox" data-bind="attr: {checked: selectedFile.mode }" checked="" name="other_write"></td>
  221. <td colspan="2">&nbsp;</td>
  222. </tr>
  223. <tr>
  224. <td><strong>${_('Execute')}</strong></td>
  225. <td class="center"><input type="checkbox" checked="" name="user_execute"></td>
  226. <td class="center"><input type="checkbox" checked="" name="group_execute"></td>
  227. <td class="center"><input type="checkbox" checked="" name="other_execute"></td>
  228. <td colspan="2">&nbsp;</td>
  229. </tr>
  230. <tr>
  231. <td><strong>${_('Sticky')}</strong></td>
  232. <td colspan="3">&nbsp;</td>
  233. <td class="center"><input type="checkbox" name="sticky"></td>
  234. <td>&nbsp;</td>
  235. </tr>
  236. <tr>
  237. <td><strong>${_('Recursive')}</strong></td>
  238. <td colspan="3">&nbsp;</td>
  239. <td class="center"><input type="checkbox" name="recursive"></td>
  240. <td>&nbsp;</td>
  241. </tbody>
  242. </table>
  243. </div>
  244. <div class="modal-footer" style="padding-top: 10px;">
  245. <a class="btn" onclick="$('#changePermissionModal').modal('hide');">${_('Cancel')}</a>
  246. <input class="btn btn-primary" type="submit" value="${_('Submit')}"/>
  247. </div>
  248. </form>
  249. </div>
  250. <!-- move modal -->
  251. <div id="moveModal" class="modal hide fade">
  252. <form id="moveForm" action="/filebrowser/move" method="POST" enctype="multipart/form-data" class="form-inline form-padding-fix">
  253. <div class="modal-header">
  254. <a href="#" class="close" data-dismiss="modal">&times;</a>
  255. <h3>${_('Move:')}</h3>
  256. </div>
  257. <div class="modal-body">
  258. <div style="padding-left: 15px;">
  259. <label for="moveDestination">${_('Destination')}</label>
  260. <input type="text" class="input-xlarge pathChooser" value="" name="dest_path" id="moveDestination" /><a class="btn fileChooserBtn" href="#" data-filechooser-destination="dest_path">..</a>
  261. </div>
  262. <br/>
  263. <div class="fileChooserModal" class="hide">
  264. </div>
  265. </div>
  266. <div class="modal-footer">
  267. <div id="moveNameRequiredAlert" class="hide" style="position: absolute; left: 10;">
  268. <span class="label label-important">${_('Name is required.')}</span>
  269. </div>
  270. <a class="btn" onclick="$('#moveModal').modal('hide');">${_('Cancel')}</a>
  271. <input class="btn btn-primary" type="submit" value="${_('Submit')}"/>
  272. </div>
  273. </form>
  274. </div>
  275. <!-- upload file modal -->
  276. <div id="uploadFileModal" class="modal hide fade">
  277. <div class="modal-header">
  278. <a href="#" class="close" data-dismiss="modal">&times;</a>
  279. <h3>${_('Uploading to:')} <span id="uploadDirName" data-bind="text: currentPath"></span></h3>
  280. </div>
  281. <div class="modal-body form-inline">
  282. <div id="fileUploader" class="uploader">
  283. <noscript>
  284. <p>${_('Enable JavaScript to use the file uploader.')}</p>
  285. </noscript>
  286. </div>
  287. </div>
  288. <div class="modal-footer"></div>
  289. </div>
  290. <!-- upload archive modal -->
  291. <div id="uploadArchiveModal" class="modal hide fade">
  292. <div class="modal-header">
  293. <a href="#" class="close" data-dismiss="modal">&times;</a>
  294. <h3>${_('Uploading to:')} <span id="uploadDirName" data-bind="text: currentPath"></span></h3>
  295. <p>${_('The file will then be extracted in the path specified above.')}</p>
  296. </div>
  297. <div class="modal-body form-inline">
  298. <div id="archiveUploader" class="uploader">
  299. <noscript>
  300. <p>${_('Enable JavaScript to use the file uploader.')}</p>
  301. </noscript>
  302. </div>
  303. </div>
  304. <div class="modal-footer"></div>
  305. </div>
  306. <!-- new directory modal -->
  307. <div id="createDirectoryModal" class="modal hide fade">
  308. <form id="createDirectoryForm" data-bind="submit: createDirectory" method="POST" enctype="multipart/form-data" class="form-inline form-padding-fix">
  309. <div class="modal-header">
  310. <a href="#" class="close" data-dismiss="modal">&times;</a>
  311. <h3>${_('Create Directory')}</h3>
  312. </div>
  313. <div class="modal-body">
  314. <label>${_('Directory Name')} <input id="newDirectoryNameInput" name="name" value="" type="text" class="input-xlarge"/></label>
  315. <input type="hidden" name="path" type="text" data-bind="value: currentPath"/>
  316. </div>
  317. <div class="modal-footer">
  318. <div id="directoryNameRequiredAlert" class="alert-message error hide" style="position: absolute; left: 10;">
  319. <p><strong>${_('Directory name is required.')}</strong>
  320. </div>
  321. <a id="cancelCreateDirectoryBtn" class="btn" href="#">${_('Cancel')}</a>
  322. <input class="btn btn-primary" type="submit" value="${_('Submit')}" />
  323. </div>
  324. </form>
  325. </div>
  326. <!-- new directory modal -->
  327. <div id="createFileModal" class="modal hide fade">
  328. <form id="createFileForm" data-bind="submit: createFile" method="POST" enctype="multipart/form-data" class="form-inline form-padding-fix">
  329. <div class="modal-header">
  330. <a href="#" class="close" data-dismiss="modal">&times;</a>
  331. <h3>${_('Create File')}</h3>
  332. </div>
  333. <div class="modal-body">
  334. <label>${_('File Name')} <input id="newFileNameInput" name="name" value="" type="text" class="input-xlarge"/></label>
  335. <input type="hidden" name="path" type="text" data-bind="value: currentPath"/>
  336. </div>
  337. <div class="modal-footer">
  338. <div id="fileNameRequiredAlert" class="alert-message error hide" style="position: absolute; left: 10;">
  339. <p><strong>${_('File name is required.')}</strong>
  340. </div>
  341. <a id="cancelCreateFileBtn" class="btn" href="#">${_('Cancel')}</a>
  342. <input class="btn btn-primary" type="submit" value="${_('Submit')}" />
  343. </div>
  344. </form>
  345. </div>
  346. <script type="text/javascript" charset="utf-8">
  347. // ajax modal windows
  348. function openChownWindow(path, user, group, next){
  349. $.ajax({
  350. url: "/filebrowser/chown",
  351. data: {"path":path, "user":user, "group" : group, "next" : next},
  352. beforeSend: function(xhr){
  353. xhr.setRequestHeader("X-Requested-With", "Hue");
  354. },
  355. dataType: "html",
  356. success: function(data){
  357. $("#changeOwnerModal").html(data);
  358. $("#changeOwnerModal").modal({
  359. keyboard: true,
  360. show: true
  361. });
  362. }
  363. });
  364. }
  365. // Modal file chooser
  366. // The file chooser should be at least 2 levels deeper than the modal container
  367. $(".fileChooserBtn").on('click', function(e){
  368. e.preventDefault();
  369. var _destination = $(this).attr("data-filechooser-destination");
  370. var fileChooser = $(this).parent().parent().find(".fileChooserModal");
  371. fileChooser.jHueFileChooser({
  372. initialPath: $("input[name='"+_destination+"']").val(),
  373. onFolderChange: function(folderPath){
  374. $("input[name='"+_destination+"']").val(folderPath);
  375. },
  376. onFolderChoose: function(folderPath){
  377. $("input[name='"+_destination+"']").val(folderPath);
  378. fileChooser.slideUp();
  379. },
  380. selectFolder: true,
  381. createFolder: true,
  382. uploadFile: false
  383. });
  384. fileChooser.slideDown();
  385. });
  386. $(document).ready(function(){
  387. $("#chownForm select[name='user']").change(function(){
  388. if ($(this).val() == "__other__"){
  389. $("input[name='user_other']").show();
  390. }
  391. else {
  392. $("input[name='user_other']").hide();
  393. }
  394. });
  395. $("#chownForm select[name='group']").change(function(){
  396. if ($(this).val() == "__other__"){
  397. $("input[name='group_other']").show();
  398. }
  399. else {
  400. $("input[name='group_other']").hide();
  401. }
  402. });
  403. $("#chownForm").submit(function(){
  404. if ($("#chownForm select[name='user']").val() == null){
  405. $("#chownRequired").find(".label").text("${_('User is required.')}");
  406. $("#chownRequired").show();
  407. return false;
  408. }
  409. else if ($("#chownForm select[name='group']").val() == null){
  410. $("#chownRequired").find(".label").text("${_('Group is required.')}");
  411. $("#chownRequired").show();
  412. return false;
  413. }
  414. else {
  415. if ($("#chownForm select[name='group']").val() == "__other__" && $("input[name='group_other']").val() == ""){
  416. $("#chownRequired").find(".label").text("${_('Specify another group.')}");
  417. $("#chownForm input[name='group_other']").addClass("fieldError");
  418. $("#chownRequired").show();
  419. return false;
  420. }
  421. if ($("#chownForm select[name='user']").val() == "__other__" && $("input[name='user_other']").val() == ""){
  422. $("#chownRequired").find(".label").text("${_('Specify another user.')}");
  423. $("#chownForm input[name='user_other']").addClass("fieldError");
  424. $("#chownRequired").show();
  425. return false;
  426. }
  427. return true;
  428. }
  429. });
  430. $("#cancelDeleteBtn").click(function(){
  431. $("#deleteModal").modal("hide");
  432. });
  433. $("#cancelRenameBtn").click(function(){
  434. $("#renameModal").modal("hide");
  435. });
  436. $("#renameForm").submit(function(){
  437. if($("#newNameInput").val() == ""){
  438. $("#renameNameRequiredAlert").show();
  439. $("#newNameInput").addClass("fieldError");
  440. return false;
  441. }
  442. });
  443. $("#newNameInput").focus(function(){
  444. $("#renameNameRequiredAlert").hide();
  445. $("#newNameInput").removeClass("fieldError");
  446. });
  447. $("#moveForm").live("submit", function(){
  448. if ($.trim($("#moveForm").find("input.pathChooser").val()) == ""){
  449. $("#moveNameRequiredAlert").show();
  450. $("#moveForm").find("input[name='*dest_path']").addClass("fieldError");
  451. return false;
  452. }
  453. return true;
  454. });
  455. $("#moveForm").find("input[name='dest_path']").live("focus", function(){
  456. $("#moveNameRequiredAlert").hide();
  457. $("#moveForm").find("input[name='dest_path']").removeClass("fieldError");
  458. });
  459. $(".create-directory-link").click(function(){
  460. $("#createDirectoryModal").modal({
  461. keyboard: true,
  462. show: true
  463. });
  464. });
  465. $(".create-file-link").click(function(){
  466. $("#createFileModal").modal({
  467. keyboard: true,
  468. show: true
  469. });
  470. });
  471. $("#cancelCreateDirectoryBtn").click(function(){
  472. $("#createDirectoryModal").modal("hide");
  473. });
  474. $("#cancelCreateFileBtn").click(function(){
  475. $("#createFileModal").modal("hide");
  476. });
  477. $("#createDirectoryForm").submit(function(){
  478. if ($.trim($("#newDirectoryNameInput").val())==""){
  479. $("#directoryNameRequiredAlert").show();
  480. $("#newDirectoryNameInput").addClass("fieldError");
  481. return false;
  482. }
  483. return true;
  484. });
  485. $("#newDirectoryNameInput").focus(function(){
  486. $("#newDirectoryNameInput").removeClass("fieldError");
  487. $("#directoryNameRequiredAlert").hide();
  488. });
  489. $(".pathChooser").click(function(){
  490. var self = this;
  491. $("#fileChooserRename").jHueFileChooser({
  492. initialPath: $(self).val(),
  493. onFileChoose: function(filePath) {
  494. $(self).val(filePath);
  495. },
  496. onFolderChange: function(folderPath){
  497. $(self).val(folderPath);
  498. },
  499. createFolder: false,
  500. uploadFile: false
  501. });
  502. $("#fileChooserRename").slideDown();
  503. });
  504. $("*[rel='tooltip']").tooltip({ placement: "bottom" });
  505. if (window.location.hash != null && window.location.hash.length > 1){
  506. viewModel.targetPath("${url('filebrowser.views.view', path=urlencode('/'))}" + stripHashes(window.location.hash.substring(2)));
  507. }
  508. viewModel.retrieveData();
  509. var filterTimeout = -1;
  510. $(".search-query").keyup(function(){
  511. window.clearTimeout(filterTimeout);
  512. filterTimeout = window.setTimeout(function(){
  513. viewModel.searchQuery($(".search-query").val());
  514. viewModel.filter();
  515. }, 500);
  516. });
  517. $("#editBreadcrumb").click(function(){
  518. $(this).hide();
  519. $(".hueBreadcrumb").hide();
  520. $("#hueBreadcrumbText").show().focus();
  521. });
  522. $("#hueBreadcrumbText").keyup(function(e){
  523. if (e.keyCode == 13) {
  524. var _el = $(this);
  525. viewModel.targetPath("${url('filebrowser.views.view', path=urlencode('/'))}" + stripHashes(_el.val().substring(1)));
  526. viewModel.getStats(function (data) {
  527. if (data.type != null && data.type == "file") {
  528. location.href = data.url;
  529. return false;
  530. }
  531. else {
  532. window.location.hash = stripHashes(_el.val());
  533. }
  534. });
  535. }
  536. });
  537. $("#hueBreadcrumbText").blur(function(){
  538. $(this).hide();
  539. $(".hueBreadcrumb").show();
  540. $("#editBreadcrumb").show();
  541. });
  542. $.ajaxSetup({
  543. error: function(x, e) {
  544. if (x.status == 500) {
  545. $.jHueNotify.error("${_('There was a problem with your request.')}");
  546. $("#hueBreadcrumbText").blur();
  547. }
  548. }
  549. });
  550. $(window).bind("hashchange", function() {
  551. var target = "";
  552. var hash = window.location.hash.substring(1);
  553. if (hash != null && hash != "") {
  554. target = "${url('filebrowser.views.view', path=urlencode('/'))}" + stripHashes(hash.substring(1));
  555. }
  556. if (window.location.href.indexOf("#") == -1){
  557. target = "${current_request_path}";
  558. }
  559. if (target != ""){
  560. viewModel.targetPath(target);
  561. viewModel.retrieveData();
  562. }
  563. });
  564. });
  565. function stripHashes(str) {
  566. return str.replace(/#/gi, encodeURIComponent("#"));
  567. }
  568. var Page = function (page) {
  569. if (page != null) {
  570. return {
  571. number:page.number,
  572. num_pages:page.num_pages,
  573. previous_page_number:page.previous_page_number,
  574. next_page_number:page.next_page_number,
  575. start_index:page.start_index,
  576. end_index:page.end_index,
  577. total_count:page.total_count
  578. }
  579. }
  580. return {
  581. }
  582. }
  583. var File = function (file) {
  584. return {
  585. name:file.name,
  586. path:file.path,
  587. url:file.url,
  588. type:file.type,
  589. permissions:file.rwx,
  590. mode:file.mode,
  591. stats:{
  592. size:file.humansize,
  593. user:file.stats.user,
  594. group:file.stats.group,
  595. mtime:moment.unix(file.stats.mtime).format("MMMM DD, YYYY hh:mm a")
  596. },
  597. selected:ko.observable(false),
  598. handleSelect:function (row, e) {
  599. this.selected(!this.selected());
  600. }
  601. }
  602. }
  603. var Breadcrumb = function (breadcrumb) {
  604. return {
  605. url:breadcrumb.url,
  606. label:breadcrumb.label,
  607. show:function () {
  608. if (this.url == null || this.url == ""){
  609. // forcing root on empty breadcrumb url
  610. this.url = "/";
  611. }
  612. viewModel.targetPath("${url('filebrowser.views.view', path=urlencode('/'))}" + stripHashes(this.url));
  613. window.location.hash = this.url;
  614. }
  615. }
  616. }
  617. var FileBrowserModel = function (files, page, breadcrumbs, currentDirPath) {
  618. var self = this;
  619. self.page = ko.observable(new Page(page));
  620. self.recordsPerPageChoices = ["15", "30", "45", "60", "100", "200"],
  621. self.recordsPerPage = ko.observable("30");
  622. self.targetPageNum = ko.observable(1);
  623. self.targetPath = ko.observable("${current_request_path}");
  624. self.sortBy = ko.observable("name");
  625. self.sortDescending = ko.observable(false);
  626. self.searchQuery = ko.observable("");
  627. self.files = ko.observableArray(ko.utils.arrayMap(files, function (file) {
  628. return new File(file);
  629. }));
  630. self.breadcrumbs = ko.observableArray(ko.utils.arrayMap(breadcrumbs, function (breadcrumb) {
  631. return new Breadcrumb(breadcrumb);
  632. }));
  633. self.sort = function (viewModel, event) {
  634. var el = $(event.currentTarget);
  635. el.siblings(".sortable").attr("class", "sortable sorting");
  636. self.sortBy(el.data("sort"))
  637. el.removeClass("sorting");
  638. if (el.hasClass("sorting_asc")) {
  639. self.sortDescending(true);
  640. }
  641. else {
  642. self.sortDescending(false);
  643. }
  644. el.attr("class", "sortable");
  645. if (self.sortDescending() == true) {
  646. el.addClass("sorting_desc");
  647. }
  648. else {
  649. el.addClass("sorting_asc");
  650. }
  651. self.retrieveData();
  652. }
  653. self.isLoading = ko.observable(true);
  654. self.allSelected = ko.observable(false);
  655. self.selectedFiles = ko.computed(function () {
  656. return ko.utils.arrayFilter(self.files(), function (file) {
  657. return file.selected();
  658. });
  659. }, self);
  660. self.selectedFile = ko.computed(function () {
  661. return self.selectedFiles()[0];
  662. }, self);
  663. self.currentPath = ko.observable(currentDirPath);
  664. self.getStats = function (callback) {
  665. $.getJSON(self.targetPath() + "?pagesize=1&format=json", callback);
  666. }
  667. self.retrieveData = function () {
  668. self.isLoading(true);
  669. $.getJSON(self.targetPath() + "?pagesize=" + self.recordsPerPage() + "&pagenum=" + self.targetPageNum() + "&filter=" + self.searchQuery() + "&sortby=" + self.sortBy() + "&descending=" + self.sortDescending() + "&format=json", function (data) {
  670. if (data.type != null && data.type == "file"){
  671. location.href = data.url;
  672. return false;
  673. }
  674. self.updateFileList(data.files, data.page, data.breadcrumbs, data.current_dir_path);
  675. if ($("#hueBreadcrumbText").is(":visible")){
  676. $(".hueBreadcrumb").show();
  677. $("#hueBreadcrumbText").hide();
  678. $("#editBreadcrumb").show();
  679. }
  680. });
  681. };
  682. self.updateFileList = function (files, page, breadcrumbs, currentDirPath) {
  683. self.page(new Page(page));
  684. self.files(ko.utils.arrayMap(files, function (file) {
  685. return new File(file);
  686. }));
  687. self.breadcrumbs(ko.utils.arrayMap(breadcrumbs, function (breadcrumb) {
  688. return new Breadcrumb(breadcrumb);
  689. }));
  690. self.currentPath(currentDirPath);
  691. $('.uploader').trigger('fb:updatePath', {dest: self.currentPath()});
  692. self.isLoading(false);
  693. $(".scrollable").jHueTableScroller();
  694. };
  695. self.recordsPerPage.subscribe(function (newValue) {
  696. self.retrieveData();
  697. });
  698. self.goToPage = function (pageNumber) {
  699. self.targetPageNum(pageNumber);
  700. self.retrieveData();
  701. };
  702. self.firstPage = function () {
  703. self.goToPage(1)
  704. };
  705. self.previousPage = function () {
  706. self.goToPage(self.page().previous_page_number)
  707. };
  708. self.nextPage = function () {
  709. self.goToPage(self.page().next_page_number)
  710. };
  711. self.lastPage = function () {
  712. self.goToPage(self.page().num_pages)
  713. };
  714. self.selectAll = function () {
  715. self.allSelected(!self.allSelected());
  716. ko.utils.arrayForEach(self.files(), function (file) {
  717. if (file.name != "..") {
  718. file.selected(self.allSelected());
  719. }
  720. });
  721. return true;
  722. };
  723. self.searchQuery.subscribe(function (newValue) {
  724. self.filter();
  725. });
  726. self.filter = function () {
  727. self.targetPageNum(1);
  728. self.retrieveData();
  729. };
  730. self.viewFile = function (file) {
  731. if (file.type == "dir") {
  732. self.targetPath("${url('filebrowser.views.view', path=urlencode('/'))}" + "." + stripHashes(file.path));
  733. window.location.hash = stripHashes(file.path);
  734. }
  735. else {
  736. location.href = "${url('filebrowser.views.view', path=urlencode('/'))}" + stripHashes(file.path);
  737. }
  738. };
  739. self.editFile = function () {
  740. location.href = "${url('filebrowser.views.edit', path=urlencode('/'))}" + self.selectedFile().path;
  741. };
  742. self.downloadFile = function () {
  743. location.href = "${url('filebrowser.views.download', path=urlencode('/'))}" + self.selectedFile().path;
  744. };
  745. self.renameFile = function () {
  746. $("#renameSrcPath").attr("value", self.selectedFile().path);
  747. $("#renameFileName").text(self.selectedFile().path);
  748. $("#newNameInput").val(self.selectedFile().name);
  749. $("#renameForm").attr("action", "/filebrowser/rename?next=${url('filebrowser.views.view', path=urlencode('/'))}"+ "." + self.currentPath());
  750. $("#renameModal").modal({
  751. keyboard:true,
  752. show:true
  753. });
  754. };
  755. self.move = function () {
  756. var paths = [];
  757. $(self.selectedFiles()).each(function(index, file) {
  758. paths.push(file.path);
  759. });
  760. hiddenFields($("#moveForm"), "src_path", paths);
  761. $("#moveForm").attr("action", "/filebrowser/move?next=${url('filebrowser.views.view', path=urlencode('/'))}" + "." + self.currentPath());
  762. $("#moveModal").modal({
  763. keyboard:true,
  764. show:true
  765. });
  766. };
  767. self.changeOwner = function () {
  768. var paths = [];
  769. $(self.selectedFiles()).each(function(index, file) {
  770. paths.push(file.path);
  771. });
  772. hiddenFields($("#chownForm"), 'path', paths);
  773. $("#chownForm").attr("action", "/filebrowser/chown?next=${url('filebrowser.views.view', path=urlencode('/'))}" + "." + self.currentPath());
  774. $("#changeOwnerModal").modal({
  775. keyboard:true,
  776. show:true
  777. });
  778. };
  779. self.changePermissions = function () {
  780. var paths = [];
  781. $(self.selectedFiles()).each(function(index, file) {
  782. paths.push(file.path);
  783. });
  784. hiddenFields($("#chmodForm"), 'path', paths);
  785. $("#chmodForm").attr("action", "/filebrowser/chmod?next=${url('filebrowser.views.view', path=urlencode('/'))}" + "." + self.currentPath());
  786. $("#changePermissionModal").modal({
  787. keyboard:true,
  788. show:true
  789. });
  790. // Initial values for form
  791. permissions = ["sticky", "user_read", "user_write", "user_execute", "group_read", "group_write", "group_execute", "other_read", "other_write", "other_execute"].reverse();
  792. var mode = octal(self.selectedFile().mode) & 01777;
  793. for (var i = 0; i < permissions.length; i++) {
  794. if (mode & 1) {
  795. $("#chmodForm input[name=" + permissions[i] + "]").attr("checked", true);
  796. } else {
  797. $("#chmodForm input[name=" + permissions[i] + "]").attr("checked", false);
  798. }
  799. mode >>>= 1;
  800. }
  801. };
  802. self.deleteSelected = function () {
  803. var paths = [];
  804. $(self.selectedFiles()).each(function(index, file) {
  805. paths.push(file.path);
  806. });
  807. hiddenFields($("#deleteForm"), 'path', paths);
  808. $("#deleteForm").attr("action", "/filebrowser/rmtree" + "?next=${url('filebrowser.views.view', path=urlencode('/'))}" + "." + self.currentPath());
  809. $("#deleteModal").modal({
  810. keyboard:true,
  811. show:true
  812. });
  813. };
  814. self.createDirectory = function (formElement) {
  815. $(formElement).attr("action", "/filebrowser/mkdir?next=${url('filebrowser.views.view', path=urlencode('/'))}"+ "." + self.currentPath());
  816. return true;
  817. };
  818. self.createFile = function (formElement) {
  819. $(formElement).attr("action", "/filebrowser/touch?next=${url('filebrowser.views.view', path=urlencode('/'))}"+ "." + self.currentPath());
  820. return true;
  821. };
  822. self.uploadFile = (function() {
  823. var num_of_pending_uploads = 0;
  824. var uploader = new qq.FileUploader({
  825. element: document.getElementById("fileUploader"),
  826. action: "/filebrowser/upload/file",
  827. template: '<div class="qq-uploader">' +
  828. '<div class="qq-upload-drop-area"><span>${_('Drop files here to upload')}</span></div>' +
  829. '<div class="qq-upload-button">${_('Upload a file')}</div>' +
  830. '<ul class="qq-upload-list"></ul>' +
  831. '</div>',
  832. fileTemplate: '<li>' +
  833. '<span class="qq-upload-file"></span>' +
  834. '<span class="qq-upload-spinner"></span>' +
  835. '<span class="qq-upload-size"></span>' +
  836. '<a class="qq-upload-cancel" href="#">${_('Cancel')}</a>' +
  837. '<span class="qq-upload-failed-text">${_('Failed')}</span>' +
  838. '</li>',
  839. params:{
  840. dest: self.currentPath(),
  841. fileFieldLabel: "hdfs_file"
  842. },
  843. onComplete:function(id, fileName, responseJSON){
  844. num_of_pending_uploads--;
  845. if(num_of_pending_uploads == 0){
  846. window.location = "/filebrowser/view" + self.currentPath();
  847. }
  848. },
  849. onSubmit:function(id, fileName, responseJSON){
  850. num_of_pending_uploads++;
  851. },
  852. debug: false
  853. });
  854. $("#archiveUploader").on('fb:updatePath', function(e, options) {
  855. uploader.setParams({
  856. dest: options.dest,
  857. fileFieldLabel: "hdfs_file"
  858. });
  859. });
  860. return function() {
  861. $("#uploadFileModal").modal({
  862. keyboard: true,
  863. show: true
  864. });
  865. };
  866. })();
  867. self.uploadArchive = (function() {
  868. var num_of_pending_uploads = 0;
  869. var uploader = new qq.FileUploader({
  870. element: document.getElementById("archiveUploader"),
  871. action: "/filebrowser/upload/archive",
  872. template: '<div class="qq-uploader">' +
  873. '<div class="qq-upload-drop-area"><span>${_('Drop files here to upload')}</span></div>' +
  874. '<div class="qq-upload-button">${_('Upload an archive')}</div>' +
  875. '<ul class="qq-upload-list"></ul>' +
  876. '</div>',
  877. fileTemplate: '<li>' +
  878. '<span class="qq-upload-file"></span>' +
  879. '<span class="qq-upload-spinner"></span>' +
  880. '<span class="qq-upload-size"></span>' +
  881. '<a class="qq-upload-cancel" href="#">${_('Cancel')}</a>' +
  882. '<span class="qq-upload-failed-text">${_('Failed')}</span>' +
  883. '</li>',
  884. params:{
  885. dest: self.currentPath(),
  886. fileFieldLabel: "archive"
  887. },
  888. onComplete:function(id, fileName, responseJSON){
  889. num_of_pending_uploads--;
  890. if(num_of_pending_uploads == 0){
  891. window.location = "/filebrowser/view" + self.currentPath();
  892. }
  893. },
  894. onSubmit:function(id, fileName, responseJSON){
  895. num_of_pending_uploads++;
  896. },
  897. debug: false
  898. });
  899. $("#archiveUploader").on('fb:updatePath', function(e, options) {
  900. uploader.setParams({
  901. dest: options.dest,
  902. fileFieldLabel: "archive"
  903. });
  904. });
  905. return function() {
  906. $("#uploadArchiveModal").modal({
  907. keyboard: true,
  908. show: true
  909. });
  910. };
  911. })();
  912. // Place all values into hidden fields under parent element.
  913. // Looks for managed hidden fields and handles sizing appropriately.
  914. var hiddenFields = function(parentEl, name, values) {
  915. parentEl = $(parentEl);
  916. var fields = parentEl.find("input.hidden-field");
  917. // Create or delete hidden fields according to needs.
  918. var resize = values.length - fields.length;
  919. while(resize > 0) {
  920. resize--;
  921. var field = $("<input type='hidden' />");
  922. field.attr("name", name);
  923. field.attr("class", "hidden-field")
  924. parentEl.append(field);
  925. }
  926. while (resize < 0) {
  927. resize++;
  928. var field = fields[fields.length - resize - 1]
  929. parentEl.remove(field);
  930. }
  931. // Set values
  932. fields = parentEl.find("input.hidden-field");
  933. $(values).each(function(index, value) {
  934. $(fields[index]).val(value);
  935. });
  936. }
  937. var octal = function(strInt) {
  938. return parseInt("0" + strInt);
  939. }
  940. };
  941. var viewModel = new FileBrowserModel([], null, [], "/");
  942. ko.applyBindings(viewModel);
  943. </script>
  944. </%def>