summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/syncthing/gui.go40
-rw-r--r--cmd/syncthing/mocked_model_test.go9
-rw-r--r--gui/black/assets/css/theme.css4
-rw-r--r--gui/dark/assets/css/theme.css3
-rw-r--r--gui/default/assets/css/overrides.css4
-rw-r--r--gui/default/assets/css/theme.css6
-rw-r--r--gui/default/assets/lang/lang-en.json11
-rw-r--r--gui/default/index.html10
-rw-r--r--gui/default/syncthing/app.js72
-rwxr-xr-xgui/default/syncthing/core/syncthingController.js237
-rw-r--r--gui/default/syncthing/device/removeDeviceDialogView.html26
-rw-r--r--gui/default/syncthing/folder/removeFolderDialogView.html32
-rw-r--r--gui/default/syncthing/folder/restoreVersionsConfirmation.html15
-rw-r--r--gui/default/syncthing/folder/restoreVersionsMassActions.html11
-rw-r--r--gui/default/syncthing/folder/restoreVersionsModalView.html51
-rw-r--r--gui/default/syncthing/folder/restoreVersionsVersionSelector.html17
-rw-r--r--gui/default/vendor/bootstrap/css/daterangepicker.css269
-rw-r--r--gui/default/vendor/bootstrap/js/daterangepicker.js1626
-rw-r--r--gui/default/vendor/fancytree/css/ui.fancytree.css663
-rw-r--r--gui/default/vendor/fancytree/jquery.fancytree-all-deps.js12045
-rw-r--r--gui/default/vendor/fancytree/skin-lion/icons.gifbin0 -> 5937 bytes
-rw-r--r--gui/default/vendor/fancytree/skin-lion/loading.gifbin0 -> 1849 bytes
-rw-r--r--gui/default/vendor/fancytree/skin-lion/vline.gifbin0 -> 852 bytes
-rw-r--r--gui/default/vendor/moment/moment.js4517
-rw-r--r--lib/config/folderconfiguration.go13
-rw-r--r--lib/model/model.go158
-rw-r--r--lib/model/model_test.go213
-rw-r--r--lib/model/rwfolder.go2
-rw-r--r--lib/versioner/simple.go4
-rw-r--r--lib/versioner/simple_test.go4
-rw-r--r--lib/versioner/staggered.go17
-rw-r--r--lib/versioner/util.go19
-rw-r--r--lib/versioner/versioner.go12
33 files changed, 20045 insertions, 65 deletions
diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go
index ad39a8c2a1..2784aa11b2 100644
--- a/cmd/syncthing/gui.go
+++ b/cmd/syncthing/gui.go
@@ -40,6 +40,7 @@ import (
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/upgrade"
+ "github.com/syncthing/syncthing/lib/versioner"
"github.com/vitrun/qart/qr"
"golang.org/x/crypto/bcrypt"
)
@@ -95,6 +96,8 @@ type modelIntf interface {
ResetFolder(folder string)
Availability(folder, file string, version protocol.Vector, block protocol.BlockInfo) []model.Availability
GetIgnores(folder string) ([]string, []string, error)
+ GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error)
+ RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error)
SetIgnores(folder string, content []string) error
DelayScan(folder string, next time.Duration)
ScanFolder(folder string) error
@@ -259,6 +262,7 @@ func (s *apiService) Serve() {
getRestMux.HandleFunc("/rest/db/remoteneed", s.getDBRemoteNeed) // device folder [perpage] [page]
getRestMux.HandleFunc("/rest/db/status", s.getDBStatus) // folder
getRestMux.HandleFunc("/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
+ getRestMux.HandleFunc("/rest/folder/versions", s.getFolderVersions) // folder
getRestMux.HandleFunc("/rest/events", s.getIndexEvents) // [since] [limit] [timeout] [events]
getRestMux.HandleFunc("/rest/events/disk", s.getDiskEvents) // [since] [limit] [timeout]
getRestMux.HandleFunc("/rest/stats/device", s.getDeviceStats) // -
@@ -287,6 +291,7 @@ func (s *apiService) Serve() {
postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores) // folder
postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder
postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
+ postRestMux.HandleFunc("/rest/folder/versions", s.postFolderVersionsRestore) // folder <body>
postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // <body>
postRestMux.HandleFunc("/rest/system/error", s.postSystemError) // <body>
postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // -
@@ -1309,6 +1314,41 @@ func (s *apiService) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
sendJSON(w, comp)
}
+func (s *apiService) getFolderVersions(w http.ResponseWriter, r *http.Request) {
+ qs := r.URL.Query()
+ versions, err := s.model.GetFolderVersions(qs.Get("folder"))
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+ sendJSON(w, versions)
+}
+
+func (s *apiService) postFolderVersionsRestore(w http.ResponseWriter, r *http.Request) {
+ qs := r.URL.Query()
+
+ bs, err := ioutil.ReadAll(r.Body)
+ r.Body.Close()
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ var versions map[string]time.Time
+ err = json.Unmarshal(bs, &versions)
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ ferr, err := s.model.RestoreFolderVersions(qs.Get("folder"), versions)
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+ sendJSON(w, ferr)
+}
+
func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
current := qs.Get("current")
diff --git a/cmd/syncthing/mocked_model_test.go b/cmd/syncthing/mocked_model_test.go
index f6e36d0301..e6c6949d5c 100644
--- a/cmd/syncthing/mocked_model_test.go
+++ b/cmd/syncthing/mocked_model_test.go
@@ -14,6 +14,7 @@ import (
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/stats"
+ "github.com/syncthing/syncthing/lib/versioner"
)
type mockedModel struct{}
@@ -75,6 +76,14 @@ func (m *mockedModel) SetIgnores(folder string, content []string) error {
return nil
}
+func (m *mockedModel) GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error) {
+ return nil, nil
+}
+
+func (m *mockedModel) RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error) {
+ return nil, nil
+}
+
func (m *mockedModel) PauseDevice(device protocol.DeviceID) {
}
diff --git a/gui/black/assets/css/theme.css b/gui/black/assets/css/theme.css
index 4a84eb5615..3092c6cad6 100644
--- a/gui/black/assets/css/theme.css
+++ b/gui/black/assets/css/theme.css
@@ -243,3 +243,7 @@ code.ng-binding{
.progress .frontal {
color: #222;
}
+
+.fancytree-title {
+ color: #aaa !important;
+}
diff --git a/gui/dark/assets/css/theme.css b/gui/dark/assets/css/theme.css
index 62fb99001c..3086ac1798 100644
--- a/gui/dark/assets/css/theme.css
+++ b/gui/dark/assets/css/theme.css
@@ -256,3 +256,6 @@ code.ng-binding{
color: #3fa9f0;
}
+.fancytree-title {
+ color: #aaa !important;
+}
diff --git a/gui/default/assets/css/overrides.css b/gui/default/assets/css/overrides.css
index b35f5f5717..32bef71f32 100644
--- a/gui/default/assets/css/overrides.css
+++ b/gui/default/assets/css/overrides.css
@@ -371,3 +371,7 @@ ul.three-columns li, ul.two-columns li {
.tab-content {
padding-top: 10px;
}
+
+.fancytree-ext-table {
+ width: 100% !important;
+}
diff --git a/gui/default/assets/css/theme.css b/gui/default/assets/css/theme.css
index 56dbc074d6..e54b06517e 100644
--- a/gui/default/assets/css/theme.css
+++ b/gui/default/assets/css/theme.css
@@ -27,3 +27,9 @@
.panel-heading:hover, .panel-heading:focus {
text-decoration: none;
}
+
+.fancytree-ext-filter-hide tr.fancytree-submatch span.fancytree-title,
+.fancytree-ext-filter-hide span.fancytree-node.fancytree-submatch span.fancytree-title {
+ color: black !important;
+ font-weight: lighter !important;
+}
diff --git a/gui/default/assets/lang/lang-en.json b/gui/default/assets/lang/lang-en.json
index 067929d52c..29d3010079 100644
--- a/gui/default/assets/lang/lang-en.json
+++ b/gui/default/assets/lang/lang-en.json
@@ -28,6 +28,7 @@
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
+ "Are you sure you want to restore {%count%} files?": "Are you sure you want to restore {{count}} files?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatic upgrade now offers the choice between stable releases and release candidates.",
"Automatic upgrades": "Automatic upgrades",
@@ -67,6 +68,8 @@
"Discovered": "Discovered",
"Discovery": "Discovery",
"Discovery Failures": "Discovery Failures",
+ "Do not restore": "Do not restore",
+ "Do not restore all": "Do not restore all",
"Documentation": "Documentation",
"Download Rate": "Download Rate",
"Downloaded": "Downloaded",
@@ -95,6 +98,8 @@
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.",
"Filesystem Notifications": "Filesystem Notifications",
+ "Filter by date": "Filter by date",
+ "Filter by name": "Filter by name",
"Folder": "Folder",
"Folder ID": "Folder ID",
"Folder Label": "Folder Label",
@@ -141,6 +146,7 @@
"Log tailing paused. Click here to continue.": "Log tailing paused. Click here to continue.",
"Logs": "Logs",
"Major Upgrade": "Major Upgrade",
+ "Mass actions": "Mass actions",
"Master": "Master",
"Maximum Age": "Maximum Age",
"Metadata Only": "Metadata Only",
@@ -201,6 +207,8 @@
"Restart": "Restart",
"Restart Needed": "Restart Needed",
"Restarting": "Restarting",
+ "Restore": "Restore",
+ "Restore Versions": "Restore Versions",
"Resume": "Resume",
"Resume All": "Resume All",
"Reused": "Reused",
@@ -210,6 +218,8 @@
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
"See external versioning help for supported templated command line parameters.": "See external versioning help for supported templated command line parameters.",
"Select a version": "Select a version",
+ "Select latest version": "Select latest version",
+ "Select oldest version": "Select oldest version",
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Send \u0026 Receive": "Send \u0026 Receive",
@@ -232,6 +242,7 @@
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
"Size": "Size",
"Smallest First": "Smallest First",
+ "Some items could not be restored:": "Some items could not be restored:",
"Source Code": "Source Code",
"Stable releases and release candidates": "Stable releases and release candidates",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.",
diff --git a/gui/default/index.html b/gui/default/index.html
index 193f126f82..1a74a45470 100644
--- a/gui/default/index.html
+++ b/gui/default/index.html
@@ -19,10 +19,12 @@
<title ng-bind="thisDeviceName() + ' | Syncthing'"></title>
<link href="vendor/bootstrap/css/bootstrap.css" rel="stylesheet"/>
+ <link href="vendor/bootstrap/css/daterangepicker.css" rel="stylesheet"/>
<link href="assets/font/raleway.css" rel="stylesheet"/>
<link href="vendor/font-awesome/css/font-awesome.css" rel="stylesheet"/>
<link href="assets/css/overrides.css" rel="stylesheet"/>
<link href="assets/css/theme.css" rel="stylesheet"/>
+ <link href="vendor/fancytree/css/ui.fancytree.css" rel="stylesheet"/>
</head>
<body>
@@ -434,6 +436,9 @@
<button ng-if="folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, false)">
<span class="fa fa-play"></span>&nbsp;<span translate>Resume</span>
</button>
+ <button type="button" class="btn btn-default btn-sm" ng-click="restoreVersions.show(folder.id)" ng-if="folder.versioning.type">
+ <span class="fa fa-undo"></span>&nbsp;<span translate>Versions</span>
+ </button>
<button type="button" class="btn btn-sm btn-default" ng-click="rescanFolder(folder.id)" ng-show="['idle', 'stopped', 'unshared'].indexOf(folderStatus(folder)) > -1">
<span class="fa fa-refresh"></span>&nbsp;<span translate>Rescan</span>
</button>
@@ -723,6 +728,8 @@
<ng-include src="'syncthing/device/globalChangesModalView.html'"></ng-include>
<ng-include src="'syncthing/folder/editFolderModalView.html'"></ng-include>
<ng-include src="'syncthing/folder/editIgnoresModalView.html'"></ng-include>
+ <ng-include src="'syncthing/folder/restoreVersionsModalView.html'"></ng-include>
+ <ng-include src="'syncthing/folder/restoreVersionsConfirmation.html'"></ng-include>
<ng-include src="'syncthing/settings/settingsModalView.html'"></ng-include>
<ng-include src="'syncthing/settings/advancedSettingsModalView.html'"></ng-include>
<ng-include src="'syncthing/usagereport/usageReportModalView.html'"></ng-include>
@@ -744,7 +751,10 @@
<script type="text/javascript" src="vendor/angular/angular-translate.js"></script>
<script type="text/javascript" src="vendor/angular/angular-translate-loader-static-files.js"></script>
<script type="text/javascript" src="vendor/angular/angular-dirPagination.js"></script>
+ <script type="text/javascript" src="vendor/moment/moment.js"></script>
<script type="text/javascript" src="vendor/bootstrap/js/bootstrap.js"></script>
+ <script type="text/javascript" src="vendor/bootstrap/js/daterangepicker.js"></script>
+ <script type="text/javascript" src="vendor/fancytree/jquery.fancytree-all-deps.js"></script>
<!-- / vendor scripts -->
<!-- gui application code -->
diff --git a/gui/default/syncthing/app.js b/gui/default/syncthing/app.js
index 6cd12b443a..ac9c89cb88 100644
--- a/gui/default/syncthing/app.js
+++ b/gui/default/syncthing/app.js
@@ -134,3 +134,75 @@ function debounce(func, wait) {
return result;
};
}
+
+function buildTree(children) {
+ /* Converts
+ *
+ * {
+ * 'foo/bar': [...],
+ * 'foo/baz': [...]
+ * }
+ *
+ * to
+ *
+ * [
+ * {
+ * title: 'foo',
+ * children: [
+ * {
+ * title: 'bar',
+ * versions: [...],
+ * ...
+ * },
+ * {
+ * title: 'baz',
+ * versions: [...],
+ * ...
+ * }
+ * ],
+ * }
+ * ]
+ */
+ var root = {
+ children: []
+ }
+
+ $.each(children, function(path, data) {
+ var parts = path.split('/');
+ var name = parts.splice(-1)[0];
+
+ var keySoFar = [];
+ var parent = root;
+ while (parts.length > 0) {
+ var part = parts.shift();
+ keySoFar.push(part);
+ var found = false;
+ for (var i = 0; i < parent.children.length; i++) {
+ if (parent.children[i].title == part) {
+ parent = parent.children[i];
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ var child = {
+ title: part,
+ key: keySoFar.join('/'),
+ folder: true,
+ children: []
+ }
+ parent.children.push(child);
+ parent = child;
+ }
+ }
+
+ parent.children.push({
+ title: name,
+ key: path,
+ folder: false,
+ versions: data,
+ });
+ });
+
+ return root.children;
+}
diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js
index 78c7cadabc..8e27440ff4 100755
--- a/gui/default/syncthing/core/syncthingController.js
+++ b/gui/default/syncthing/core/syncthingController.js
@@ -2,7 +2,7 @@ angular.module('syncthing.core')
.config(function($locationProvider) {
$locationProvider.html5Mode({enabled: true, requireBase: false}).hashPrefix('!');
})
- .controller('SyncthingController', function ($scope, $http, $location, LocaleService, Events, $filter, $q, $interval) {
+ .controller('SyncthingController', function ($scope, $http, $location, LocaleService, Events, $filter, $q, $compile, $timeout, $rootScope) {
'use strict';
// private/helper definitions
@@ -1107,9 +1107,9 @@ angular.module('syncthing.core')
},
show: function() {
$scope.logging.refreshFacilities();
- $scope.logging.timer = $interval($scope.logging.fetch, 0, 1);
+ $scope.logging.timer = $timeout($scope.logging.fetch);
$('#logViewer').modal().on('hidden.bs.modal', function () {
- $interval.cancel($scope.logging.timer);
+ $timeout.cancel($scope.logging.timer);
$scope.logging.timer = null;
$scope.logging.entries = [];
});
@@ -1138,7 +1138,7 @@ angular.module('syncthing.core')
var textArea = $('#logViewerText');
if (textArea.is(":focus")) {
if (!$scope.logging.timer) return;
- $scope.logging.timer = $interval($scope.logging.fetch, 500, 1);
+ $scope.logging.timer = $timeout($scope.logging.fetch, 500);
return;
}
@@ -1149,7 +1149,7 @@ angular.module('syncthing.core')
$http.get(urlbase + '/system/log' + (last ? '?since=' + encodeURIComponent(last) : '')).success(function (data) {
if (!$scope.logging.timer) return;
- $scope.logging.timer = $interval($scope.logging.fetch, 2000, 1);
+ $scope.logging.timer = $timeout($scope.logging.fetch, 2000);
if (!textArea.is(":focus")) {
if (data.messages) {
$scope.logging.entries.push.apply($scope.logging.entries, data.messages);
@@ -1767,6 +1767,233 @@ angular.module('syncthing.core')
});
};
+ function resetRestoreVersions() {
+ $scope.restoreVersions = {
+ folder: null,
+ selections: {},
+ versions: null,
+ tree: null,
+ errors: null,
+ filters: {},
+ massAction: function (name, action) {
+ $.each($scope.restoreVersions.versions, function(key) {
+ if (key.startsWith(name + '/') && (!$scope.restoreVersions.filters.text || key.indexOf($scope.restoreVersions.filters.text) > -1)) {
+ if (action == 'unset') {
+ delete $scope.restoreVersions.selections[key];
+ return;
+ }
+
+ var availableVersions = [];
+ $.each($scope.restoreVersions.filterVersions($scope.restoreVersions.versions[key]), function(idx, version) {
+ availableVersions.push(version.versionTime);
+ })
+
+ if (availableVersions.length) {
+ availableVersions.sort(function (a, b) { return a - b; });
+ if (action == 'latest') {
+ $scope.restoreVersions.selections[key] = availableVersions.pop();
+ } else if (action == 'oldest') {
+ $scope.restoreVersions.selections[key] = availableVersions.shift();
+ }
+ }
+ }
+ });
+ },
+ filterVersions: function(versions) {
+ var filteredVersions = [];
+ $.each(versions, function (idx, version) {
+ if (moment(version.versionTime).isBetween($scope.restoreVersions.filters['start'], $scope.restoreVersions.filters['end'], null, '[]')) {
+ filteredVersions.push(version);
+ }
+ });
+ return filteredVersions;
+ },
+ selectionCount: function() {
+ var count = 0;
+ $.each($scope.restoreVersions.selections, function(key, value) {
+ if (value) {
+ count++;
+ }
+ });
+ return count;
+ },
+
+ restore: function() {
+ $scope.restoreVersions.tree.clear();
+ $scope.restoreVersions.tree = null;
+ $scope.restoreVersions.versions = null;
+ var selections = {};
+ $.each($scope.restoreVersions.selections, function(key, value) {
+ if (value) {
+ selections[key] = value;
+ }
+ });
+ $scope.restoreVersions.selections = {};
+
+ $http.post(urlbase + '/folder/versions?folder=' + encodeURIComponent($scope.restoreVersions.folder), selections).success(function (data) {
+ if (Object.keys(data).length == 0) {
+ $('#restoreVersions').modal('hide');
+ } else {
+ $scope.restoreVersions.errors = data;
+ }
+ });
+ },
+ show: function(folder) {
+ $scope.restoreVersions.folder = folder;
+
+ var closed = false;
+ var modalShown = $q.defer();
+ $('#restoreVersions').modal().on('hidden.bs.modal', function () {
+ closed = true;
+ resetRestoreVersions();
+ }).on('shown.bs.modal', function() {
+ modalShown.resolve();
+ });
+
+ var dataReceived = $http.get(urlbase + '/folder/versions?folder=' + encodeURIComponent($scope.restoreVersions.folder))
+ .success(function (data) {
+ $.each(data, function(key, values) {
+ $.each(values, function(idx, value) {
+ value.modTime = new Date(value.modTime);
+ value.versionTime = new Date(value.versionTime);
+ });
+ });
+ if (closed) return;
+ $scope.restoreVersions.versions = data;
+ });
+
+ $q.all([dataReceived, modalShown.promise]).then(function() {
+ if (closed) {
+ resetRestoreVersions();
+ return;
+ }
+
+ $scope.restoreVersions.tree = $("#restoreTree").fancytree({
+ extensions: ["table", "filter"],
+ quicksearch: true,
+ filter: {
+ autoApply: true,
+ counter: true,
+ hideExpandedCounter: true,
+ hideExpanders: true,
+ highlight: true,
+ leavesOnly: false,
+ nodata: true,
+ mode: "hide"
+ },
+ table: {
+ indentation: 20,
+ nodeColumnIdx: 0,
+ },
+ debugLevel: 2,
+ source: buildTree($scope.restoreVersions.versions),
+ renderColumns: function(event, data) {
+ var node = data.node,
+ $tdList = $(node.tr).find(">td"),
+ template;
+ if (node.folder) {
+ template = '<div ng-include="\'syncthing/folder/restoreVersionsMassActions.html\'" class="pull-right"/>';
+ } else {
+ template = '<div ng-include="\'syncthing/folder/restoreVersionsVersionSelector.html\'" class="pull-right"/>';
+ }
+
+ var scope = $rootScope.$new(true);
+ scope.key = node.key;
+ scope.restoreVersions = $scope.restoreVersions;
+
+ $tdList.eq(1).html(
+ $compile(template)(scope)
+ );
+
+ // Force angular to redraw.
+ $timeout(function() {
+ $scope.$apply();
+ });
+ }
+ }).fancytree("getTree");
+
+ var minDate = moment(),
+ maxDate = moment(0, 'X'),
+ date;
+
+ // Find version window.
+ $.each($scope.restoreVersions.versions, function(key) {
+ $.each($scope.restoreVersions.versions[key], function(idx, version) {
+ date = moment(version.versionTime);
+ if (date.isBefore(minDate)) {
+ minDate = date;
+ }
+ if (date.isAfter(maxDate)) {
+ maxDate = date;
+ }
+ });
+ });
+
+ $scope.restoreVersions.filters['start'] = minDate;
+ $scope.restoreVersions.filters['end'] = maxDate;
+
+ var ranges = {
+ 'All time': [minDate, maxDate],
+ 'Today': [moment(), moment()],
+ 'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
+ 'Last 7 Days': [moment().subtract(6, 'days'), moment()],
+ 'Last 30 Days': [moment().subtract(29, 'days'), moment()],
+ 'This Month': [moment().startOf('month'), moment().endOf('month')],
+ 'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
+ };
+
+ // Filter out invalid ranges.
+ $.each(ranges, function(key, range) {
+ if (!range[0].isBetween(minDate, maxDate, null, '[]') && !range[1].isBetween(minDate, maxDate, null, '[]')) {
+ delete ranges[key];
+ }
+ });
+
+ $("#restoreVersionDateRange").daterangepicker({
+ timePicker: true,
+ timePicker24Hour: true,
+ timePickerSeconds: true,
+ autoUpdateInput: true,
+ opens: "left",
+ drops: "up",
+ startDate: minDate,
+ endDate: maxDate,
+ minDate: minDate,
+ maxDate: maxDate,
+ ranges: ranges,
+ locale: {
+ format: 'YYYY/MM/DD HH:mm:ss',
+ }
+ }).on('apply.daterangepicker', function(ev, picker) {
+ $scope.restore