// vim: set sw=4 ts=4 et /* 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 = $j('#restore_queue_timer'); countdown_span.data('timer', countdown_span.data('dur')); if (data.status != 0) { console.log(data); $j('#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 = $j('#restore_queue'); queue_div.empty(); $j.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) { $j('#restore_queue_div').show(); } else { $j('#restore_queue_div').hide(); } if (do_timer) { $j('#restore_queue_timer_div').show(); if (window.countdown_timer == undefined) { window.countdown_timer = setInterval(update_countdown, 1000); } } else { $j('#restore_queue_timer_div').hide(); } $j('#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 = $j('#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 = $j('
', { 'class': 'progress restore-progress' }); if (restore.state == 'Failed') { var progbar_inner = $j('
', { 'class': 'progress-bar restore-progress-failed' }); progbar_inner.css('width', '100%'); } else if (restore.state == 'Finished') { var progbar_inner = $j('
', { 'class': 'progress-bar restore-progress-finished' }); progbar_inner.css('width', '100%'); } else { var progbar_inner = $j('
', { 'class': 'progress-bar bar-animated' }); progbar_inner.css('width', String(restore.progress).concat('%')); } if (restore.state == 'Failed' || restore.state == 'Finished') { progbar.append($j('', { 'text': restore.state, 'class': 'restore-prog-span-done' })); } else { progbar.append($j('', { 'text': restore.state, 'class': 'restore-prog-span' })); } progbar.append(progbar_inner); // Make the cancel link var cancel_btn = $j('', { href: '#', onClick: 'cancel_restore_item(this)' }); cancel_btn.data('tag', restore.tag); if (restore.state == 'Finished') { cancel_btn.append($j('', { 'class': 'fa fa-trash', 'html': ' Dismiss' })); } else if (restore.state == 'Failed') { cancel_btn.append($j('', { 'class': 'fa fa-trash', 'html': ' Delete' })); } else { cancel_btn.append($j('', { 'class': 'fa fa-trash', 'html': ' Cancel' })); } var top = $j('
', { 'class': 'row' }); var title = ''.concat(restore.task) .concat(' Restoration from ') .concat(stamp_to_human(restore.time, false)); top.append($j('', { 'text': title, 'class': 'col-sm-4' })); var progbar_col = $j('
', { 'class': 'col-sm-8' }); progbar_col.append(progbar); top.append(progbar_col); var bottom = $j('
', { 'class': 'row' }); bottom.append($j('
', { 'text': restore.restoring, 'class': 'col-sm-4' })); var btn_grp = $j('
', { 'class': 'col-sm-4 restore-item-btns' }); var ticket_area = $j('
', { '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 = $j('', { href: '#', onClick: 'view_fail_log(this)' }); log_btn.append($j('', { 'class': 'fa fa-book', 'html': ' View Fail Log' })); log_btn.data('log', restore.log); // Button to make a ticket var ticket_btn = $j('', { href: '#', onClick: 'ticket_modal(this)' }); ticket_btn.append($j('', { '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($j('', { '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 = $j('
', { '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 = $j(input); var err_div = $j(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 = $j(link); var div = $j('#log-modal-body'); div.empty(); $j.each(link.data('log'), function(e_id, entry) { var date = stamp_to_human(entry[0], true) var msg = entry[1]; var p = $j('

'); p.append($j('', { text: date })); p.append(' '.concat(msg)); div.append(p); }); $j('#log-modal').modal('show'); } /* Shows the "Submit Ticket" popup modal */ function ticket_modal(link) { link = $j(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 = $j('#ticket-modal'); $j('#ticket-do-btn').prop('disabled', false); $j('#ticket-do-btn').text('Submit Ticket'); $j('#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 = $j('#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'); $j('#ticket-do-btn').prop('disabled', true); $j('#ticket-do-btn').text('Please wait...'); var params = { title: title, task: task, ipaddr: window.remote_ip, log: log, params: backup_params, msg: $j('#ticket-body').val() }; do_post('make_ticket', params, function(data) { if (data.status == 0) { window.tickets_submitted.push(tag); $j('.ticket-submit').each(function(index, alert) { // show the success message alert = $j(alert); if (alert.data('tag') == tag) { alert.empty(); alert.addClass('ticket-submitted alert alert-success'); alert.append($j('', { 'class': 'fa fa-check-circle ticket-success-icon' })); alert.append('A ticket was submitted with the restore log for troubleshooting'); } }); $j('.ticket-submit-btn').each(function(index, btn) { // disable the button btn = $j(btn); if (btn.data('tag') == tag) { btn.css('color', '#666'); btn.data('sent', 'y'); } }); modal.modal('hide'); $j('#ticket-body').val(''); } else { console.log(data); $j('#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 = $j(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')) { $j('#homedir-apply-btn').prop('disabled', false); console.log('homedir disabled, so its size is 0MB'); return 0; // disabled } var home_mode = $j('input[name=mode_homedir]:checked').val(); if (home_mode == 'whitelist') { var homedir_mb = 0; var included = get_browser_selected('#homedir-include-browser'); $j('#homedir-apply-btn').prop('disabled', included.length == 0); var all_sizes = $j('#homedir-include-browser').data('all-sizes'); $j.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 = $j('#homedir-size').data('homedir-size'); var quotactl_mb = homedir_mb; if (homedir_mb == '?') return '?'; var excluded = get_browser_selected('#homedir-exclude-browser'); $j('#homedir-apply-btn').prop('disabled', excluded.length == 0); var all_sizes = $j('#homedir-exclude-browser').data('all-sizes'); $j.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 $j('#homedir-apply-btn').prop('disabled', false); var homedir_mb = $j('#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 = $j('#total_gb'); var avail_gb_span = $j('#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 $j('.total-selected-homedir').text(to_gb(homedir_mb)); $j('.usage_gb_homedir').text(to_gb(homedir_mb)); if (homedir_mb === '?') { mb_usage = '?'; } else { mb_usage += homedir_mb; } // Add MySQL and PgSQL usage $j.each(['mysql', 'pgsql'], function(i, dbtype) { var dbase_mode = $j('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 $j('#'.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 $j('#'.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 $j('.total-selected-'.concat(dbtype)).text(to_gb(dbase_mb)); $j('.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 = $j('#parent_usage'); if (parent_usage.length > 0) { // it exists parent_usage.text(to_gb(mb_usage)); parent_bar = $j('#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); $j('#avail_denominator').hide(); $j("#avail_lbl").text(" more GB needed"); } else { $j('#avail_denominator').show(); $j("#avail_lbl").text("GB remain"); } avail_gb_span.text(to_gb(avail)); } // Update each bar shown under custom selections in the settings tab $j('.total-selected-bar').each(function(b_index, bar) { set_bar($j(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) { $j('#total-incalculable-warning').hide(); $j('#total-over-quota-warning').show(); $j('#over-quota-hover').css('display', 'inline'); $j('.total-selected-warning').show(); } else if (mb_usage == "?") { $j('#total-incalculable-warning').show(); $j('#total-over-quota-warning').hide(); $j('#over-quota-hover').hide(); $j('.total-selected-warning').hide(); } else { $j('#total-incalculable-warning').hide(); $j('#total-over-quota-warning').hide(); $j('#over-quota-hover').hide(); $j('.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 = $j('#custom_items_whitelist_'.concat(baktype).concat(' input')); } else { var inputs = $j('#custom_items_blacklist_'.concat(baktype).concat(' input')); } var size_mb = 0; var num_checked = 0; var error = false; $j.each(inputs, function(i, checkbox) { checkbox = $j(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); } } }); $j('#'.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; $j.each($j('#custom_items_whitelist_'.concat(baktype).concat(' input')), function(i, checkbox) { var this_size = $j(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 $j("#".concat(id)).hasClass('switch-on'); } /* set the state of a cpanel enable/disable switch without needing AngularJS */ function set_switch_state(id, enabled) { var divs = $j(".switchdiv_".concat(id)); if (divs.hasClass('disabled')) { enabled = false; } var label = $j("#switchlabel_".concat(id)); if (enabled) { divs.removeClass('switch-off'); divs.addClass('switch-on'); label.text(label.data('enabled-text')); } else { divs.removeClass('switch-on'); divs.addClass('switch-off'); label.text(label.data('disabled-text')); } } /* 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 = $j(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 = $j('#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 $j.each($j('#restore_tab .collapse'), function(i, elem) { elem = $j(elem); if (collapse.attr('id') != elem.attr('id')) { elem.collapse('hide'); } }); collapse.collapse('show'); // show this accordion item $j('#nav-link-restore').tab('show'); } /* 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() { $j('.restore_db_date').each(function(index, date_select) { var date_select = $j(date_select); var dbtype = date_select.data('baktype'); var date_option = date_select.find(":selected"); var dbname_select = $j('#restore_dbname_'.concat(dbtype)); dbname_select.find('option').remove(); dbname_select.append($j('