672 lines
22 KiB
JavaScript
672 lines
22 KiB
JavaScript
$(document).ready(function() {
|
|
|
|
var curpage = 0;
|
|
var backupId = 0;
|
|
var dirSep = '/';
|
|
var pathSep = ':';
|
|
var trees = {};
|
|
var searchdata = {};
|
|
var nodedata = {};
|
|
|
|
var manualIncludes = {};
|
|
var searchTrees = {};
|
|
var commonPrefix = [];
|
|
|
|
var includeMap = null;
|
|
|
|
var performRestore = function(tasks) {
|
|
var dlg = $('<div></div>').attr('title', 'Restoring files ...');
|
|
var cancelling = false;
|
|
var currentProgressTask = null;
|
|
var remainingTasks = tasks.length;
|
|
|
|
dlg.dialog({
|
|
autoOpen: true,
|
|
modal: true,
|
|
closeOnEscape: false,
|
|
buttons: [
|
|
{ text: 'Cancel', click: function(event, ui) {
|
|
if (confirm('Stop the restore?')) {
|
|
cancelling = true;
|
|
if (currentProgressTask != null)
|
|
APP_DATA.stopTask(currentProgressTask.taskId, true);
|
|
|
|
updatePageNav();
|
|
dlg.dialog('close');
|
|
dlg.remove();
|
|
}
|
|
}}
|
|
]
|
|
});
|
|
|
|
dlg.parent().find('.ui-dialog-titlebar-close').remove().first().remove();
|
|
|
|
var pg = $('<div></div>');
|
|
pg.progressbar({ value: false });
|
|
var pgtxt = $('<div></div>');
|
|
pgtxt.text('Sending jobs to server ...');
|
|
|
|
dlg.append(pg);
|
|
dlg.append(pgtxt);
|
|
|
|
var onAllRestoresCompleted = function() {
|
|
$(document).off('server-progress-updated', serverProgressUpdateMethod);
|
|
$(document).off('server-state-updated', serverStateUpdateMethod);
|
|
|
|
curpage = Math.min(2, curpage+1);
|
|
updatePageNav();
|
|
dlg.dialog('close');
|
|
dlg.remove();
|
|
};
|
|
|
|
var serverProgressUpdateMethod = function(e, data) {
|
|
if (currentProgressTask == null)
|
|
pgtxt.text('Waiting for restore to begin ...');
|
|
else {
|
|
if (tasks.length == 1)
|
|
pgtxt.text('Restoring files ...');
|
|
else
|
|
pgtxt.text('Restoring files (' + (tasks.length - remainingTasks) + ' of ' + tasks.length + ')');
|
|
}
|
|
|
|
};
|
|
|
|
var serverStateUpdateMethod = function(e, data) {
|
|
var activeTaskId = -1;
|
|
var queuedTasks = [];
|
|
|
|
if (cancelling)
|
|
return;
|
|
|
|
if (data.ActiveTask != null)
|
|
activeTaskId = data.ActiveTask.Item1;
|
|
|
|
if (data.SchedulerQueueIds != null)
|
|
for(var n in data.SchedulerQueueIds)
|
|
queuedTasks.push(data.SchedulerQueueIds[n].Item1);
|
|
|
|
var remaining = 0;
|
|
for(var n in tasks) {
|
|
|
|
if (tasks[n].taskId == activeTaskId) {
|
|
currentProgressTask = tasks[n];
|
|
remaining++;
|
|
continue;
|
|
}
|
|
|
|
for(var i in queuedTasks)
|
|
if (queuedTasks[i] == tasks[n].taskId) {
|
|
remaining++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (remaining == 0)
|
|
onAllRestoresCompleted();
|
|
};
|
|
|
|
var curTask = 0;
|
|
var registerTasks = function() {
|
|
if (curTask == tasks.length) {
|
|
pgtxt.text('Waiting for restore to begin ...');
|
|
$(document).on('server-progress-updated', serverProgressUpdateMethod);
|
|
$(document).on('server-state-updated', serverStateUpdateMethod);
|
|
return;
|
|
}
|
|
|
|
var task = tasks[curTask];
|
|
task['action'] = 'restore-files';
|
|
task['HTTP_METHOD'] = 'POST';
|
|
curTask++;
|
|
|
|
APP_DATA.callServer(task, function(data) {
|
|
task['taskId'] = data.TaskID;
|
|
registerTasks();
|
|
},
|
|
function(d, s, m) {
|
|
alert('Error: ' + m);
|
|
dlg.dialog('close');
|
|
dlg.remove();
|
|
});
|
|
|
|
}
|
|
|
|
registerTasks();
|
|
};
|
|
|
|
$('#restore-dialog').dialog({
|
|
minWidth: 320,
|
|
width: $('body').width > 600 ? 320 : 600,
|
|
minHeight: 480,
|
|
height: 500,
|
|
modal: true,
|
|
autoOpen: false,
|
|
closeOnEscape: true,
|
|
buttons: [
|
|
{ text: '< Previous', disabled: true, click: function(event, ui) {
|
|
curpage = Math.max(0, curpage-1);
|
|
updatePageNav();
|
|
|
|
}},
|
|
{ text: 'Next >', click: function(event, ui) {
|
|
if (curpage == 2) {
|
|
$('#restore-dialog').dialog('close');
|
|
} else if (curpage == 1) {
|
|
|
|
var restorePath = null;
|
|
|
|
var overwrite = $('#restore-overwrite-overwrite').is(':checked');
|
|
|
|
if ($('#restore-overwrite-target-other').is(':checked'))
|
|
restorePath = $('#restore-target-path').val();
|
|
|
|
var tasks = [];
|
|
for(var n in includeMap) {
|
|
var t = {
|
|
time: $('#restore-version').val(),
|
|
id: backupId,
|
|
'restore-path': restorePath,
|
|
'overwrite': overwrite,
|
|
paths: []
|
|
}
|
|
for(var p in includeMap[n]) {
|
|
if (p.lastIndexOf(dirSep) == p.length -1)
|
|
t.paths.push(p + '*');
|
|
else
|
|
t.paths.push(p);
|
|
}
|
|
|
|
if (t.paths.length > 0)
|
|
{
|
|
t.paths = t.paths.join(pathSep);
|
|
tasks.push(t);
|
|
}
|
|
}
|
|
|
|
performRestore(tasks);
|
|
} else {
|
|
var els = 0;
|
|
includeMap = buildIncludeMap();
|
|
|
|
for(var t in includeMap)
|
|
for(var i in includeMap[t])
|
|
els++;
|
|
|
|
if (els == 0) {
|
|
alert('You must select at least one path to restore');
|
|
return;
|
|
}
|
|
|
|
curpage = Math.min(2, curpage+1);
|
|
updatePageNav();
|
|
|
|
}
|
|
}}
|
|
]
|
|
});
|
|
|
|
var dlg_buttons = $('#restore-dialog').parent().find('.ui-dialog-buttonpane').find('.ui-button');
|
|
var updatePageNav = function() {
|
|
if (curpage == 0) {
|
|
$('#restore-files-page').show();
|
|
$('#restore-path-page').hide();
|
|
$('#restore-complete-page').hide();
|
|
dlg_buttons.first().show();
|
|
dlg_buttons.last().button('option', 'label', 'Next >');
|
|
} else if (curpage == 1) {
|
|
$('#restore-files-page').hide();
|
|
$('#restore-path-page').show();
|
|
$('#restore-complete-page').hide();
|
|
dlg_buttons.first().show();
|
|
dlg_buttons.last().button('option', 'label', 'Restore');
|
|
} else {
|
|
$('#restore-files-page').hide();
|
|
$('#restore-path-page').hide();
|
|
$('#restore-complete-page').show();
|
|
|
|
dlg_buttons.first().hide();
|
|
dlg_buttons.last().button('option', 'label', 'OK');
|
|
}
|
|
|
|
dlg_buttons.first().button('option', 'disabled', curpage == 0);
|
|
};
|
|
|
|
$('#restore-search').watermark('Search for files & folders');
|
|
|
|
$('#restore-files-page').show();
|
|
$('#restore-path-page').hide();
|
|
$('#restore-complete-page').hide();
|
|
|
|
var colorize = function(tree, term, entry) {
|
|
var tr = tree.jstree();
|
|
$(entry || tree).find('a.jstree-anchor').each(function(i, e) {
|
|
var s = $(e);
|
|
var node = tr.get_node(s);
|
|
if (node.text) {
|
|
s.children('.search-match').remove();
|
|
var o = s.children();
|
|
s.html(replace_all_insensitive(node.text, term, '<div class="search-match">$1</div>'));
|
|
s.prepend(o);
|
|
}
|
|
});
|
|
};
|
|
|
|
var buildSearchNodes = function(time, data, search) {
|
|
var rootpath = commonPrefix[time];
|
|
if (rootpath != '' && rootpath[rootpath.length - 1] != dirSep)
|
|
rootpath += dirSep;
|
|
var rootnode = createNodeFromData(commonPrefix[time], commonPrefix[time], time, true);
|
|
rootnode.state = rootnode.state || {};
|
|
rootnode.state.opened = true;
|
|
|
|
|
|
for(var i = 0; i < data.Files.length; i++) {
|
|
var n = rootnode;
|
|
var p = rootpath;
|
|
var sp = data.Files[i].Path;
|
|
|
|
if (search && sp.toLowerCase().indexOf(search.toLowerCase()) < 0)
|
|
continue;
|
|
|
|
var isDir = sp.substr(sp.length - 1) == dirSep;
|
|
if (isDir)
|
|
sp = sp.substr(0, sp.length - 1);
|
|
|
|
var items = sp.substr(rootpath.length).split(dirSep);
|
|
|
|
for(var j = 0; j < items.length; j++) {
|
|
p += items[j] + (!isDir && j == items.length - 1 ? '' : dirSep);
|
|
|
|
var e = null;
|
|
for(var x in n.children)
|
|
if (n.children[x].filepath == p) {
|
|
e = n.children[x];
|
|
break;
|
|
}
|
|
|
|
if (e == null) {
|
|
e = createNodeFromData(p, commonPrefix[time], time, false);
|
|
|
|
if (j != items.length - 1) {
|
|
e.state = e.state || {};
|
|
e.state.opened = true;
|
|
}
|
|
|
|
if (n.children === false || n.children === true)
|
|
n.children = [];
|
|
n.children.push(e);
|
|
|
|
}
|
|
n = e;
|
|
}
|
|
}
|
|
|
|
return [rootnode];
|
|
}
|
|
|
|
var inSearch = false;
|
|
var doSearch = function(time, search) {
|
|
if (inSearch)
|
|
return;
|
|
|
|
$('#restore-search-loader').show();
|
|
inSearch = true;
|
|
|
|
var stree = searchTrees[time];
|
|
var tree = trees[time];
|
|
|
|
var buildSearchTree = function(nodes) {
|
|
var streeel = $('<div></div>');
|
|
searchTrees[time] = streeel;
|
|
$('#restore-files-tree').append(streeel);
|
|
|
|
var te = streeel.jstree({
|
|
'plugins' : [ 'checkbox' ],
|
|
'core': { 'data' : nodes }
|
|
});
|
|
|
|
colorize(streeel, search);
|
|
streeel.show();
|
|
};
|
|
|
|
if (searchdata[time]) {
|
|
var t = searchdata[time];
|
|
for(var p in t) {
|
|
if (search.indexOf(p) == 0) {
|
|
colorize(tree, search);
|
|
|
|
if (t[p].Files.length == 0) {
|
|
alert('No results matched the query');
|
|
tree.show();
|
|
} else if (stree) {
|
|
colorize(stree, search);
|
|
tree.hide();
|
|
stree.show();
|
|
} else {
|
|
buildSearchTree(buildSearchNodes(time, t[p], search));
|
|
tree.hide();
|
|
}
|
|
$('#restore-search-loader').hide();
|
|
inSearch = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tree) {
|
|
colorize(tree, search);
|
|
tree.hide();
|
|
}
|
|
if (stree)
|
|
colorize(stree, search);
|
|
|
|
|
|
if (stree) {
|
|
stree.remove();
|
|
delete searchTrees[time];
|
|
}
|
|
|
|
$.ajax({
|
|
'url': APP_CONFIG.server_url,
|
|
'data': {
|
|
'action': 'search-backup-files',
|
|
'id': backupId,
|
|
'time': time,
|
|
'prefix-only': 'false',
|
|
'filter': '*' + search + '*'
|
|
},
|
|
'dataType': 'json'
|
|
})
|
|
.done(function(data, status, xhr) {
|
|
|
|
if (!searchdata[time])
|
|
searchdata[time] = {};
|
|
|
|
if (!manualIncludes[time])
|
|
manualIncludes[time] = [];
|
|
|
|
searchdata[time][search] = data;
|
|
|
|
$('#restore-search-loader').hide();
|
|
inSearch = false;
|
|
|
|
if (data.Files.length == 0) {
|
|
alert('No results matched the query');
|
|
tree.show();
|
|
return;
|
|
}
|
|
|
|
buildSearchTree(buildSearchNodes(time, data));
|
|
|
|
})
|
|
.fail(function() {
|
|
$('#restore-search-loader').hide();
|
|
inSearch = false;
|
|
alert('Search failed ...');
|
|
tree.show();
|
|
});
|
|
};
|
|
|
|
var createNodeFromData = function(path, prefix, time, rootnode) {
|
|
var disp = path.substr(prefix.length);
|
|
var isFolder = disp.lastIndexOf(dirSep) == disp.length - 1;
|
|
var icon = null;
|
|
var state = null;
|
|
if (isFolder)
|
|
disp = disp.substr(0, disp.length - 1);
|
|
else
|
|
icon = 'icon-file icon-file-' + disp.substr(disp.lastIndexOf('.')+1);
|
|
|
|
if (rootnode) {
|
|
disp = APP_DATA.getBackupName(backupId) || disp;
|
|
//state = {opened: true};
|
|
}
|
|
|
|
return {
|
|
text: disp,
|
|
filepath: path,
|
|
time: time,
|
|
children: isFolder,
|
|
state: state,
|
|
icon: icon
|
|
};
|
|
};
|
|
|
|
var loadNodes = function(node, callback, time) {
|
|
$.ajax({
|
|
'url': APP_CONFIG.server_url,
|
|
'data': {
|
|
'action': 'search-backup-files',
|
|
'id': backupId,
|
|
'time': time,
|
|
'prefix-only': node.id === '#',
|
|
'folder-contents': node.id !== '#',
|
|
'filter': node.id === '#' ? '*' : node.original.filepath,
|
|
'Prefix': node.id === '#' ? '' : node.original.filepath
|
|
},
|
|
'dataType': 'json'
|
|
})
|
|
.done(function(data, status, xhr) {
|
|
var nodes = [];
|
|
data.Files = data.Files || [];
|
|
for(var i = 0; i < data.Files.length; i++)
|
|
nodes.push(createNodeFromData(data.Files[i].Path, data.Prefix, time, node.id === '#'));
|
|
|
|
callback(nodes, data);
|
|
|
|
if (node.id === '#')
|
|
commonPrefix[time] = data.Files[0].Path;
|
|
});
|
|
};
|
|
|
|
var setupTree = function(time) {
|
|
var treeel = trees[time];
|
|
|
|
if (!trees[time]) {
|
|
var treeel = $('<div></div>');
|
|
trees[time] = treeel;
|
|
$('#restore-files-tree').append(treeel);
|
|
|
|
treeel.jstree({
|
|
'core': {
|
|
'data': function(node, callback) {
|
|
if (nodedata[node.id]) {
|
|
callback(nodedata[node.id]);
|
|
colorize(treeel, $('#restore-search').val());
|
|
} else {
|
|
loadNodes(node, function(nodes, data) {
|
|
callback(nodes);
|
|
if (data.Prefix == '')
|
|
treeel.jstree("open_node", treeel.find('li').first());
|
|
colorize(treeel, $('#restore-search').val());
|
|
},
|
|
time);
|
|
}
|
|
}
|
|
},
|
|
'plugins' : [ 'checkbox' ]
|
|
});
|
|
}
|
|
|
|
for(var t in trees)
|
|
if (t != time)
|
|
trees[t].hide();
|
|
else {
|
|
trees[t].show();
|
|
}
|
|
};
|
|
|
|
var buildIncludeMap = function() {
|
|
var time = $('#restore-version').val();
|
|
var m = {};
|
|
//strees = strees || trees;
|
|
var strees = [ trees[time] ];
|
|
var partialfolders = false;
|
|
if (searchTrees[time] && searchTrees[time].is(':visible')) {
|
|
partialfolders = true;
|
|
strees = [ searchTrees[time] ];
|
|
}
|
|
|
|
for(var t in strees) {
|
|
m[t] = {};
|
|
var tr = strees[t].jstree();
|
|
|
|
var roots = strees[t].children('ul').children('li');
|
|
var follow = [];
|
|
|
|
roots.each(function(i,e) {
|
|
follow.push(tr.get_node(e));
|
|
});
|
|
|
|
while(follow.length > 0) {
|
|
var n = follow.pop();
|
|
if (n.state.selected && n.original.filepath) {
|
|
// If we have partial folder matches,
|
|
// we only include the leaves
|
|
if (partialfolders) {
|
|
if (!n.children || n.children.length == 0) {
|
|
m[t][n.original.filepath] = 1;
|
|
} else {
|
|
// Non-leaf node, recurse it
|
|
for(var c in n.children)
|
|
follow.push(tr.get_node(n.children[c]));
|
|
}
|
|
|
|
} else {
|
|
m[t][n.original.filepath] = 1;
|
|
}
|
|
} else {
|
|
//TODO: This traverses the entire tree,
|
|
// we could choose only those with indeterminate,
|
|
// but that state only exists in the DOM which is
|
|
// removed when collapsed
|
|
for(var c in n.children)
|
|
follow.push(tr.get_node(n.children[c]));
|
|
}
|
|
}
|
|
}
|
|
|
|
return m;
|
|
};
|
|
|
|
$('#restore-dialog').on('setup-data', function(e, id) {
|
|
backupId = id;
|
|
trees = { };
|
|
searchdata = { };
|
|
$('#restore-files-tree').empty();
|
|
$('#restore-search-loader').hide();
|
|
$('#restore-form').each(function(i, e) { e.reset(); });
|
|
$('#restore-overwrite-overwrite').each(function(i, e) { e.checked = true; });
|
|
$('#restore-overwrite-target-original').each(function(i, e) { e.checked = true; });
|
|
|
|
curpage = 0;
|
|
updatePageNav();
|
|
|
|
APP_DATA.getServerConfig(function(serverdata) {
|
|
|
|
// TODO: Should be the backup data versions, not the host OS
|
|
dirSep = serverdata.DirectorySeparator;
|
|
pathSep = serverdata.PathSeparator;
|
|
|
|
APP_DATA.callServer({ 'action': 'list-backup-sets', id: id }, function(data, success, message) {
|
|
$('#restore-version').empty();
|
|
|
|
if (data == null || data.length == 0) {
|
|
alert('Failed to get list of backup times');
|
|
$('#restore-dialog').dialog('close');
|
|
}
|
|
|
|
$('#restore-version').append($("<option></option>").attr("value", data[0].Time).text('Latest - ' + $.timeago(data[0].Time)));
|
|
for(var i in data)
|
|
if (i != '0')
|
|
$('#restore-version').append($("<option></option>").attr("value", data[i].Time).text($.timeago(data[i].Time)));
|
|
|
|
$('#restore-version').trigger('change');
|
|
|
|
}, function(data, success, message) {
|
|
alert('Failed to get list of backup times:\n' + message);
|
|
$('#restore-dialog').dialog('close');
|
|
});
|
|
}, function() {
|
|
alert('Failed to get server config');
|
|
$('#restore-dialog').dialog('close');
|
|
});
|
|
});
|
|
|
|
var doQuickSearch = function(search) {
|
|
var search = $('#restore-search').val();
|
|
var time = $('#restore-version').val();
|
|
if (searchdata[time]) {
|
|
for(var k in searchdata[time]) {
|
|
if (search.indexOf(k) == 0) {
|
|
colorize(trees[time], search);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
$('#restore-search').keypress(function(e) {
|
|
var time = $('#restore-version').val();
|
|
if (e.which == 13 && $('#restore-search').val().trim() != '')
|
|
doSearch(time, $('#restore-search').val());
|
|
else if ($('#restore-search').val().trim() != '') {
|
|
colorize(trees[time], $('#restore-search').val());
|
|
if (searchTrees[time])
|
|
colorize(searchTrees[time], $('#restore-search').val());
|
|
|
|
}
|
|
});
|
|
|
|
$('#restore-search').keyup(function(e) {
|
|
var time = $('#restore-version').val();
|
|
colorize(trees[time], $('#restore-search').val());
|
|
if (searchTrees[time])
|
|
colorize(searchTrees[time], $('#restore-search').val());
|
|
});
|
|
|
|
$('#restore-search').change(function(e) {
|
|
var time = $('#restore-version').val();
|
|
colorize(trees[time], $('#restore-search').val());
|
|
if (searchTrees[time])
|
|
colorize(searchTrees[time], $('#restore-search').val());
|
|
});
|
|
|
|
$('#restore-search').on('search', function(e) {
|
|
var time = $('#restore-version').val();
|
|
if ($('#restore-search').val() == '') {
|
|
trees[time].show();
|
|
if (searchTrees[time]) {
|
|
searchTrees[time].remove();
|
|
delete searchTrees[time];
|
|
}
|
|
colorize(trees[time], $('#restore-search').val());
|
|
} else
|
|
doSearch(time, $('#restore-search').val());
|
|
});
|
|
|
|
$('#restore-version').change(function() {
|
|
$('#restore-search').val('');
|
|
setupTree($('#restore-version').val());
|
|
});
|
|
|
|
$('#restore-target-path').keypress(function() { $('#restore-overwrite-target-other').each(function(i,e) { e.checked = true; }); });
|
|
$('#restore-target-path').change(function() { $('#restore-overwrite-target-other').each(function(i,e) { e.checked = true; }); });
|
|
|
|
$('#restore-overwrite-target-other').click(function() { $('#restore-target-path-browse').trigger('click'); } );
|
|
|
|
$('#restore-target-path-browse').click(function(e) {
|
|
$.browseForFolder({
|
|
title: 'Select restore folder',
|
|
resolvePath: true,
|
|
callback: function(path, display) {
|
|
$('#restore-target-path').val(path);
|
|
$('#restore-overwrite-target-other').each(function(i, e) { e.checked = true; });
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
}); |