// vim: set sw=4 ts=4 et ft=js /* performs AJAX calls */ function do_post(action, args, func) { console.log( 'Posting: ' .concat(action).concat(' ') .concat(JSON.stringify(args)) ); $.ajax({ url: "./{{users}}/index.php?module=backup_mgr&action=".concat(action), type: 'post', timeout: 120 * 1000, data: {args: JSON.stringify(args)} }) .done(function(data) { try { data = JSON.parse(data); } catch (err) { console.log("error in ".concat(action)); console.log(data); console.log(err); data = { status: 1, error: 'error - invalid JSON from server' } } console.log(data); func(data); }) .fail(function(xhr, statusText, errorThrown) { if (xhr.state() == 'rejected') { data = { status: 1, error: 'Sorry, your settings could not be saved at this time as your session has expired. Please log back in and try again.' } } else { console.log(errorThrown); data = { status: 1, error: 'error - '.concat(statusText) } } func(data); }); } /* rudimentary check for email validation */ function validate_email_string(input){ // not true RFC 5322 support but "close enough." see emailregex.com var regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return input.match(regex) != null; } /* check for ongoing restores and display them; if there are onoing restores, * set a timer for update_countdown */ function update_restore_queues() { do_post('get_restores', {}, function(data) { // reset the timer var countdown_span = $('#restore_queue_timer'); countdown_span.data('timer', countdown_span.data('dur')); if (data.status != 0) { console.log(data); $('#loading-div').data('restores-loaded', 1); finish_loading(); return; } var do_timer = false; // whether to start the refresh timer var has_queue = false; // if items appear in the "Restorations In Queue" col var queue_div = $('#restore_queue'); queue_div.empty(); $.each(data.data, function(restore_index, restore) { has_queue = true; if (restore.state == 'In Progress' || restore.state == 'Starting') { do_timer = true; } queue_div.append(make_restore_div(restore)); }); if (has_queue) { $('#restore_queue_div').show(); } else { $('#restore_queue_div').hide(); } if (do_timer) { $('#restore_queue_timer_div').show(); if (window.countdown_timer == undefined) { window.countdown_timer = setInterval(update_countdown, 1000); } } else { $('#restore_queue_timer_div').hide(); } $('#loading-div').data('restores-loaded', 1); finish_loading(); }); } /* decrement the restore queue countdown until 0, then stop the timer * and run update_restore_queues again */ function update_countdown() { var span = $('#restore_queue_timer'); var num = Number(span.data('timer')); if (num > 1) { span.data('timer', num - 1); } else { span.text('timer', 0); // update_restore_queues will start the timer again if needed clearInterval(window.countdown_timer); window.countdown_timer = undefined; update_restore_queues(); } } /* make an individual div.well for the restoration queues */ function make_restore_div(restore) { // Make the progress bar var progbar = $('
', { 'class': 'progress restore-progress' }); if (restore.state == 'Failed') { var progbar_inner = $('
', { 'class': 'progress-bar restore-progress-failed' }); progbar_inner.css('width', '100%'); } else if (restore.state == 'Finished') { var progbar_inner = $('
', { 'class': 'progress-bar restore-progress-finished' }); progbar_inner.css('width', '100%'); } else { var progbar_inner = $('
', { 'class': 'progress-bar bar-animated' }); progbar_inner.css('width', String(restore.progress).concat('%')); } if (restore.state == 'Failed' || restore.state == 'Finished') { progbar.append($('', { 'text': restore.state, 'class': 'restore-prog-span-done' })); } else { progbar.append($('', { 'text': restore.state, 'class': 'restore-prog-span' })); } progbar.append(progbar_inner); // Make the cancel link var cancel_btn = $('', { href: '#', onClick: 'cancel_restore_item(this)' }); cancel_btn.data('tag', restore.tag); if (restore.state == 'Finished') { cancel_btn.append($('', { 'class': 'fa fa-trash', 'html': ' Dismiss' })); } else if (restore.state == 'Failed') { cancel_btn.append($('', { 'class': 'fa fa-trash', 'html': ' Delete' })); } else { cancel_btn.append($('', { 'class': 'fa fa-trash', 'html': ' Cancel' })); } var top = $('
', { 'class': 'row' }); var title = ''.concat(restore.task) .concat(' Restoration from ') .concat(stamp_to_human(restore.time, false)); top.append($('', { 'text': title, 'class': 'col-sm-4' })); var progbar_col = $('
', { 'class': 'col-sm-8' }); progbar_col.append(progbar); top.append(progbar_col); var bottom = $('
', { 'class': 'row' }); bottom.append($('
', { 'text': restore.restoring, 'class': 'col-sm-4' })); var btn_grp = $('
', { 'class': 'col-sm-4 restore-item-btns' }); var ticket_area = $('
', { 'class': 'col-sm-4 ticket-submit' }); ticket_area.data('tag', restore.tag); // If this is a failed task, more buttons if (restore.state == 'Failed') { // Make the log button var log_btn = $('', { href: '#', onClick: 'view_fail_log(this)' }); log_btn.append($('', { 'class': 'fa fa-book', 'html': ' View Fail Log' })); log_btn.data('log', restore.log); // Button to make a ticket var ticket_btn = $('', { href: '#', onClick: 'ticket_modal(this)' }); ticket_btn.append($('', { 'class': 'fa fa-ticket ticket-submit-btn', 'html': ' Submit Ticket' })); // copy data attributes into the ticket button so it can be accessed when showing the popup modal ticket_btn.data('log', restore.log); ticket_btn.data('title', title); ticket_btn.data('params', restore.params); ticket_btn.data('tag', restore.tag); ticket_btn.data('task', restore.task); if (window.tickets_submitted.indexOf(restore.tag) != -1) { ticket_btn.css('color', '#666'); ticket_btn.data('sent', 'y'); ticket_area.addClass('ticket-submitted alert alert-success'); ticket_area.append($('', { 'class': 'fa fa-check-circle ticket-success-icon' })); ticket_area.append('A ticket was submitted with the log file for troubleshooting'); } btn_grp.append(ticket_btn); btn_grp.append(log_btn); } btn_grp.append(cancel_btn); bottom.append(ticket_area); bottom.append(btn_grp); var div = $('
', { 'class': 'well restore-well' }); div.append(top); div.append(bottom); return div; } /* rudimentary check for trying to escape homedir. * the backend will do a better check later */ function good_path(path) { path = path.replace(/^\/+/, '').replace(/\/+$/, '').split('/'); var backs = 0; var forwards = 0; for (var i = 0; i < path.length; i++) { if (path[i] == '..') { backs += 1; } else { forwards += 1; } } return forwards >= backs; } /* runs whenever a dump path is changed */ function check_path(input) { input = $(input); var err_div = $(input.data('check-error')); if (good_path(input.val())) { err_div.hide(); } else { err_div.show(); } } /* displays a log viewer when "View Fail Log" is clicked on a restore */ function view_fail_log(link) { link = $(link); var div = $('#log-modal-body'); div.empty(); $.each(link.data('log'), function(e_id, entry) { var date = stamp_to_human(entry[0], true) var msg = entry[1]; var p = $('

'); p.append($('', { text: date })); p.append(' '.concat(msg)); div.append(p); }); $('#log-modal').modal('show'); } /* Shows the "Submit Ticket" popup modal */ function ticket_modal(link) { link = $(link); var tag = link.data('tag'); if (link.data('sent') == 'y') { return; } if (window.tickets_submitted.indexOf(tag) != -1) { return; } // access data attrs of the link and place them on the modal before showing var log = link.data('log'); var title = link.data('title'); var params = link.data('params'); var task = link.data('task'); var modal = $('#ticket-modal'); $('#ticket-do-btn').prop('disabled', false); $('#ticket-do-btn').text('Submit Ticket'); $('#ticket-error').hide(); modal.data('tag', tag); modal.data('task', task); modal.data('log', log); modal.data('title', title); modal.data('params', params); modal.modal('show'); } /* when "Submit Ticket" is clicked in the popup modal, this emails support */ function make_ticket() { var modal = $('#ticket-modal') var log = modal.data('log'); var title = modal.data('title'); var task = modal.data('task'); var backup_params = modal.data('params'); var tag = modal.data('tag'); $('#ticket-do-btn').prop('disabled', true); $('#ticket-do-btn').text('Please wait...'); var params = { title: title, task: task, ipaddr: window.remote_ip, log: log, params: backup_params, msg: $('#ticket-body').val() }; do_post('make_ticket', params, function(data) { if (data.status == 0) { window.tickets_submitted.push(tag); $('.ticket-submit').each(function(index, alert) { // show the success message alert = $(alert); if (alert.data('tag') == tag) { alert.empty(); alert.addClass('ticket-submitted alert alert-success'); alert.append($('', { 'class': 'fa fa-check-circle ticket-success-icon' })); alert.append('A ticket was submitted with the restore log for troubleshooting'); } }); $('.ticket-submit-btn').each(function(index, btn) { // disable the button btn = $(btn); if (btn.data('tag') == tag) { btn.css('color', '#666'); btn.data('sent', 'y'); } }); modal.modal('hide'); $('#ticket-body').val(''); } else { console.log(data); $('#ticket-error').show(); } }); } /* update a progress-bar div */ function set_bar(elem, numerator, denominator, show_msg) { if (numerator == '?') { // we were not able to fetch dir sizes from cPanel // and the customer selected for custom backups elem.css('background-color', 'orangered'); elem.css('width', '100%'); elem.text("Data Unavailable"); return; } if (denominator == 0) { if (show_msg) { elem.text('DISABLED'); } var percent = 100; } else if (numerator > denominator) { if (show_msg) { elem.text('OVER QUOTA'); } var percent = 100; } else { elem.text(''); var percent = numerator / denominator * 100 } if (percent <= 30) { var color = 'green'; } else if (percent <= 50) { var color = 'goldenrod'; } else if (percent <= 80) { var color = 'orangered'; } else { var color = 'red'; } elem.css('background-color', color); elem.css('width', String(percent).concat('%')); } /* then the cancel button next to an ongoing restore is clicked */ function cancel_restore_item(link) { link = $(link); var well = link.closest('.well'); do_post('cancel_restore', { tag: link.data('tag') }, function(data) { if (data.status == 0) { update_restore_queues(); } }); } /* calculate homedir selection size, return '?' or a number in GB */ function calc_home_usage() { if (!get_switch_state('homedir_enabled')) { $('#homedir-apply-btn').prop('disabled', false); console.log('homedir disabled, so its size is 0MB'); return 0; // disabled } var home_mode = $('input[name=mode_homedir]:checked').val(); if (home_mode == 'whitelist') { var homedir_mb = 0; var included = get_browser_selected('#homedir-include-browser'); $('#homedir-apply-btn').prop('disabled', included.length == 0); var all_sizes = $('#homedir-include-browser').data('all-sizes'); $.each(included, function(index, included_item) { if (!(included_item in all_sizes)) return '?'; if (all_sizes[included_item] === null) return '?'; homedir_mb += all_sizes[included_item]; }); console.log('homedir whitelist total: '.concat(homedir_mb).concat('MB')); return homedir_mb; } else if (home_mode == 'blacklist') { var homedir_mb = $('#homedir-size').data('homedir-size'); var quotactl_mb = homedir_mb; if (homedir_mb == '?') return '?'; var excluded = get_browser_selected('#homedir-exclude-browser'); $('#homedir-apply-btn').prop('disabled', excluded.length == 0); var all_sizes = $('#homedir-exclude-browser').data('all-sizes'); $.each(excluded, function(index, excluded_item) { if (!(excluded_item in all_sizes)) return '?'; if (all_sizes[excluded_item] === null) return '?'; homedir_mb -= all_sizes[excluded_item]; }); console.log('homedir size after blacklist: '.concat(homedir_mb).concat('MB (quotactl was ').concat(quotactl_mb).concat('MB)')); return homedir_mb; } else { // entire homedir selected $('#homedir-apply-btn').prop('disabled', false); var homedir_mb = $('#homedir-size').data('homedir-size'); if (homedir_mb == '?') return '?'; console.log('homedir size from quotactl is '.concat(homedir_mb).concat('MB')); return homedir_mb; } } /* set the total usage bar at the top of the page from usage in mb */ function recalculate_usage() { var total_gb_span = $('#total_gb'); var avail_gb_span = $('#avail_gb'); var quota_mb = Number(total_gb_span.data('quota-mb')); var srv_unused = total_gb_span.data('srv-unused'); var mb_usage = 0; // Add homedir usage var homedir_mb = calc_home_usage(); // update totals under custom selections for homedir $('.total-selected-homedir').text(to_gb(homedir_mb)); $('.usage_gb_homedir').text(to_gb(homedir_mb)); if (homedir_mb === '?') { mb_usage = '?'; } else { mb_usage += homedir_mb; } // Add MySQL and PgSQL usage $.each(['mysql', 'pgsql'], function(i, dbtype) { var dbase_mode = $('input[name=mode_'.concat(dbtype).concat(']:checked')).val(); if (dbase_mode != undefined) { // it's on the page if (!get_switch_state(dbtype.concat('_enabled'))) { console.log(dbtype.concat(' disabled, so its size is 0MB')); var dbase_mb = 0; // disabled $('#'.concat(dbtype).concat('-apply-btn')).prop('disabled', false); } else if (dbase_mode == 'whitelist') { var dbase_mb = get_custom_db_usage(dbtype, true); console.log(dbtype.concat(' whitelist total: '.concat(dbase_mb).concat('MB'))); } else if (dbase_mode == 'blacklist') { var total_all_dbs = get_custom_db_total(dbtype); if (total_all_dbs != '?') { var dbase_mb = total_all_dbs - get_custom_db_usage(dbtype, false); } else { var dbase_mb = '?'; } console.log(dbtype.concat(' size after blacklist: ').concat(dbase_mb).concat('MB (total was ').concat(total_all_dbs).concat('MB)')); } else { // all databases $('#'.concat(dbtype).concat('-apply-btn')).prop('disabled', false); var dbase_mb = get_custom_db_total(dbtype); console.log(dbtype.concat(' total (all) is ').concat(dbase_mb).concat('MB')); } // update totals under custom selections for databases $('.total-selected-'.concat(dbtype)).text(to_gb(dbase_mb)); $('.usage_gb_'.concat(dbtype)).text(to_gb(dbase_mb)); if (mb_usage != '?') { mb_usage += dbase_mb; } } }); // If the child accounts tab is shown, update the usage for the parent var parent_usage = $('#parent_usage'); if (parent_usage.length > 0) { // it exists parent_usage.text(to_gb(mb_usage)); parent_bar = $('#parent_progressbar'); set_bar(parent_bar, mb_usage, quota_mb, true); if (mb_usage != '?') { mb_usage += get_child_total(); } } // Set the total at the top total_gb_span.text(to_gb(mb_usage)); if (mb_usage == "?") { avail_gb_span.text("?"); } else { var avail = quota_mb - mb_usage; if (srv_unused != undefined && srv_unused < avail) { avail = srv_unused; } if (avail < 0) { avail = Math.abs(avail); $('#avail_denominator').hide(); $("#avail_lbl").text(" more GB needed"); } else { $('#avail_denominator').show(); $("#avail_lbl").text("GB remain"); } avail_gb_span.text(to_gb(avail)); } // Update each bar shown under custom selections in the settings tab $('.total-selected-bar').each(function(b_index, bar) { set_bar($(bar), mb_usage, quota_mb, true); }); // If over quota, also show a warning div beneath the total bar and // within any custom selections that are expanded if (mb_usage > quota_mb) { $('#total-incalculable-warning').hide(); $('#total-over-quota-warning').show(); $('#over-quota-hover').css('display', 'inline'); $('.total-selected-warning').show(); } else if (mb_usage == "?") { $('#total-incalculable-warning').show(); $('#total-over-quota-warning').hide(); $('#over-quota-hover').hide(); $('.total-selected-warning').hide(); } else { $('#total-incalculable-warning').hide(); $('#total-over-quota-warning').hide(); $('#over-quota-hover').hide(); $('.total-selected-warning').hide(); } } /* get the usage of a all selected items for a given database backup type and whether * we're counting for a whitelist (or blacklist) */ function get_custom_db_usage(baktype, whitelist) { if (whitelist) { var inputs = $('#custom_items_whitelist_'.concat(baktype).concat(' input')); } else { var inputs = $('#custom_items_blacklist_'.concat(baktype).concat(' input')); } var size_mb = 0; var num_checked = 0; var error = false; $.each(inputs, function(i, checkbox) { checkbox = $(checkbox); if (checkbox.is(':checked')) { num_checked += 1; var this_size = checkbox.data('size'); if (this_size == '?') { error = true; } else { size_mb += Number(this_size); } } }); $('#'.concat(baktype).concat('-apply-btn')).prop('disabled', num_checked == 0); if (error) return '?'; return size_mb; } /* get the total size of all db items for custom backups if all were selected */ function get_custom_db_total(baktype) { var size_mb = 0; var error = false; $.each($('#custom_items_whitelist_'.concat(baktype).concat(' input')), function(i, checkbox) { var this_size = $(checkbox).data('size'); if (this_size == '?') { error = true; } else { size_mb += Number(this_size); } }); if (error) return '?'; return size_mb; } /* get the state of a cpanel enable/disable switch without needing AngularJS */ function get_switch_state(id) { return $("#".concat(id)).hasClass('btn-primary'); } /* set the state of a cpanel enable/disable switch without needing AngularJS */ function set_switch_state(id, enabled) { var btn = $('#'.concat(id)); var label = $("#".concat(id).concat(' span')); if (enabled) { btn.removeClass('btn-danger'); btn.addClass('btn-primary'); label.html(label.data('enabled-html')); } else { btn.removeClass('btn-primary'); btn.addClass('btn-danger'); label.html(label.data('disabled-html')); } } /* When the "Restore" link is clicked in the settings tab, this takes the user to that section of the restore tab and auto-selects that date */ function goto_restore(link) { link = $(link); var baktype = link.data('baktype'); var geo = link.data('geo'); if (baktype == 'homedir') { // the change event will already init the file browser, so // if this is the first time expanding, avoid initializing it twice window.homedir_restore_shown_once = true; } var collapse = $('#restore_'.concat(baktype).concat('_collapse')); var val = ''.concat(geo).concat(':').concat(link.data('snap')); collapse.find('.restore-date-select').val(val).change(); // hide all other accordion items on the restore form $.each($('#restore_tab .collapse'), function(i, elem) { elem = $(elem); if (collapse.attr('id') != elem.attr('id')) { elem.collapse('hide'); } }); collapse.collapse('show'); // show this accordion item $('#nav-link-restore').tab('show'); } function update_geo_dests() { var geo_loc = $('#restore_accordion').data('geo-loc'); var cluster_loc = $('#restore_accordion').data('cluster-loc'); $('.restore-date-select').each(function (index, select) { var select = $(select); var baktype = select.data('baktype'); var geo = select.find(":selected").data('geo'); if (geo == 1) { $("#geo-dest-".concat(baktype)).text(geo_loc); } else { $("#geo-dest-".concat(baktype)).text(cluster_loc); } }); } /* flip the state of a cpanel enable/disable switch without needing AngularJS */ function toggle_switch_state(id) { var enabled = !get_switch_state(id); set_switch_state(id, enabled); if (id.endsWith('_enabled')) { var baktype = id.split('_', 1)[0]; apply_settings(baktype, '#'.concat(baktype).concat('apply-btn')); } set_child_usage(); // update child progress bars show_hide_baktypes(); // show/hide any backup forms if necessary recalculate_usage(); // adjust quotas for anything that changed show_hide_child_cols(); // hide child account form columns if needed update_sched(); // update scheduling column if it disabled something if (id.startsWith('childnotify-')) { validate_child_email(); } if (id == 'do_child_limits') { // submit on toggle if this is the enable button at the top of // the child account form check_overprovision(); apply_child_limits(false); } } /* convert MB to GB and round 2 decimal points; return as a string */ function to_gb(mb) { if (mb === '?') { return '?'; } return (Math.round(Number(mb / 1024) * 100) / 100).toFixed(2); } /* Different dates may have different databases backed up. * This populates the database select dropdown whever the date * is changed in the restore form */ function update_restore_dbnames() { $('.restore_db_date').each(function(index, date_select) { var date_select = $(date_select); var dbtype = date_select.data('baktype'); var date_option = date_select.find(":selected"); var dbname_select = $('#restore_dbname_'.concat(dbtype)); dbname_select.find('option').remove(); dbname_select.append($('

    ', { style: 'list-style-type: none;' }); bySortedValue($('#settings_tab').data('home-items').dirs, function(dir, size_mb) { // for each directory var checked = selections.indexOf(dir) != -1; if (checked) selections.remove(dir); all_sizes[dir] = size_mb; ul.append(create_browser_li({ browser_id: browser_id, folder: true, path: dir, toplevel: true, size: size_mb, checked: checked })); }); bySortedValue($('#settings_tab').data('home-items').files, function(path, size_mb) { // for each file var checked = selections.indexOf(path) != -1; if (checked) selections.remove(path); all_sizes[path] = size_mb; ul.append(create_browser_li({ browser_id: browser_id, folder: false, path: path, toplevel: true, size: size_mb, checked: checked })); }); browser_div.append(ul); // Any selections previously made which haven't been expanded yet browser_div.data('unshown-selections', selections); browser_div.data('all-sizes', all_sizes); } function init_from_restic_browse(post_uri, browser_div, snap, geo) { var browser_id = browser_div.attr('id'); browser_div.data('unshown-selections', []); // not relevant to restic's browser; just set it empty browser_div.empty(); if (snap == undefined) { console.log('There appears to be no file backups to browse; not initializing the browser widget'); return; } var temp_p = $('

    ', { text: 'Loading ', class: 'browser-msg' }); temp_p.append($('', { src: window.filebrowse_img_root.concat('/spinner.gif') })); browser_div.append(temp_p); var post_args = JSON.stringify({snap: snap, post_uri: post_uri, geo: geo}); $.ajax({ url: post_uri.concat('&action=init_restic_browser'), type: 'POST', timeout: 120 * 1000, data: {args: post_args} }) .done(function(data) { try { data = JSON.parse(data); console.log(data); } catch (err) { browser_div.append($('

    ', { text: 'error - invalid JSON from server', class: 'browser-msg text-danger' })); console.log('could not decode server response as JSON: '.concat(data)); return; } browser_div.empty(); if (data.status != 0) { // successfully contacted server, but ran into an error browser_div.append($('

    ', { text: data.data })); console.log(data); return; } // create the main ul where file/folders are displayed var ul = $('

      ', { style: 'list-style-type: none;' }); $.each(data.data.dirs, function(dir_index, dir) { // for each directory ul.append(create_browser_li({ folder: true, browser_id: browser_id, snap: snap, geo: geo, path: dir, toplevel: true })); }); $.each(data.data.files, function(file_index, file) { // for each file ul.append(create_browser_li({ folder: false, browser_id: browser_id, path: file, toplevel: true })); }); browser_div.append(ul); }) .fail(function(data) { // could not contact server temp_p.text('error - '.concat(data.statusText)); console.log(data); }); } function update_sel_count() { $('.filebrowser-count').each(function(index, span) { var span = $(span); var browser_name = span.data('browser'); var num_items = get_browser_selected(browser_name).length; span.text(num_items); if (num_items > 0) { span.parent().removeClass('count-zero'); } else { span.parent().addClass('count-zero'); } var has_items_func = window[span.data('has-items-func')]; has_items_func(num_items > 0); }); } /* create a
    • for the browser. params: folder: whether this is a folder which can be expanded browser_id: id for the browser div (only if folder=true) snap: optional snapshot ID to set in data for the li path: file/folder path checked: whether to set prop checked (default: false) toplevel: whether to include the browser-toplevel class (default: false) size: size to display for the item (optional) */ function create_browser_li(opts) { var post_action = $('#'.concat(opts.browser_id)).data('post-action'); if (post_action == 'listdir') { var path = hex2str(opts.path); } else { var path = opts.path; } if (opts.size === undefined) { var label = path; } else if (opts.size === null) { var label = path; } else { var label = path.concat(' (').concat((opts.size / 1024).toFixed(2)).concat('GB)'); } var li_args = {}; if (opts.toplevel) li_args['class'] = 'browser-toplevel'; if (opts.folder) { li_args['style'] = 'list-style-image: url("'.concat(window.filebrowse_img_root).concat('/directory.png")'); li_args['onclick'] = 'browse_expand(event, this);'; } else { li_args['style'] = 'list-style-image: url("'.concat(icon_for(path)).concat('")'); } var check_label = $('
    • ', li_args); li.data('path', opts.path); li.data('browser_id', opts.browser_id); if (!(opts.snap === undefined)) { li.data('snap', opts.snap); li.data('geo', opts.geo); } var check_div = $('
      ', { class: 'checkbox' }); check_label.prepend(check_input); check_div.append(check_label); li.append(check_div); return li; } /* triggers when a checkbox is clicked */ function update_checked(checkbox) { checkbox = $(checkbox); var file_browse = checkbox.closest('.file_browse'); var path = checkbox.val(); var checked = checkbox.is(':checked'); // when a checkbox is manually checked: // - check all items underneath it // when a checkbox is manually unchecked // - uncheck all items underneath it // - uncheck all items above it. update_sel_count() will then set them as indeterminate if (file_browse.data('post-action') == 'listdir') { var slash = '2f'; // utf-8 of '/' in hex } else { var slash = '/'; } file_browse.find(' :input').each(function(i, check) { var check = $(check); if (check.val() != path && check.val().startsWith(path.concat(slash))) { check.prop('checked', checked); } if (!checked && check.val() != path && path.startsWith(check.val().concat(slash))) { check.prop('checked', false); } }); update_sel_count(); } /* show an alert div for errors */ function show_browser_error(alert_div, msg, full_error) { console.log(full_error); alert_div.find('span').text(msg); alert_div.removeClass('hidden'); alert_div.show(); } /* expands a folder when clicked for the first time */ function browse_expand(event, li_item) { if (event.target.tagName == 'LABEL') { return; // it triggers twice if the label part of the checkbox is clicked } if (event.target.tagName == 'INPUT') { // the checkbox triggered this, not the li update_checked($(event.target)); return; } li_item = $(li_item); var path = li_item.data('path'); var snap = li_item.data('snap'); var geo = li_item.data('geo'); var browser_id = li_item.data('browser_id'); if (li_item.data('open') == 'y') { // already populated the items if (li_item.data('expanded') == 'y') { // expanded - hide the items beneath it expand_item(li_item, false); } else { // items hidden - show them again expand_item(li_item, true); } return; } var browser_div = $('#'.concat(browser_id)); var processing = browser_div.data('processing'); if (processing.indexOf(path) != -1) { console.log(browser_id.concat(' is already processing a request for ').concat(path)); return; } else { processing.push(path); browser_div.data('processing', processing); } li_item.css('list-style-image', 'url("'.concat(window.filebrowse_img_root).concat('/spinner.gif")')); var browser_div = $('#'.concat(browser_id)); var action = browser_div.data('post-action'); if (action == 'listdir') { var post_args = JSON.stringify({ path: path }); } else { var post_args = JSON.stringify({ path: path, snap: snap, geo: geo }); } var post_uri = "./{{users}}/index.php?module=backup_mgr&action=".concat(action) $.ajax({ url: post_uri, type: 'POST', timeout: 120 * 1000, data: { args: post_args } }) .done(function(data) { try { data = JSON.parse(data); } catch (err) { show_browser_error( $(li_item.closest('.file_browse').data('errors')), 'Error browsing '.concat(path).concat(': error - invalid JSON from server'), data ); return; } if (data.status != 0) { // successfully contacted server, but ran into an error show_browser_error( $(li_item.closest('.file_browse').data('errors')), 'Error browsing '.concat(path).concat(': ').concat(data.data), data ); return; } // update data-all-sizes on the browser div if (action == 'listdir') { var all_sizes = browser_div.data('all-sizes'); $.each(data.data.dirs, function(path, size) { all_sizes[path] = size; }); $.each(data.data.files, function(path, size) { all_sizes[path] = size; }) browser_div.data('all-sizes', all_sizes); } var next_li = $('
    • '); var ul = $('
        ', { style: 'list-style-type: none;' }); var empty = true; var check_new_items = li_item.find(' :input').is(':checked'); var unshown_selections = browser_div.data('unshown-selections'); $.each(data.data.dirs, function(key, val) { // for each directory if (action == 'listdir') { // local listdir returns a dict of {dir:size} var dir = key; var size = val; var this_path = dir; } else { // restic browse returns a list of dirs var dir = val; var size = undefined; var this_path = path.concat('/').concat(dir); } empty = false; var check = check_new_items; if (unshown_selections.indexOf(this_path) != -1) { var check = true; } ul.append(create_browser_li({ folder: true, browser_id: browser_id, snap: snap, geo: geo, path: this_path, size: size, checked: check })); }); $.each(data.data.files, function(key, val) { // for each file if (action == 'listdir') { // local listdir returns a dict of {file:size} var file = key; var size = val; var this_path = file; } else { // restic browse returns a list of files var file = val; var size = undefined; var this_path = path.concat('/').concat(file); } empty = false; var check = check_new_items; if (unshown_selections.indexOf(this_path) != -1) { var check = true; } ul.append(create_browser_li({ folder: false, browser_id: browser_id, path: this_path, size: size, checked: check })); }); if (li_item.data('open') != 'y') { li_item.data('open', 'y'); li_item.data('expanded', 'y'); next_li.append(ul) li_item.after(next_li); li_item.css('list-style-image', 'url("'.concat(window.filebrowse_img_root).concat('/folder_open.png")')); var dir_label = li_item.first('checkbox'); if (empty) { dir_label.text(dir_label.text().concat(' (empty)')); } } }) .fail(function(data) { // could not contact server show_browser_error( $(li_item.closest('.file_browse').data('errors')), 'Error browsing '.concat(path).concat(': ').concat(data.statusText), data ); li_item.css('list-style-image', 'url("'.concat(window.filebrowse_img_root).concat('/directory.png")')); }) .always(function() { var processing = browser_div.data('processing'); processing.remove(path); browser_div.data('processing', processing); }); } /* expand (or hide) an already opened folder */ function expand_item(li_elem, expand) { var inner_li = li_elem.next('li'); if (expand) { // items hidden - show them again inner_li.show(); li_elem.data('expanded', 'y'); li_elem.css('list-style-image', 'url("'.concat(window.filebrowse_img_root).concat('/folder_open.png")')); } else { // expanded - hide the items beneath it inner_li.hide(); li_elem.css('list-style-image', 'url("'.concat(window.filebrowse_img_root).concat('/directory.png")')); li_elem.data('expanded', 'n'); } } /* when the select all or deselect all button is clicked */ function filebrowser_selectall(browser_id, checked) { var browser_div = $(browser_id); browser_div.find('li').each(function(li_index, li) { li = $(li); if (checked) { // selecting all if (li.hasClass('browser-toplevel')) { $(li.find('input')).prop('checked', true); update_checked($(li.find('input'))); } if (li.data('expanded') == 'y') { expand_item(li, false); } } else { // deselecting all $(li.find('input')).prop('checked', false); update_checked($(li.find('input'))); } }); update_sel_count(); } /* get the icon that should be displayed for a file. * directories are different and should be directory.png or folder_open.png */ function icon_for(filename) { var ext = filename.substr(filename.lastIndexOf('.') + 1); // unused: file-lock.png, directory-lock.png var file_browse_extensions = { 'rtf': 'txt', 'gz': 'zip', 'jpeg': 'picture', '4fb': 'flash', 'mp4': 'picture', 'mp2': 'film', 'mp3': 'music', 'm4p': 'film', 'jar': 'java', 'wmv': 'film', 'wml': 'html', 'm4b': 'music', 'wma': 'music', 'm4a': 'music', 'bin': 'application', 'mpg': 'film', 'mpe': 'film', 'cpio': 'zip', 'psd': 'psd', 'mpv': 'film', 'tif': 'picture', 'f4p': 'flash', 'bat': 'script', 'gifv': 'film', 'f4v': 'flash', 'f4a': 'flash', 'pbm': 'picture', 'htm': 'html', 'webm': 'film', 'webp': 'picture', 'mpeg': 'film', 'm2v': 'film', 'rb': 'ruby', 'cgi': 'script', 'js': 'script', 'plx': 'script', 'bz': 'zip', 'c': 'code', 's7z': 'zip', 'iso': 'zip', 'pdf': 'pdf', 'tiff': 'picture', 'pgm': 'picture', 'ppm': 'picture', 'xz': 'zip', 'txt': 'txt', 'doc': 'doc', 'pp': 'code', 'vob': 'film', 'zip': 'zip', 'py': 'script', 'swf': 'flash', 'gif': 'picture', 'wav': 'music', 'pl': 'script', 'phtml': 'html', 'ogv': 'film', 'pnm': 'picture', 'flac': 'music', 'ogg': 'film', 'oga': 'music', 'png': 'picture', 'aac': 'music', 'flv': 'flash', 'erb': 'ruby', 'cab': 'zip', 'z': 'zip', 'tar': 'zip', '3g2': 'film', 'jpg': 'picture', 'ar': 'zip', 'rar': 'zip', 'avi': 'film', 'vox': 'music', '7z': 'zip', 'shtml': 'html', 'bz2': 'zip', 'html': 'html', 'php4': 'php', 'php5': 'php', 'xls': 'xls', 'xhtml': 'html', 'php7': 'php', 'css': 'css', 'php3': 'php', '3gp': 'music', 'ppt': 'ppt', 'mov': 'film', 'perl': 'script', 'jsp': 'code', 'sql': 'db', 'php': 'php', 'm4v': 'film', 'a': 'zip', 'svg': 'picture', 'sh': 'script', 'so': 'linux', 'cpp': 'code' }; if (ext in file_browse_extensions) { return window.filebrowse_img_root.concat('/').concat(file_browse_extensions[ext]).concat('.png'); } return window.filebrowse_img_root.concat('/file.png'); }