mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-05 12:57:55 +00:00
parent
40bb52fdd8
commit
21d04b895a
@ -10,8 +10,10 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -25,6 +27,7 @@ import (
|
||||
type APIClient interface {
|
||||
Get(url string) (*http.Response, error)
|
||||
Post(url, body string) (*http.Response, error)
|
||||
PutJSON(url string, o interface{}) (*http.Response, error)
|
||||
}
|
||||
|
||||
type apiClient struct {
|
||||
@ -118,20 +121,36 @@ func (c *apiClient) Do(req *http.Request) (*http.Response, error) {
|
||||
return resp, checkResponse(resp)
|
||||
}
|
||||
|
||||
func (c *apiClient) Get(url string) (*http.Response, error) {
|
||||
request, err := http.NewRequest("GET", c.Endpoint()+"rest/"+url, nil)
|
||||
func (c *apiClient) Request(url, method string, r io.Reader) (*http.Response, error) {
|
||||
request, err := http.NewRequest(method, c.Endpoint()+"rest/"+url, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Do(request)
|
||||
}
|
||||
|
||||
func (c *apiClient) Post(url, body string) (*http.Response, error) {
|
||||
request, err := http.NewRequest("POST", c.Endpoint()+"rest/"+url, bytes.NewBufferString(body))
|
||||
func (c *apiClient) RequestString(url, method, data string) (*http.Response, error) {
|
||||
return c.Request(url, method, bytes.NewBufferString(data))
|
||||
}
|
||||
|
||||
func (c *apiClient) RequestJSON(url, method string, o interface{}) (*http.Response, error) {
|
||||
data, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Do(request)
|
||||
return c.Request(url, method, bytes.NewBuffer(data))
|
||||
}
|
||||
|
||||
func (c *apiClient) Get(url string) (*http.Response, error) {
|
||||
return c.RequestString(url, "GET", "")
|
||||
}
|
||||
|
||||
func (c *apiClient) Post(url, body string) (*http.Response, error) {
|
||||
return c.RequestString(url, "POST", body)
|
||||
}
|
||||
|
||||
func (c *apiClient) PutJSON(url string, o interface{}) (*http.Response, error) {
|
||||
return c.RequestJSON(url, "PUT", o)
|
||||
}
|
||||
|
||||
var errNotFound = errors.New("invalid endpoint or API call")
|
||||
|
@ -7,8 +7,12 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@ -38,6 +42,12 @@ var operationCommand = cli.Command{
|
||||
ArgsUsage: "[folder id]",
|
||||
Action: expects(1, foldersOverride),
|
||||
},
|
||||
{
|
||||
Name: "default-ignores",
|
||||
Usage: "Set the default ignores (config) from a file",
|
||||
ArgsUsage: "path",
|
||||
Action: expects(1, setDefaultIgnores),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -74,3 +84,29 @@ func foldersOverride(c *cli.Context) error {
|
||||
}
|
||||
return fmt.Errorf("Folder " + rid + " not found")
|
||||
}
|
||||
|
||||
func setDefaultIgnores(c *cli.Context) error {
|
||||
client, err := getClientFactory(c).getClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir, file := filepath.Split(c.Args()[0])
|
||||
filesystem := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
|
||||
|
||||
fd, err := filesystem.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scanner := bufio.NewScanner(fd)
|
||||
var lines []string
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
fd.Close()
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.PutJSON("config/defaults/ignores", config.Ignores{Lines: lines})
|
||||
return err
|
||||
}
|
||||
|
@ -58,6 +58,9 @@ angular.module('syncthing.core')
|
||||
text: '',
|
||||
error: null,
|
||||
disabled: false,
|
||||
originalLines: [],
|
||||
defaultLines: [],
|
||||
saved: false,
|
||||
};
|
||||
resetRemoteNeed();
|
||||
|
||||
@ -409,8 +412,14 @@ angular.module('syncthing.core')
|
||||
console.log("FolderScanProgress", data);
|
||||
});
|
||||
|
||||
// May be called through .error with the presented arguments, or through
|
||||
// .catch with the http response object containing the same arguments.
|
||||
$scope.emitHTTPError = function (data, status, headers, config) {
|
||||
$scope.$emit('HTTPError', { data: data, status: status, headers: headers, config: config });
|
||||
var out = data;
|
||||
if (data && !data.data) {
|
||||
out = { data: data, status: status, headers: headers, config: config };
|
||||
}
|
||||
$scope.$emit('HTTPError', out);
|
||||
};
|
||||
|
||||
var debouncedFuncs = {};
|
||||
@ -741,7 +750,7 @@ angular.module('syncthing.core')
|
||||
}
|
||||
|
||||
function shouldSetDefaultFolderPath() {
|
||||
return $scope.config.defaults.folder.path && !$scope.editingExisting && $scope.folderEditor.folderPath.$pristine && !$scope.editingDefaults;
|
||||
return $scope.config.defaults.folder.path && $scope.folderEditor.folderPath.$pristine && $scope.currentFolder._editing == "add";
|
||||
}
|
||||
|
||||
function resetRemoteNeed() {
|
||||
@ -750,7 +759,6 @@ angular.module('syncthing.core')
|
||||
$scope.remoteNeedDevice = undefined;
|
||||
}
|
||||
|
||||
|
||||
function setDefaultTheme() {
|
||||
if (!document.getElementById("fallback-theme-css")) {
|
||||
|
||||
@ -767,13 +775,9 @@ angular.module('syncthing.core')
|
||||
}
|
||||
}
|
||||
|
||||
function saveIgnores(ignores, cb) {
|
||||
$http.post(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id), {
|
||||
function saveIgnores(ignores) {
|
||||
return $http.post(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id), {
|
||||
ignore: ignores
|
||||
}).success(function () {
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -1268,8 +1272,9 @@ angular.module('syncthing.core')
|
||||
if (cfg) {
|
||||
cfg.paused = pause;
|
||||
$scope.config.folders = folderList($scope.folders);
|
||||
$scope.saveConfig();
|
||||
return $scope.saveConfig();
|
||||
}
|
||||
return $q.when();
|
||||
};
|
||||
|
||||
$scope.showListenerStatus = function () {
|
||||
@ -1421,18 +1426,14 @@ angular.module('syncthing.core')
|
||||
});
|
||||
};
|
||||
|
||||
$scope.saveConfig = function (callback) {
|
||||
$scope.saveConfig = function () {
|
||||
var cfg = JSON.stringify($scope.config);
|
||||
var opts = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
$http.put(urlbase + '/config', cfg, opts).finally(refreshConfig).then(function() {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}, $scope.emitHTTPError);
|
||||
return $http.put(urlbase + '/config', cfg, opts).finally(refreshConfig).catch($scope.emitHTTPError);
|
||||
};
|
||||
|
||||
$scope.urVersions = function () {
|
||||
@ -1512,7 +1513,7 @@ angular.module('syncthing.core')
|
||||
// here as well...
|
||||
$scope.devices = deviceMap($scope.config.devices);
|
||||
|
||||
$scope.saveConfig(function () {
|
||||
$scope.saveConfig.then(function () {
|
||||
if (themeChanged) {
|
||||
document.location.reload(true);
|
||||
}
|
||||
@ -1578,11 +1579,11 @@ angular.module('syncthing.core')
|
||||
}
|
||||
|
||||
$scope.editDeviceModalTitle = function() {
|
||||
if ($scope.editingDefaults) {
|
||||
if ($scope.editingDeviceDefaults()) {
|
||||
return $translate.instant("Edit Device Defaults");
|
||||
}
|
||||
var title = '';
|
||||
if ($scope.editingExisting) {
|
||||
if ($scope.editingDeviceExisting()) {
|
||||
title += $translate.instant("Edit Device");
|
||||
} else {
|
||||
title += $translate.instant("Add Device");
|
||||
@ -1595,16 +1596,23 @@ angular.module('syncthing.core')
|
||||
};
|
||||
|
||||
$scope.editDeviceModalIcon = function() {
|
||||
if ($scope.editingDefaults || $scope.editingExisting) {
|
||||
if ($scope.has(["existing", "defaults"], $scope.currentDevice._editing)) {
|
||||
return 'fas fa-pencil-alt';
|
||||
}
|
||||
return 'fas fa-desktop';
|
||||
};
|
||||
|
||||
$scope.editingDeviceDefaults = function() {
|
||||
return $scope.currentDevice._editing == 'defaults';
|
||||
}
|
||||
|
||||
$scope.editingDeviceExisting = function() {
|
||||
return $scope.currentDevice._editing == 'existing';
|
||||
}
|
||||
|
||||
$scope.editDeviceExisting = function (deviceCfg) {
|
||||
$scope.currentDevice = $.extend({}, deviceCfg);
|
||||
$scope.editingExisting = true;
|
||||
$scope.editingDefaults = false;
|
||||
$scope.currentDevice._editing = "existing";
|
||||
$scope.willBeReintroducedBy = undefined;
|
||||
if (deviceCfg.introducedBy) {
|
||||
var introducerDevice = $scope.devices[deviceCfg.introducedBy];
|
||||
@ -1633,7 +1641,7 @@ angular.module('syncthing.core')
|
||||
$scope.editDeviceDefaults = function () {
|
||||
$http.get(urlbase + '/config/defaults/device').then(function (p) {
|
||||
$scope.currentDevice = p.data;
|
||||
$scope.editingDefaults = true;
|
||||
$scope.currentDevice._editing = "defaults";
|
||||
editDeviceModal();
|
||||
}, $scope.emitHTTPError);
|
||||
};
|
||||
@ -1671,8 +1679,7 @@ angular.module('syncthing.core')
|
||||
$scope.currentDevice = p.data;
|
||||
$scope.currentDevice.name = name;
|
||||
$scope.currentDevice.deviceID = deviceID;
|
||||
$scope.editingExisting = false;
|
||||
$scope.editingDefaults = false;
|
||||
$scope.currentDevice._editing = "add";
|
||||
initShareEditing('device');
|
||||
$scope.currentSharing.unrelated = $scope.folderList();
|
||||
editDeviceModal();
|
||||
@ -1682,7 +1689,7 @@ angular.module('syncthing.core')
|
||||
|
||||
$scope.deleteDevice = function () {
|
||||
$('#editDevice').modal('hide');
|
||||
if (!$scope.editingExisting) {
|
||||
if ($scope.currentDevice._editing != "existing") {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1705,13 +1712,13 @@ angular.module('syncthing.core')
|
||||
return x.trim();
|
||||
});
|
||||
delete $scope.currentDevice._addressesStr;
|
||||
if ($scope.editingDefaults) {
|
||||
if ($scope.currentDevice._editing == "defaults") {
|
||||
$scope.config.defaults.device = $scope.currentDevice;
|
||||
} else {
|
||||
setDeviceConfig();
|
||||
}
|
||||
delete $scope.currentSharing;
|
||||
delete $scope.currentDevice;
|
||||
$scope.currentDevice = {};
|
||||
$scope.saveConfig();
|
||||
};
|
||||
|
||||
@ -1955,20 +1962,34 @@ angular.module('syncthing.core')
|
||||
$('#folder-ignores textarea').focus();
|
||||
}
|
||||
}).one('hidden.bs.modal', function () {
|
||||
window.location.hash = "";
|
||||
$scope.currentFolder = {};
|
||||
var p = $q.when();
|
||||
// If the modal was closed default patterns should still apply
|
||||
if ($scope.currentFolder._editing == "add-ignores" && !$scope.ignores.saved && $scope.ignores.defaultLines) {
|
||||
p = saveFolderAddIgnores($scope.currentFolder.id, true);
|
||||
}
|
||||
p.then(function () {
|
||||
window.location.hash = "";
|
||||
$scope.currentFolder = {};
|
||||
$scope.ignores = {};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editFolderModalTitle = function() {
|
||||
if ($scope.editingDefaults) {
|
||||
if ($scope.editingFolderDefaults()) {
|
||||
return $translate.instant("Edit Folder Defaults");
|
||||
}
|
||||
var title = '';
|
||||
if ($scope.editingExisting) {
|
||||
title += $translate.instant("Edit Folder");
|
||||
} else {
|
||||
title += $translate.instant("Add Folder");
|
||||
switch ($scope.currentFolder._editing) {
|
||||
case "existing":
|
||||
title = $translate.instant("Edit Folder");
|
||||
break;
|
||||
case "add":
|
||||
title = $translate.instant("Add Folder");
|
||||
break;
|
||||
case "add-ignores":
|
||||
title = $translate.instant("Set Ignores on Added Folder");
|
||||
break;
|
||||
}
|
||||
if ($scope.currentFolder.id !== '') {
|
||||
title += ' (' + $scope.folderLabel($scope.currentFolder.id) + ')';
|
||||
@ -1977,12 +1998,20 @@ angular.module('syncthing.core')
|
||||
};
|
||||
|
||||
$scope.editFolderModalIcon = function() {
|
||||
if ($scope.editingDefaults || $scope.editingExisting) {
|
||||
if ($scope.has(["existing", "defaults"], $scope.currentFolder._editing)) {
|
||||
return 'fas fa-pencil-alt';
|
||||
}
|
||||
return 'fas fa-folder';
|
||||
};
|
||||
|
||||
$scope.editingFolderDefaults = function() {
|
||||
return $scope.currentFolder._editing == 'defaults';
|
||||
}
|
||||
|
||||
$scope.editingFolderExisting = function() {
|
||||
return $scope.currentFolder._editing == 'existing';
|
||||
}
|
||||
|
||||
function editFolder(initialTab) {
|
||||
if ($scope.currentFolder.path.length > 1 && $scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
|
||||
$scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
|
||||
@ -2033,39 +2062,60 @@ angular.module('syncthing.core')
|
||||
};
|
||||
|
||||
$scope.editFolderExisting = function(folderCfg, initialTab) {
|
||||
$scope.editingExisting = true;
|
||||
$scope.editingDefaults = false;
|
||||
$scope.currentFolder = angular.copy(folderCfg);
|
||||
|
||||
$scope.ignores.text = 'Loading...';
|
||||
$scope.ignores.error = null;
|
||||
$scope.ignores.disabled = true;
|
||||
$http.get(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id))
|
||||
.success(function (data) {
|
||||
$scope.currentFolder.ignores = data.ignore || [];
|
||||
$scope.ignores.text = $scope.currentFolder.ignores.join('\n');
|
||||
$scope.ignores.error = data.error;
|
||||
$scope.ignores.disabled = false;
|
||||
})
|
||||
.error(function (err) {
|
||||
$scope.ignores.text = $translate.instant("Failed to load ignore patterns.");
|
||||
$scope.emitHTTPError(err);
|
||||
});
|
||||
|
||||
$scope.currentFolder._editing = "existing";
|
||||
editFolderLoadIgnores();
|
||||
editFolder(initialTab);
|
||||
};
|
||||
|
||||
$scope.editFolderDefaults = function() {
|
||||
$http.get(urlbase + '/config/defaults/folder')
|
||||
.success(function (data) {
|
||||
$scope.currentFolder = data;
|
||||
$scope.editingExisting = false;
|
||||
$scope.editingDefaults = true;
|
||||
editFolder();
|
||||
})
|
||||
.error($scope.emitHTTPError);
|
||||
function editFolderLoadingIgnores() {
|
||||
$scope.ignores.text = 'Loading...';
|
||||
$scope.ignores.error = null;
|
||||
$scope.ignores.disabled = true;
|
||||
}
|
||||
|
||||
function editFolderGetIgnores() {
|
||||
return $http.get(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id))
|
||||
.then(function(r) {
|
||||
return r.data;
|
||||
}, function (response) {
|
||||
$scope.ignores.text = $translate.instant("Failed to load ignore patterns.");
|
||||
return $q.reject(response);
|
||||
});
|
||||
};
|
||||
|
||||
function editFolderLoadIgnores() {
|
||||
editFolderLoadingIgnores();
|
||||
return editFolderGetIgnores().then(editFolderInitIgnores, $scope.emitHTTPError);
|
||||
}
|
||||
|
||||
$scope.editFolderDefaults = function() {
|
||||
$q.all([
|
||||
$http.get(urlbase + '/config/defaults/folder').then(function (response) {
|
||||
$scope.currentFolder = response.data;
|
||||
$scope.currentFolder._editing = "defaults";
|
||||
}),
|
||||
getDefaultIgnores().then(editFolderInitIgnores),
|
||||
]).then(editFolder, $scope.emitHTTPError);
|
||||
};
|
||||
|
||||
function getDefaultIgnores() {
|
||||
return $http.get(urlbase + '/config/defaults/ignores').then(function (r) {
|
||||
return r.data.lines;
|
||||
});
|
||||
}
|
||||
|
||||
function editFolderInitIgnores(data) {
|
||||
$scope.ignores.originalLines = data.ignore || [];
|
||||
setIgnoresText(data.ignore);
|
||||
$scope.ignores.error = data.error;
|
||||
$scope.ignores.disabled = false;
|
||||
}
|
||||
|
||||
function setIgnoresText(lines) {
|
||||
$scope.ignores.text = lines ? lines.join('\n') : "";
|
||||
}
|
||||
|
||||
$scope.selectAllSharedDevices = function (state) {
|
||||
var devices = $scope.currentSharing.shared;
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
@ -2093,9 +2143,6 @@ angular.module('syncthing.core')
|
||||
|
||||
$scope.addFolderAndShare = function (folderID, pendingFolder, device) {
|
||||
addFolderInit(folderID).then(function() {
|
||||
$scope.currentFolder.viewFlags = {
|
||||
importFromOtherDevice: true
|
||||
};
|
||||
$scope.currentSharing.selected[device] = true;
|
||||
$scope.currentFolder.label = pendingFolder.offeredBy[device].label;
|
||||
for (var k in pendingFolder.offeredBy) {
|
||||
@ -2110,19 +2157,16 @@ angular.module('syncthing.core')
|
||||
};
|
||||
|
||||
function addFolderInit(folderID) {
|
||||
$scope.editingExisting = false;
|
||||
$scope.editingDefaults = false;
|
||||
return $http.get(urlbase + '/config/defaults/folder').then(function(p) {
|
||||
$scope.currentFolder = p.data;
|
||||
return $http.get(urlbase + '/config/defaults/folder').then(function (response) {
|
||||
$scope.currentFolder = response.data;
|
||||
$scope.currentFolder._editing = "add";
|
||||
$scope.currentFolder.id = folderID;
|
||||
|
||||
initShareEditing('folder');
|
||||
$scope.currentSharing.unrelated = $scope.currentSharing.unrelated.concat($scope.currentSharing.shared);
|
||||
$scope.currentSharing.shared = [];
|
||||
|
||||
$scope.ignores.text = '';
|
||||
$scope.ignores.error = null;
|
||||
$scope.ignores.disabled = false;
|
||||
// Ignores don't need to be initialized here, as that happens in
|
||||
// a second step if the user indicates in the creation modal
|
||||
// that they want to set ignores
|
||||
}, $scope.emitHTTPError);
|
||||
}
|
||||
|
||||
@ -2142,7 +2186,14 @@ angular.module('syncthing.core')
|
||||
};
|
||||
|
||||
$scope.saveFolder = function () {
|
||||
$('#editFolder').modal('hide');
|
||||
if ($scope.currentFolder._editing == "add-ignores") {
|
||||
// On modal being hidden without clicking save, the defaults will be saved.
|
||||
$scope.ignores.saved = true;
|
||||
saveFolderAddIgnores($scope.currentFolder.id);
|
||||
hideFolderModal();
|
||||
return;
|
||||
}
|
||||
|
||||
var folderCfg = angular.copy($scope.currentFolder);
|
||||
$scope.currentSharing.selected[$scope.myID] = true;
|
||||
var newDevices = [];
|
||||
@ -2191,44 +2242,88 @@ angular.module('syncthing.core')
|
||||
}
|
||||
delete folderCfg._guiVersioning;
|
||||
|
||||
if ($scope.editingDefaults) {
|
||||
if ($scope.currentFolder._editing == "defaults") {
|
||||
hideFolderModal();
|
||||
$scope.config.defaults.ignores.lines = ignoresArray();
|
||||
$scope.config.defaults.folder = folderCfg;
|
||||
$scope.saveConfig();
|
||||
} else {
|
||||
saveFolderExisting(folderCfg);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a new folder where ignores should apply before it first starts.
|
||||
if ($scope.currentFolder._addIgnores) {
|
||||
folderCfg.paused = true;
|
||||
}
|
||||
$scope.folders[folderCfg.id] = folderCfg;
|
||||
$scope.config.folders = folderList($scope.folders);
|
||||
|
||||
if ($scope.currentFolder._editing == "existing") {
|
||||
hideFolderModal();
|
||||
saveFolderIgnoresExisting();
|
||||
$scope.saveConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
// No ignores to be set on the new folder, save directly.
|
||||
if (!$scope.currentFolder._addIgnores) {
|
||||
hideFolderModal();
|
||||
$scope.saveConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add folder (paused), load existing ignores and if there are none,
|
||||
// load default ignores, then let the user edit them.
|
||||
$scope.saveConfig().then(function() {
|
||||
editFolderLoadingIgnores();
|
||||
$scope.currentFolder._editing = "add-ignores";
|
||||
$('.nav-tabs a[href="#folder-ignores"]').tab('show');
|
||||
return editFolderGetIgnores();
|
||||
}).then(function(data) {
|
||||
// Error getting ignores -> leave error message.
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
if ((data.ignore && data.ignore.length > 0) || data.error) {
|
||||
editFolderInitIgnores(data);
|
||||
} else {
|
||||
getDefaultIgnores().then(function(lines) {
|
||||
setIgnoresText(lines);
|
||||
$scope.ignores.defaultLines = lines;
|
||||
$scope.ignores.disabled = false;
|
||||
});
|
||||
}
|
||||
}, $scope.emitHTTPError);
|
||||
};
|
||||
|
||||
function saveFolderExisting(folderCfg) {
|
||||
var ignoresLoaded = !$scope.ignores.disabled;
|
||||
function saveFolderIgnoresExisting() {
|
||||
if ($scope.ignores.disabled) {
|
||||
return;
|
||||
}
|
||||
var ignores = ignoresArray();
|
||||
|
||||
function arrayDiffers(a, b) {
|
||||
return !a !== !b || a.length !== b.length || a.some(function(v, i) { return v !== b[i]; });
|
||||
}
|
||||
if (arrayDiffers(ignores, $scope.ignores.originalLines)) {
|
||||
return saveIgnores(ignores);
|
||||
};
|
||||
}
|
||||
|
||||
function saveFolderAddIgnores(folderID, useDefault) {
|
||||
var ignores = useDefault ? $scope.ignores.defaultLines : ignoresArray();
|
||||
return saveIgnores(ignores).then(function () {
|
||||
return $scope.setFolderPause(folderID, $scope.currentFolder.paused);
|
||||
});
|
||||
};
|
||||
|
||||
function ignoresArray() {
|
||||
var ignores = $scope.ignores.text.split('\n');
|
||||
// Split always returns a minimum 1-length array even for no patterns
|
||||
if (ignores.length === 1 && ignores[0] === "") {
|
||||
ignores = [];
|
||||
}
|
||||
if (!$scope.editingExisting && ignores.length) {
|
||||
folderCfg.paused = true;
|
||||
};
|
||||
|
||||
$scope.folders[folderCfg.id] = folderCfg;
|
||||
$scope.config.folders = folderList($scope.folders);
|
||||
|
||||
function arrayEquals(a, b) {
|
||||
return a.length === b.length && a.every(function(v, i) { return v === b[i] });
|
||||
}
|
||||
|
||||
if (ignoresLoaded && $scope.editingExisting && !arrayEquals(ignores, folderCfg.ignores)) {
|
||||
saveIgnores(ignores);
|
||||
};
|
||||
|
||||
$scope.saveConfig(function () {
|
||||
if (!$scope.editingExisting && ignores.length) {
|
||||
saveIgnores(ignores, function () {
|
||||
$scope.setFolderPause(folderCfg.id, false);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
return ignores;
|
||||
}
|
||||
|
||||
$scope.ignoreFolder = function (device, folderID, offeringDevice) {
|
||||
var ignoredFolder = {
|
||||
@ -2282,8 +2377,8 @@ angular.module('syncthing.core')
|
||||
};
|
||||
|
||||
$scope.deleteFolder = function (id) {
|
||||
$('#editFolder').modal('hide');
|
||||
if (!$scope.editingExisting) {
|
||||
hideFolderModal();
|
||||
if ($scope.currentFolder._editing != "existing") {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2295,6 +2390,10 @@ angular.module('syncthing.core')
|
||||
$scope.saveConfig();
|
||||
};
|
||||
|
||||
function hideFolderModal() {
|
||||
$('#editFolder').modal('hide');
|
||||
}
|
||||
|
||||
function resetRestoreVersions() {
|
||||
$scope.restoreVersions = {
|
||||
folder: null,
|
||||
@ -2839,6 +2938,10 @@ angular.module('syncthing.core')
|
||||
return Object.keys(dict).length;
|
||||
};
|
||||
|
||||
$scope.has = function (array, element) {
|
||||
return array.indexOf(element) >= 0;
|
||||
};
|
||||
|
||||
$scope.dismissNotification = function (id) {
|
||||
var idx = $scope.config.options.unackedNotificationIDs.indexOf(id);
|
||||
if (idx > -1) {
|
||||
|
@ -4,7 +4,7 @@ angular.module('syncthing.core')
|
||||
require: 'ngModel',
|
||||
link: function (scope, elm, attrs, ctrl) {
|
||||
ctrl.$parsers.unshift(function (viewValue) {
|
||||
if (scope.editingExisting) {
|
||||
if (scope.currentFolder._editing != "add") {
|
||||
// we shouldn't validate
|
||||
ctrl.$setValidity('uniqueFolder', true);
|
||||
} else if (scope.folders.hasOwnProperty(viewValue)) {
|
||||
|
@ -4,7 +4,7 @@ angular.module('syncthing.core')
|
||||
require: 'ngModel',
|
||||
link: function (scope, elm, attrs, ctrl) {
|
||||
ctrl.$parsers.unshift(function (viewValue) {
|
||||
if (scope.editingExisting) {
|
||||
if (scope.currentDevice._editing != "add") {
|
||||
// we shouldn't validate
|
||||
ctrl.$setValidity('validDeviceid', true);
|
||||
} else {
|
||||
|
@ -3,14 +3,14 @@
|
||||
<form role="form" name="deviceEditor">
|
||||
<ul class="nav nav-tabs" ng-init="loadFormIntoScope(deviceEditor)">
|
||||
<li class="active"><a data-toggle="tab" href="#device-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
|
||||
<li ng-if="!editingDefaults"><a data-toggle="tab" href="#device-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
|
||||
<li ng-if="!editingDeviceDefaults()"><a data-toggle="tab" href="#device-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
|
||||
<li><a data-toggle="tab" href="#device-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div id="device-general" class="tab-pane in active">
|
||||
<div ng-if="!editingDefaults" class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}" ng-init="loadFormIntoScope(deviceEditor)">
|
||||
<div ng-if="!editingDeviceDefaults()" class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}" ng-init="loadFormIntoScope(deviceEditor)">
|
||||
<label translate for="deviceID">Device ID</label>
|
||||
<div ng-if="!editingExisting">
|
||||
<div ng-if="!editingDeviceExisting()">
|
||||
<div class="input-group">
|
||||
<input name="deviceID" id="deviceID" class="form-control text-monospace" type="text" ng-model="currentDevice.deviceID" required="" valid-deviceid list="discovery-list" aria-required="true" />
|
||||
<div class="input-group-btn">
|
||||
@ -40,7 +40,7 @@
|
||||
<span translate ng-if="deviceEditor.deviceID.$error.unique && deviceEditor.deviceID.$dirty">A device with that ID is already added.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div ng-if="editingExisting" class="input-group">
|
||||
<div ng-if="editingDeviceExisting()" class="input-group">
|
||||
<div class="well well-sm text-monospace form-control" style="height: auto;" select-on-click>{{currentDevice.deviceID}}</div>
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#idqr">
|
||||
@ -56,7 +56,7 @@
|
||||
<p translate ng-if="currentDevice.deviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="!editingDefaults" id="device-sharing" class="tab-pane">
|
||||
<div ng-if="!editingDeviceDefaults()" id="device-sharing" class="tab-pane">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
@ -172,7 +172,7 @@
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
|
||||
<span class="fas fa-times"></span> <span translate>Close</span>
|
||||
</button>
|
||||
<div ng-if="editingExisting && !editingDefaults" class="pull-left">
|
||||
<div ng-if="has(['existing', 'defaults'], currentDevice._editing)" class="pull-left">
|
||||
<button type="button" class="btn btn-warning btn-sm" data-toggle="modal" data-target="#remove-device-confirmation">
|
||||
<span class="fas fa-minus-circle"></span> <span translate>Remove</span>
|
||||
</button>
|
||||
|
@ -2,44 +2,44 @@
|
||||
<div class="modal-body">
|
||||
<form role="form" name="folderEditor">
|
||||
<ul class="nav nav-tabs" ng-init="loadFormIntoScope(folderEditor)">
|
||||
<li class="active"><a data-toggle="tab" href="#folder-general"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
|
||||
<li><a data-toggle="tab" href="#folder-sharing"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
|
||||
<li><a data-toggle="tab" href="#folder-versioning"><span class="fas fa-copy"></span> <span translate>File Versioning</span></a></li>
|
||||
<li ng-if="!editingDefaults" ng-class="{'disabled': currentFolder._recvEnc}"><a ng-attr-data-toggle="{{ currentFolder._recvEnc ? undefined : 'tab'}}" href="{{currentFolder._recvEnc ? '#' : '#folder-ignores'}}"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
|
||||
<li><a data-toggle="tab" href="#folder-advanced"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
|
||||
<li ng-class="{'disabled': currentFolder._editing == 'add-ignores'}" class="active"><a data-toggle="tab" href="{{currentFolder._editing == 'add-ignores' ? '' : '#folder-general'}}"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
|
||||
<li ng-class="{'disabled': currentFolder._editing == 'add-ignores'}"><a data-toggle="tab" href="{{currentFolder._editing == 'add-ignores' ? '' : '#folder-sharing'}}"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
|
||||
<li ng-class="{'disabled': currentFolder._editing == 'add-ignores'}"><a data-toggle="tab" href="{{currentFolder._editing == 'add-ignores' ? '' : '#folder-versioning'}}"><span class="fas fa-copy"></span> <span translate>File Versioning</span></a></li>
|
||||
<li ng-class="{'disabled': currentFolder._recvEnc}"><a data-toggle="tab" href="{{currentFolder._recvEnc ? '' : '#folder-ignores'}}"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
|
||||
<li ng-class="{'disabled': currentFolder._editing == 'add-ignores'}"><a data-toggle="tab" href="{{currentFolder._editing == 'add-ignores' ? '' : '#folder-advanced'}}"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
|
||||
<div id="folder-general" class="tab-pane in active">
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty && !editingDefaults}">
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderLabel.$invalid && folderEditor.folderLabel.$dirty && !editingFolderDefaults()}">
|
||||
<label for="folderLabel"><span translate>Folder Label</span></label>
|
||||
<input name="folderLabel" id="folderLabel" class="form-control" type="text" ng-model="currentFolder.label" value="{{currentFolder.label}}" />
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.folderLabel.$valid || folderEditor.folderLabel.$pristine">Optional descriptive label for the folder. Can be different on each device.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div ng-if="!editingDefaults" class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
|
||||
<div ng-if="!editingFolderDefaults()" class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
|
||||
<label for="folderID"><span translate>Folder ID</span></label>
|
||||
<input name="folderID" ng-readonly="editingExisting || (!editingExisting && currentFolder.viewFlags.importFromOtherDevice)" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required="" aria-required="true" unique-folder value="{{currentFolder.id}}" />
|
||||
<input name="folderID" ng-readonly="has(['existing', 'add'], currentFolder._editing)" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required="" aria-required="true" unique-folder value="{{currentFolder.id}}" />
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.folderID.$valid || folderEditor.folderID.$pristine">Required identifier for the folder. Must be the same on all cluster devices.</span>
|
||||
<span translate ng-if="folderEditor.folderID.$error.uniqueFolder">The folder ID must be unique.</span>
|
||||
<span translate ng-if="folderEditor.folderID.$error.required && folderEditor.folderID.$dirty">The folder ID cannot be blank.</span>
|
||||
<span translate ng-show="!editingExisting">When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.</span>
|
||||
<span translate ng-show="!editingFolderExisting()">When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty && !editingDefaults}">
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty && !editingFolderDefaults()}">
|
||||
<label translate for="folderPath">Folder Path</label>
|
||||
<input name="folderPath" ng-readonly="editingExisting && !editingDefaults" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" ng-required="!editingDefaults" ng-aria-required="!editingDefaults" path-is-sub-dir />
|
||||
<input name="folderPath" ng-readonly="editingFolderExisting()" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" ng-required="!editingFolderDefaults()" ng-aria-required="!editingFolderDefaults()" path-is-sub-dir />
|
||||
<datalist id="directory-list">
|
||||
<option ng-repeat="directory in directoryList" value="{{ directory }}" />
|
||||
</datalist>
|
||||
<p class="help-block">
|
||||
<span ng-if="folderEditor.folderPath.$valid || folderEditor.folderPath.$pristine"><span translate>Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for</span> <code>{{system.tilde}}</code>.</br></span>
|
||||
<span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty && !editingDefaults">The folder path cannot be blank.</span>
|
||||
<span translate ng-if="folderEditor.folderPath.$error.required && folderEditor.folderPath.$dirty && !editingFolderDefaults()">The folder path cannot be blank.</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length == 0">Warning, this path is a subdirectory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.isSub && folderPathErrors.otherLabel.length != 0">Warning, this path is a subdirectory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
<span ng-if="folderPathErrors.isParent && !editingDefaults">
|
||||
<span ng-if="folderPathErrors.isParent && !editingFolderDefaults()">
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" ng-if="folderPathErrors.otherLabel.length == 0">Warning, this path is a parent directory of an existing folder "{%otherFolder%}".</span>
|
||||
<span class="text-danger" translate translate-value-other-folder="{{folderPathErrors.otherID}}" translate-value-other-folder-label="{{folderPathErrors.otherLabel}}" ng-if="folderPathErrors.otherLabel.length != 0">Warning, this path is a parent directory of an existing folder "{%otherFolderLabel%}" ({%otherFolder%}).</span>
|
||||
</span>
|
||||
@ -148,33 +148,42 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="!editingDefaults" id="folder-ignores" class="tab-pane">
|
||||
<p translate>Enter ignore patterns, one per line.</p>
|
||||
<div ng-class="{'has-error': ignores.error != null}">
|
||||
<textarea class="form-control" rows="5" ng-model="ignores.text" ng-disabled="ignores.disabled"></textarea>
|
||||
<p class="help-block" ng-if="ignores.error">
|
||||
{{ignores.error}}
|
||||
</p>
|
||||
<div id="folder-ignores" class="tab-pane" ng-switch="currentFolder._editing">
|
||||
<div ng-switch-when="add">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="currentFolder._addIgnores" > <span translate>Add ignore patterns</span>
|
||||
</label>
|
||||
<p translate class="help-block">Ignore patterns can only be added after the folder is created. If checked, an input field to enter ignore patterns will be presented after saving.</p>
|
||||
</div>
|
||||
<div ng-switch-default>
|
||||
<p translate>Enter ignore patterns, one per line.</p>
|
||||
<div ng-class="{'has-error': ignores.error != null}">
|
||||
<textarea class="form-control" name="ignoresText" rows="5" ng-model="ignores.text" ng-disabled="ignores.disabled"></textarea>
|
||||
<p class="help-block" ng-if="ignores.error">
|
||||
{{ignores.error}}
|
||||
</p>
|
||||
</div>
|
||||
<hr />
|
||||
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="https://docs.syncthing.net/users/ignoring.html" target="_blank" translate>full documentation</a>):</p>
|
||||
<dl class="dl-horizontal dl-narrow small">
|
||||
<dt><code>(?d)</code></dt>
|
||||
<dd><b><span translate>Prefix indicating that the file can be deleted if preventing directory removal</span></b></dd>
|
||||
<dt><code>(?i)</code></dt>
|
||||
<dd><span translate>Prefix indicating that the pattern should be matched without case sensitivity</span></dd>
|
||||
<dt><code>!</code></dt>
|
||||
<dd><span translate>Inversion of the given condition (i.e. do not exclude)</span></dd>
|
||||
<dt><code>*</code></dt>
|
||||
<dd><span translate>Single level wildcard (matches within a directory only)</span></dd>
|
||||
<dt><code>**</code></dt>
|
||||
<dd><span translate>Multi level wildcard (matches multiple directory levels)</span></dd>
|
||||
<dt><code>//</code></dt>
|
||||
<dd><span translate>Comment, when used at the start of a line</span></dd>
|
||||
</dl>
|
||||
<div ng-if="!editingFolderDefaults()">
|
||||
<hr />
|
||||
<span translate translate-value-path="{{currentFolder.path}}{{system.pathSeparator}}.stignore">Editing {%path%}.</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="https://docs.syncthing.net/users/ignoring.html" target="_blank" translate>full documentation</a>):</p>
|
||||
<dl class="dl-horizontal dl-narrow small">
|
||||
<dt><code>(?d)</code></dt>
|
||||
<dd><b><span translate>Prefix indicating that the file can be deleted if preventing directory removal</span></b></dd>
|
||||
<dt><code>(?i)</code></dt>
|
||||
<dd><span translate>Prefix indicating that the pattern should be matched without case sensitivity</span></dd>
|
||||
<dt><code>!</code></dt>
|
||||
<dd><span translate>Inversion of the given condition (i.e. do not exclude)</span></dd>
|
||||
<dt><code>*</code></dt>
|
||||
<dd><span translate>Single level wildcard (matches within a directory only)</span></dd>
|
||||
<dt><code>**</code></dt>
|
||||
<dd><span translate>Multi level wildcard (matches multiple directory levels)</span></dd>
|
||||
<dt><code>//</code></dt>
|
||||
<dd><span translate>Comment, when used at the start of a line</span></dd>
|
||||
</dl>
|
||||
<hr />
|
||||
<span translate ng-show="editingExisting" translate-value-path="{{currentFolder.path}}{{system.pathSeparator}}.stignore">Editing {%path%}.</span>
|
||||
<span translate ng-show="!editingExisting" translate-value-path="{{currentFolder.path}}{{system.pathSeparator}}.stignore">Creating ignore patterns, overwriting an existing file at {%path%}.</span>
|
||||
</div>
|
||||
|
||||
<div id="folder-advanced" class="tab-pane">
|
||||
@ -205,17 +214,17 @@
|
||||
<div class="col-md-6 form-group">
|
||||
<label translate>Folder Type</label>
|
||||
<a href="https://docs.syncthing.net/users/foldertypes.html" target="_blank"><span class="fas fa-question-circle"></span> <span translate>Help</span></a>
|
||||
<select class="form-control" ng-change="setDefaultsForFolderType()" ng-model="currentFolder.type" ng-disabled="editingExisting && currentFolder.type == 'receiveencrypted'">
|
||||
<select class="form-control" ng-change="setDefaultsForFolderType()" ng-model="currentFolder.type" ng-disabled="editingFolderExisting() && currentFolder.type == 'receiveencrypted'">
|
||||
<option value="sendreceive" translate>Send & Receive</option>
|
||||
<option value="sendonly" translate>Send Only</option>
|
||||
<option value="receiveonly" translate>Receive Only</option>
|
||||
<option value="receiveencrypted" ng-disabled="editingExisting" translate>Receive Encrypted</option>
|
||||
<option value="receiveencrypted" ng-disabled="editingFolderExisting()" translate>Receive Encrypted</option>
|
||||
</select>
|
||||
<p ng-if="currentFolder.type == 'sendonly'" translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p>
|
||||
<p ng-if="currentFolder.type == 'receiveonly'" translate class="help-block">Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.</p>
|
||||
<p ng-if="currentFolder.type == 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type "{%receiveEncrypted%}" too.</p>
|
||||
<p ng-if="editingExisting && currentFolder.type == 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Folder type "{%receiveEncrypted%}" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.</p>
|
||||
<p ng-if="editingExisting && currentFolder.type != 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Folder type "{%receiveEncrypted%}" can only be set when adding a new folder.</p>
|
||||
<p ng-if="editingFolderExisting() && currentFolder.type == 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Folder type "{%receiveEncrypted%}" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.</p>
|
||||
<p ng-if="editingFolderExisting() && currentFolder.type != 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Folder type "{%receiveEncrypted%}" can only be set when adding a new folder.</p>
|
||||
</div>
|
||||
<div class="col-md-6 form-group">
|
||||
<label translate>File Pull Order</label>
|
||||
@ -274,7 +283,7 @@
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
|
||||
<span class="fas fa-times"></span> <span translate>Close</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingExisting && !editingDefaults">
|
||||
<button type="button" class="btn btn-warning pull-left btn-sm" data-toggle="modal" data-target="#remove-folder-confirmation" ng-if="editingFolderExisting()">
|
||||
<span class="fas fa-minus-circle"></span> <span translate>Remove</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -313,6 +313,7 @@ func (s *service) Serve(ctx context.Context) error {
|
||||
configBuilder.registerDevice("/rest/config/devices/:id")
|
||||
configBuilder.registerDefaultFolder("/rest/config/defaults/folder")
|
||||
configBuilder.registerDefaultDevice("/rest/config/defaults/device")
|
||||
configBuilder.registerDefaultIgnores("/rest/config/defaults/ignores")
|
||||
configBuilder.registerOptions("/rest/config/options")
|
||||
configBuilder.registerLDAP("/rest/config/ldap")
|
||||
configBuilder.registerGUI("/rest/config/gui")
|
||||
|
@ -229,6 +229,28 @@ func (c *configMuxBuilder) registerDefaultDevice(path string) {
|
||||
})
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) registerDefaultIgnores(path string) {
|
||||
c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
|
||||
sendJSON(w, c.cfg.DefaultIgnores())
|
||||
})
|
||||
|
||||
c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
|
||||
var ignores config.Ignores
|
||||
if err := unmarshalTo(r.Body, &ignores); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
cfg.Defaults.Ignores = ignores
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
c.finish(w, waiter)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) registerOptions(path string) {
|
||||
c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
|
||||
sendJSON(w, c.cfg.Options())
|
||||
|
@ -621,3 +621,9 @@ func ensureZeroForNodefault(empty interface{}, target interface{}) {
|
||||
return len(v) > 0
|
||||
})
|
||||
}
|
||||
|
||||
func (i Ignores) Copy() Ignores {
|
||||
out := Ignores{Lines: make([]string, len(i.Lines))}
|
||||
copy(out.Lines, i.Lines)
|
||||
return out
|
||||
}
|
||||
|
@ -69,8 +69,9 @@ func (m *Configuration) XXX_DiscardUnknown() {
|
||||
var xxx_messageInfo_Configuration proto.InternalMessageInfo
|
||||
|
||||
type Defaults struct {
|
||||
Folder FolderConfiguration `protobuf:"bytes,1,opt,name=folder,proto3" json:"folder" xml:"folder"`
|
||||
Device DeviceConfiguration `protobuf:"bytes,2,opt,name=device,proto3" json:"device" xml:"device"`
|
||||
Folder FolderConfiguration `protobuf:"bytes,1,opt,name=folder,proto3" json:"folder" xml:"folder"`
|
||||
Device DeviceConfiguration `protobuf:"bytes,2,opt,name=device,proto3" json:"device" xml:"device"`
|
||||
Ignores Ignores `protobuf:"bytes,3,opt,name=ignores,proto3" json:"ignores" xml:"ignores"`
|
||||
}
|
||||
|
||||
func (m *Defaults) Reset() { *m = Defaults{} }
|
||||
@ -106,56 +107,98 @@ func (m *Defaults) XXX_DiscardUnknown() {
|
||||
|
||||
var xxx_messageInfo_Defaults proto.InternalMessageInfo
|
||||
|
||||
type Ignores struct {
|
||||
Lines []string `protobuf:"bytes,1,rep,name=lines,proto3" json:"lines" xml:"line"`
|
||||
}
|
||||
|
||||
func (m *Ignores) Reset() { *m = Ignores{} }
|
||||
func (m *Ignores) String() string { return proto.CompactTextString(m) }
|
||||
func (*Ignores) ProtoMessage() {}
|
||||
func (*Ignores) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_baadf209193dc627, []int{2}
|
||||
}
|
||||
func (m *Ignores) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *Ignores) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_Ignores.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (m *Ignores) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Ignores.Merge(m, src)
|
||||
}
|
||||
func (m *Ignores) XXX_Size() int {
|
||||
return m.ProtoSize()
|
||||
}
|
||||
func (m *Ignores) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Ignores.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Ignores proto.InternalMessageInfo
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Configuration)(nil), "config.Configuration")
|
||||
proto.RegisterType((*Defaults)(nil), "config.Defaults")
|
||||
proto.RegisterType((*Ignores)(nil), "config.Ignores")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("lib/config/config.proto", fileDescriptor_baadf209193dc627) }
|
||||
|
||||
var fileDescriptor_baadf209193dc627 = []byte{
|
||||
// 654 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0xcd, 0x6e, 0xd3, 0x40,
|
||||
0x10, 0xc7, 0xed, 0xa6, 0x4d, 0xda, 0xed, 0x17, 0x32, 0x08, 0x5c, 0x3e, 0xbc, 0x61, 0x15, 0x50,
|
||||
0x41, 0xa5, 0x95, 0xca, 0x05, 0x71, 0x23, 0x44, 0x94, 0x0a, 0x24, 0x2a, 0xa3, 0x22, 0xe0, 0x82,
|
||||
0x92, 0x78, 0xeb, 0xae, 0x94, 0xd8, 0x96, 0xbd, 0xae, 0xda, 0x47, 0xe0, 0x86, 0x78, 0x02, 0x4e,
|
||||
0x48, 0xdc, 0x79, 0x88, 0xdc, 0x92, 0x23, 0xa7, 0x95, 0x9a, 0xdc, 0x7c, 0xf4, 0x91, 0x13, 0xda,
|
||||
0x0f, 0xbb, 0xb6, 0x6a, 0xe0, 0x64, 0xcf, 0xfc, 0xff, 0xf3, 0x9b, 0xd5, 0x78, 0xc7, 0xe0, 0xc6,
|
||||
0x80, 0xf4, 0x76, 0xfa, 0xbe, 0x77, 0x44, 0x5c, 0xf5, 0xd8, 0x0e, 0x42, 0x9f, 0xfa, 0x46, 0x5d,
|
||||
0x46, 0x37, 0x5b, 0x05, 0xc3, 0x91, 0x3f, 0x70, 0x70, 0x28, 0x83, 0x38, 0xec, 0x52, 0xe2, 0x7b,
|
||||
0xd2, 0x5d, 0x72, 0x39, 0xf8, 0x84, 0xf4, 0x71, 0x95, 0xeb, 0x6e, 0xc1, 0xe5, 0xc6, 0xa4, 0xca,
|
||||
0x82, 0x0a, 0x96, 0x81, 0xd3, 0x0d, 0xaa, 0x3c, 0xf7, 0x0a, 0x1e, 0x3f, 0xe0, 0x42, 0x54, 0x65,
|
||||
0xdb, 0x28, 0xda, 0x7a, 0x11, 0x0e, 0x4f, 0xb0, 0xa3, 0xa4, 0x25, 0x7c, 0x4a, 0xe5, 0x2b, 0xfa,
|
||||
0xde, 0x00, 0xab, 0xcf, 0x8b, 0xd5, 0x86, 0x0d, 0x1a, 0x27, 0x38, 0x8c, 0x88, 0xef, 0x99, 0x7a,
|
||||
0x53, 0xdf, 0x5c, 0x68, 0x3f, 0x49, 0x18, 0xcc, 0x52, 0x29, 0x83, 0xc6, 0xe9, 0x70, 0xf0, 0x14,
|
||||
0xa9, 0x78, 0xab, 0x4b, 0x69, 0x88, 0x7e, 0x33, 0x58, 0x23, 0x1e, 0x4d, 0xc6, 0xad, 0x95, 0x62,
|
||||
0xde, 0xce, 0xaa, 0x8c, 0x77, 0xa0, 0x21, 0x87, 0x17, 0x99, 0x73, 0xcd, 0xda, 0xe6, 0xf2, 0xee,
|
||||
0xad, 0x6d, 0x35, 0xed, 0x17, 0x22, 0x5d, 0x3a, 0x41, 0x1b, 0x8e, 0x18, 0xd4, 0x78, 0x53, 0x55,
|
||||
0x93, 0x32, 0xb8, 0x22, 0x9a, 0xca, 0x18, 0xd9, 0x99, 0xc0, 0xb9, 0x72, 0xdc, 0x91, 0x59, 0x2b,
|
||||
0x73, 0x3b, 0x22, 0xfd, 0x17, 0xae, 0xaa, 0xc9, 0xb9, 0x32, 0x46, 0x76, 0x26, 0x18, 0x36, 0xa8,
|
||||
0xb9, 0x31, 0x31, 0xe7, 0x9b, 0xfa, 0xe6, 0xf2, 0xae, 0x99, 0x31, 0xf7, 0x0e, 0xf7, 0xcb, 0xc0,
|
||||
0xfb, 0x1c, 0x38, 0x65, 0xb0, 0xb6, 0x77, 0xb8, 0x9f, 0x30, 0xc8, 0x6b, 0x52, 0x06, 0x97, 0x04,
|
||||
0xd3, 0x8d, 0x09, 0xfa, 0x3a, 0x69, 0x71, 0xc9, 0xe6, 0x82, 0xf1, 0x01, 0xcc, 0xf3, 0x2f, 0x6a,
|
||||
0x2e, 0x08, 0xe8, 0x46, 0x06, 0x7d, 0xdd, 0x79, 0x76, 0x50, 0xa6, 0x3e, 0x54, 0xd4, 0x79, 0x2e,
|
||||
0x25, 0x0c, 0x8a, 0xb2, 0x94, 0x41, 0x20, 0xb8, 0x3c, 0xe0, 0x60, 0xa1, 0xda, 0x42, 0x33, 0xde,
|
||||
0x83, 0x86, 0xba, 0x08, 0x66, 0x5d, 0xd0, 0x6f, 0x67, 0xf4, 0x37, 0x32, 0x5d, 0x6e, 0xd0, 0xcc,
|
||||
0xe6, 0xa0, 0x8a, 0x52, 0x06, 0x57, 0x05, 0x5b, 0xc5, 0xc8, 0xce, 0x14, 0xe3, 0x87, 0x0e, 0xd6,
|
||||
0x89, 0xeb, 0xf9, 0x21, 0x76, 0x3e, 0x65, 0x93, 0x6e, 0x88, 0x49, 0x5f, 0xcf, 0x5b, 0xa8, 0xbb,
|
||||
0x25, 0x27, 0xde, 0x3e, 0x56, 0xf0, 0x6b, 0x21, 0x1e, 0xfa, 0x14, 0xef, 0xcb, 0xe2, 0x4e, 0x3e,
|
||||
0xf1, 0x0d, 0xd1, 0xa9, 0x42, 0x44, 0xc9, 0xb8, 0x75, 0xb5, 0x22, 0x9f, 0x8e, 0x5b, 0x95, 0x2c,
|
||||
0x7b, 0x8d, 0x94, 0x62, 0xe3, 0xb3, 0x0e, 0xd6, 0x03, 0xec, 0x39, 0xc4, 0x73, 0xf3, 0xb3, 0x2e,
|
||||
0xfe, 0xf3, 0xac, 0x2f, 0xd5, 0xa4, 0xcd, 0x0e, 0x0e, 0x42, 0xdc, 0xef, 0x52, 0xec, 0x1c, 0x48,
|
||||
0x80, 0x62, 0x26, 0x0c, 0xea, 0x8f, 0x52, 0x06, 0xef, 0x88, 0x43, 0x07, 0x45, 0x6d, 0xcb, 0x1f,
|
||||
0x12, 0x8a, 0x87, 0x01, 0x3d, 0x43, 0xa6, 0x6e, 0xaf, 0x95, 0xb4, 0xc8, 0x38, 0x00, 0x8b, 0x0e,
|
||||
0x3e, 0xea, 0xc6, 0x03, 0x1a, 0x99, 0x4b, 0xe2, 0x93, 0x5c, 0xb9, 0xb8, 0x99, 0x32, 0xdf, 0x46,
|
||||
0x6a, 0x52, 0xb9, 0x33, 0x65, 0x70, 0x4d, 0xdd, 0x47, 0x99, 0x40, 0x76, 0xae, 0xa1, 0x9f, 0x3a,
|
||||
0x58, 0xcc, 0x4a, 0x8d, 0xb7, 0xa0, 0x2e, 0x57, 0x40, 0xac, 0xe8, 0x7f, 0xd6, 0xc9, 0x52, 0x7d,
|
||||
0x54, 0xc9, 0xa5, 0x6d, 0x52, 0x79, 0x0e, 0x95, 0x63, 0x33, 0xe7, 0xca, 0xd0, 0xaa, 0x5d, 0xca,
|
||||
0xa1, 0xb2, 0xe4, 0xd2, 0x2a, 0xa9, 0x7c, 0xfb, 0xd5, 0xe8, 0xdc, 0xd2, 0x26, 0xe7, 0x96, 0x36,
|
||||
0x9a, 0x5a, 0xfa, 0x64, 0x6a, 0xe9, 0x5f, 0x66, 0x96, 0xf6, 0x6d, 0x66, 0xe9, 0x93, 0x99, 0xa5,
|
||||
0xfd, 0x9a, 0x59, 0xda, 0xc7, 0x07, 0x2e, 0xa1, 0xc7, 0x71, 0x6f, 0xbb, 0xef, 0x0f, 0x77, 0xa2,
|
||||
0x33, 0xaf, 0x4f, 0x8f, 0x89, 0xe7, 0x16, 0xde, 0x2e, 0x7e, 0x63, 0xbd, 0xba, 0xf8, 0x67, 0x3d,
|
||||
0xfe, 0x13, 0x00, 0x00, 0xff, 0xff, 0x50, 0xe9, 0xd5, 0x50, 0xb6, 0x05, 0x00, 0x00,
|
||||
// 709 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0x4b, 0x6f, 0xd3, 0x40,
|
||||
0x10, 0xc7, 0xe3, 0xa6, 0x8d, 0x9b, 0xed, 0x0b, 0x19, 0x44, 0x5d, 0x1e, 0xde, 0xb0, 0x0a, 0x28,
|
||||
0xa0, 0x3e, 0xa4, 0x72, 0xa9, 0xb8, 0x11, 0x22, 0x4a, 0x55, 0x24, 0x2a, 0xa3, 0x22, 0xe0, 0x82,
|
||||
0x92, 0x78, 0xeb, 0xae, 0x94, 0xd8, 0x96, 0xed, 0x54, 0xed, 0x91, 0x23, 0x37, 0xc4, 0x27, 0xe0,
|
||||
0x84, 0xc4, 0x37, 0xe9, 0xad, 0x39, 0x72, 0x5a, 0xa9, 0xcd, 0xcd, 0x47, 0x1f, 0x39, 0xa1, 0x7d,
|
||||
0x39, 0xb6, 0x6a, 0xe0, 0x94, 0xcc, 0xfc, 0xff, 0xf3, 0xdb, 0xd5, 0xec, 0x8c, 0xc1, 0xea, 0x80,
|
||||
0xf4, 0xb6, 0xfa, 0xbe, 0x77, 0x44, 0x5c, 0xf9, 0xb3, 0x19, 0x84, 0x7e, 0xec, 0x1b, 0x35, 0x11,
|
||||
0xdd, 0x69, 0xe6, 0x0c, 0x47, 0xfe, 0xc0, 0xc1, 0xa1, 0x08, 0x46, 0x61, 0x37, 0x26, 0xbe, 0x27,
|
||||
0xdc, 0x05, 0x97, 0x83, 0x4f, 0x48, 0x1f, 0x97, 0xb9, 0x1e, 0xe4, 0x5c, 0xee, 0x88, 0x94, 0x59,
|
||||
0x50, 0xce, 0x32, 0x70, 0xba, 0x41, 0x99, 0xe7, 0x61, 0xce, 0xe3, 0x07, 0x4c, 0x88, 0xca, 0x6c,
|
||||
0x6b, 0x79, 0x5b, 0x2f, 0xc2, 0xe1, 0x09, 0x76, 0xa4, 0x54, 0xc7, 0xa7, 0xb1, 0xf8, 0x8b, 0x7e,
|
||||
0xe8, 0x60, 0xe9, 0x45, 0xbe, 0xda, 0xb0, 0x81, 0x7e, 0x82, 0xc3, 0x88, 0xf8, 0x9e, 0xa9, 0x35,
|
||||
0xb4, 0xd6, 0x5c, 0x7b, 0x27, 0xa1, 0x50, 0xa5, 0x52, 0x0a, 0x8d, 0xd3, 0xe1, 0xe0, 0x19, 0x92,
|
||||
0xf1, 0x7a, 0x37, 0x8e, 0x43, 0xf4, 0x9b, 0xc2, 0x2a, 0xf1, 0xe2, 0xe4, 0xa2, 0xb9, 0x98, 0xcf,
|
||||
0xdb, 0xaa, 0xca, 0x78, 0x07, 0x74, 0xd1, 0xbc, 0xc8, 0x9c, 0x69, 0x54, 0x5b, 0x0b, 0xdb, 0x77,
|
||||
0x37, 0x65, 0xb7, 0x5f, 0xf2, 0x74, 0xe1, 0x06, 0x6d, 0x78, 0x4e, 0x61, 0x85, 0x1d, 0x2a, 0x6b,
|
||||
0x52, 0x0a, 0x17, 0xf9, 0xa1, 0x22, 0x46, 0xb6, 0x12, 0x18, 0x57, 0xb4, 0x3b, 0x32, 0xab, 0x45,
|
||||
0x6e, 0x87, 0xa7, 0xff, 0xc2, 0x95, 0x35, 0x19, 0x57, 0xc4, 0xc8, 0x56, 0x82, 0x61, 0x83, 0xaa,
|
||||
0x3b, 0x22, 0xe6, 0x6c, 0x43, 0x6b, 0x2d, 0x6c, 0x9b, 0x8a, 0xb9, 0x7b, 0xb8, 0x57, 0x04, 0x3e,
|
||||
0x62, 0xc0, 0x2b, 0x0a, 0xab, 0xbb, 0x87, 0x7b, 0x09, 0x85, 0xac, 0x26, 0xa5, 0xb0, 0xce, 0x99,
|
||||
0xee, 0x88, 0xa0, 0x6f, 0xe3, 0x26, 0x93, 0x6c, 0x26, 0x18, 0x1f, 0xc0, 0x2c, 0x7b, 0x51, 0x73,
|
||||
0x8e, 0x43, 0xd7, 0x14, 0xf4, 0x75, 0xe7, 0xf9, 0x41, 0x91, 0xfa, 0x44, 0x52, 0x67, 0x99, 0x94,
|
||||
0x50, 0xc8, 0xcb, 0x52, 0x0a, 0x01, 0xe7, 0xb2, 0x80, 0x81, 0xb9, 0x6a, 0x73, 0xcd, 0x78, 0x0f,
|
||||
0x74, 0x39, 0x08, 0x66, 0x8d, 0xd3, 0xef, 0x29, 0xfa, 0x1b, 0x91, 0x2e, 0x1e, 0xd0, 0x50, 0x7d,
|
||||
0x90, 0x45, 0x29, 0x85, 0x4b, 0x9c, 0x2d, 0x63, 0x64, 0x2b, 0xc5, 0xf8, 0xa9, 0x81, 0x15, 0xe2,
|
||||
0x7a, 0x7e, 0x88, 0x9d, 0x4f, 0xaa, 0xd3, 0x3a, 0xef, 0xf4, 0xed, 0xec, 0x08, 0x39, 0x5b, 0xa2,
|
||||
0xe3, 0xed, 0x63, 0x09, 0xbf, 0x15, 0xe2, 0xa1, 0x1f, 0xe3, 0x3d, 0x51, 0xdc, 0xc9, 0x3a, 0xbe,
|
||||
0xc6, 0x4f, 0x2a, 0x11, 0x51, 0x72, 0xd1, 0xbc, 0x59, 0x92, 0x4f, 0x2f, 0x9a, 0xa5, 0x2c, 0x7b,
|
||||
0x99, 0x14, 0x62, 0xe3, 0x8b, 0x06, 0x56, 0x02, 0xec, 0x39, 0xc4, 0x73, 0xb3, 0xbb, 0xce, 0xff,
|
||||
0xf3, 0xae, 0xaf, 0x64, 0xa7, 0xcd, 0x0e, 0x0e, 0x42, 0xdc, 0xef, 0xc6, 0xd8, 0x39, 0x10, 0x00,
|
||||
0xc9, 0x4c, 0x28, 0xd4, 0x36, 0x52, 0x0a, 0xef, 0xf3, 0x4b, 0x07, 0x79, 0x6d, 0xdd, 0x1f, 0x92,
|
||||
0x18, 0x0f, 0x83, 0xf8, 0x0c, 0x99, 0x9a, 0xbd, 0x5c, 0xd0, 0x22, 0xe3, 0x00, 0xcc, 0x3b, 0xf8,
|
||||
0xa8, 0x3b, 0x1a, 0xc4, 0x91, 0x59, 0xe7, 0x4f, 0x72, 0x63, 0x3a, 0x99, 0x22, 0xdf, 0x46, 0xb2,
|
||||
0x53, 0x99, 0x33, 0xa5, 0x70, 0x59, 0xce, 0xa3, 0x48, 0x20, 0x3b, 0xd3, 0xd0, 0xe7, 0x19, 0x30,
|
||||
0xaf, 0x4a, 0x8d, 0xb7, 0xa0, 0x26, 0x56, 0x80, 0xaf, 0xe8, 0x7f, 0xd6, 0xc9, 0x92, 0xe7, 0xc8,
|
||||
0x92, 0x6b, 0xdb, 0x24, 0xf3, 0x0c, 0x2a, 0xda, 0x66, 0xce, 0x14, 0xa1, 0x65, 0xbb, 0x94, 0x41,
|
||||
0x45, 0xc9, 0xb5, 0x55, 0x92, 0x79, 0x63, 0x1f, 0xe8, 0xe2, 0x99, 0xd8, 0x86, 0x32, 0xea, 0x8a,
|
||||
0xa2, 0x8a, 0xd7, 0x8c, 0xa6, 0xd3, 0x28, 0x7d, 0xd9, 0x34, 0xca, 0x18, 0xd9, 0x4a, 0x41, 0x3b,
|
||||
0x40, 0x97, 0x55, 0xc6, 0x06, 0x98, 0x1b, 0x10, 0x0f, 0x47, 0xa6, 0xd6, 0xa8, 0xb6, 0xea, 0xed,
|
||||
0xd5, 0x84, 0x42, 0x91, 0x98, 0x2e, 0x0a, 0xf1, 0x30, 0xb2, 0x45, 0xb2, 0xbd, 0x7f, 0x7e, 0x69,
|
||||
0x55, 0xc6, 0x97, 0x56, 0xe5, 0xfc, 0xca, 0xd2, 0xc6, 0x57, 0x96, 0xf6, 0x75, 0x62, 0x55, 0xbe,
|
||||
0x4f, 0x2c, 0x6d, 0x3c, 0xb1, 0x2a, 0xbf, 0x26, 0x56, 0xe5, 0xe3, 0x63, 0x97, 0xc4, 0xc7, 0xa3,
|
||||
0xde, 0x66, 0xdf, 0x1f, 0x6e, 0x45, 0x67, 0x5e, 0x3f, 0x3e, 0x26, 0x9e, 0x9b, 0xfb, 0x37, 0xfd,
|
||||
0x9a, 0xf6, 0x6a, 0xfc, 0xd3, 0xf9, 0xf4, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x77, 0x7d, 0xcb,
|
||||
0x2a, 0x3d, 0x06, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *Configuration) Marshal() (dAtA []byte, err error) {
|
||||
@ -302,6 +345,16 @@ func (m *Defaults) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
{
|
||||
size, err := m.Ignores.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintConfig(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x1a
|
||||
{
|
||||
size, err := m.Device.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
@ -325,6 +378,38 @@ func (m *Defaults) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *Ignores) Marshal() (dAtA []byte, err error) {
|
||||
size := m.ProtoSize()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *Ignores) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.ProtoSize()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *Ignores) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Lines) > 0 {
|
||||
for iNdEx := len(m.Lines) - 1; iNdEx >= 0; iNdEx-- {
|
||||
i -= len(m.Lines[iNdEx])
|
||||
copy(dAtA[i:], m.Lines[iNdEx])
|
||||
i = encodeVarintConfig(dAtA, i, uint64(len(m.Lines[iNdEx])))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func encodeVarintConfig(dAtA []byte, offset int, v uint64) int {
|
||||
offset -= sovConfig(v)
|
||||
base := offset
|
||||
@ -390,6 +475,23 @@ func (m *Defaults) ProtoSize() (n int) {
|
||||
n += 1 + l + sovConfig(uint64(l))
|
||||
l = m.Device.ProtoSize()
|
||||
n += 1 + l + sovConfig(uint64(l))
|
||||
l = m.Ignores.ProtoSize()
|
||||
n += 1 + l + sovConfig(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *Ignores) ProtoSize() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Lines) > 0 {
|
||||
for _, s := range m.Lines {
|
||||
l = len(s)
|
||||
n += 1 + l + sovConfig(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@ -831,6 +933,124 @@ func (m *Defaults) Unmarshal(dAtA []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Ignores", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowConfig
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.Ignores.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipConfig(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
if (iNdEx + skippy) < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *Ignores) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowConfig
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: Ignores: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: Ignores: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Lines", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowConfig
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthConfig
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Lines = append(m.Lines, string(dAtA[iNdEx:postIndex]))
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipConfig(dAtA[iNdEx:])
|
||||
|
@ -113,6 +113,9 @@ func TestDefaultValues(t *testing.T) {
|
||||
Compression: protocol.CompressionMetadata,
|
||||
IgnoredFolders: []ObservedFolder{},
|
||||
},
|
||||
Ignores: Ignores{
|
||||
Lines: []string{},
|
||||
},
|
||||
},
|
||||
IgnoredDevices: []ObservedDevice{},
|
||||
}
|
||||
|
@ -40,6 +40,16 @@ type Wrapper struct {
|
||||
defaultFolderReturnsOnCall map[int]struct {
|
||||
result1 config.FolderConfiguration
|
||||
}
|
||||
DefaultIgnoresStub func() config.Ignores
|
||||
defaultIgnoresMutex sync.RWMutex
|
||||
defaultIgnoresArgsForCall []struct {
|
||||
}
|
||||
defaultIgnoresReturns struct {
|
||||
result1 config.Ignores
|
||||
}
|
||||
defaultIgnoresReturnsOnCall map[int]struct {
|
||||
result1 config.Ignores
|
||||
}
|
||||
DeviceStub func(protocol.DeviceID) (config.DeviceConfiguration, bool)
|
||||
deviceMutex sync.RWMutex
|
||||
deviceArgsForCall []struct {
|
||||
@ -449,6 +459,59 @@ func (fake *Wrapper) DefaultFolderReturnsOnCall(i int, result1 config.FolderConf
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *Wrapper) DefaultIgnores() config.Ignores {
|
||||
fake.defaultIgnoresMutex.Lock()
|
||||
ret, specificReturn := fake.defaultIgnoresReturnsOnCall[len(fake.defaultIgnoresArgsForCall)]
|
||||
fake.defaultIgnoresArgsForCall = append(fake.defaultIgnoresArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.DefaultIgnoresStub
|
||||
fakeReturns := fake.defaultIgnoresReturns
|
||||
fake.recordInvocation("DefaultIgnores", []interface{}{})
|
||||
fake.defaultIgnoresMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *Wrapper) DefaultIgnoresCallCount() int {
|
||||
fake.defaultIgnoresMutex.RLock()
|
||||
defer fake.defaultIgnoresMutex.RUnlock()
|
||||
return len(fake.defaultIgnoresArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *Wrapper) DefaultIgnoresCalls(stub func() config.Ignores) {
|
||||
fake.defaultIgnoresMutex.Lock()
|
||||
defer fake.defaultIgnoresMutex.Unlock()
|
||||
fake.DefaultIgnoresStub = stub
|
||||
}
|
||||
|
||||
func (fake *Wrapper) DefaultIgnoresReturns(result1 config.Ignores) {
|
||||
fake.defaultIgnoresMutex.Lock()
|
||||
defer fake.defaultIgnoresMutex.Unlock()
|
||||
fake.DefaultIgnoresStub = nil
|
||||
fake.defaultIgnoresReturns = struct {
|
||||
result1 config.Ignores
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *Wrapper) DefaultIgnoresReturnsOnCall(i int, result1 config.Ignores) {
|
||||
fake.defaultIgnoresMutex.Lock()
|
||||
defer fake.defaultIgnoresMutex.Unlock()
|
||||
fake.DefaultIgnoresStub = nil
|
||||
if fake.defaultIgnoresReturnsOnCall == nil {
|
||||
fake.defaultIgnoresReturnsOnCall = make(map[int]struct {
|
||||
result1 config.Ignores
|
||||
})
|
||||
}
|
||||
fake.defaultIgnoresReturnsOnCall[i] = struct {
|
||||
result1 config.Ignores
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *Wrapper) Device(arg1 protocol.DeviceID) (config.DeviceConfiguration, bool) {
|
||||
fake.deviceMutex.Lock()
|
||||
ret, specificReturn := fake.deviceReturnsOnCall[len(fake.deviceArgsForCall)]
|
||||
@ -1752,6 +1815,8 @@ func (fake *Wrapper) Invocations() map[string][][]interface{} {
|
||||
defer fake.defaultDeviceMutex.RUnlock()
|
||||
fake.defaultFolderMutex.RLock()
|
||||
defer fake.defaultFolderMutex.RUnlock()
|
||||
fake.defaultIgnoresMutex.RLock()
|
||||
defer fake.defaultIgnoresMutex.RUnlock()
|
||||
fake.deviceMutex.RLock()
|
||||
defer fake.deviceMutex.RUnlock()
|
||||
fake.deviceListMutex.RLock()
|
||||
|
@ -100,6 +100,7 @@ type Wrapper interface {
|
||||
GUI() GUIConfiguration
|
||||
LDAP() LDAPConfiguration
|
||||
Options() OptionsConfiguration
|
||||
DefaultIgnores() Ignores
|
||||
|
||||
Folder(id string) (FolderConfiguration, bool)
|
||||
Folders() map[string]FolderConfiguration
|
||||
@ -437,6 +438,13 @@ func (w *wrapper) GUI() GUIConfiguration {
|
||||
return w.cfg.GUI.Copy()
|
||||
}
|
||||
|
||||
// DefaultIgnores returns the list of ignore patterns to be used by default on folders.
|
||||
func (w *wrapper) DefaultIgnores() Ignores {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.cfg.Defaults.Ignores.Copy()
|
||||
}
|
||||
|
||||
// IgnoredDevice returns whether or not connection attempts from the given
|
||||
// device should be silently ignored.
|
||||
func (w *wrapper) IgnoredDevice(id protocol.DeviceID) bool {
|
||||
|
@ -1651,6 +1651,11 @@ func (m *model) handleAutoAccepts(deviceID protocol.DeviceID, folder protocol.Fo
|
||||
|
||||
if len(ccDeviceInfos.remote.EncryptionPasswordToken) > 0 || len(ccDeviceInfos.local.EncryptionPasswordToken) > 0 {
|
||||
fcfg.Type = config.FolderTypeReceiveEncrypted
|
||||
} else {
|
||||
ignores := m.cfg.DefaultIgnores()
|
||||
if err := m.setIgnores(fcfg, ignores.Lines); err != nil {
|
||||
l.Warnf("Failed to apply default ignores to auto-accepted folder %s at path %s: %v", folder.Description(), fcfg.Path, err)
|
||||
}
|
||||
}
|
||||
|
||||
l.Infof("Auto-accepted %s folder %s at path %s", deviceID, folder.Description(), fcfg.Path)
|
||||
@ -2035,11 +2040,6 @@ func (m *model) LoadIgnores(folder string) ([]string, []string, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// On creation a new folder with ignore patterns validly has no marker yet.
|
||||
if err := cfg.CheckPath(); err != nil && err != config.ErrMarkerMissing {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !ignoresOk {
|
||||
ignores = ignore.New(cfg.Filesystem())
|
||||
}
|
||||
@ -2081,7 +2081,10 @@ func (m *model) SetIgnores(folder string, content []string) error {
|
||||
if !ok {
|
||||
return fmt.Errorf("folder %s does not exist", cfg.Description())
|
||||
}
|
||||
return m.setIgnores(cfg, content)
|
||||
}
|
||||
|
||||
func (m *model) setIgnores(cfg config.FolderConfiguration, content []string) error {
|
||||
err := cfg.CheckPath()
|
||||
if err == config.ErrPathMissing {
|
||||
if err = cfg.CreateRoot(); err != nil {
|
||||
@ -2099,7 +2102,7 @@ func (m *model) SetIgnores(folder string, content []string) error {
|
||||
}
|
||||
|
||||
m.fmut.RLock()
|
||||
runner, ok := m.folderRunners[folder]
|
||||
runner, ok := m.folderRunners[cfg.ID]
|
||||
m.fmut.RUnlock()
|
||||
if ok {
|
||||
runner.ScheduleScan()
|
||||
|
@ -1515,7 +1515,7 @@ func TestIgnores(t *testing.T) {
|
||||
t.Error("No error")
|
||||
}
|
||||
|
||||
// Invalid path, marker should be missing, hence returns an error.
|
||||
// Invalid path, treated like no patterns at all.
|
||||
fcfg := config.FolderConfiguration{ID: "fresh", Path: "XXX"}
|
||||
ignores := ignore.New(fcfg.Filesystem(), ignore.WithCache(m.cfg.Options().CacheIgnoredFiles))
|
||||
m.fmut.Lock()
|
||||
@ -1524,8 +1524,8 @@ func TestIgnores(t *testing.T) {
|
||||
m.fmut.Unlock()
|
||||
|
||||
_, _, err = m.LoadIgnores("fresh")
|
||||
if err == nil {
|
||||
t.Error("No error")
|
||||
if err != nil {
|
||||
t.Error("Got error for inexistent folder path")
|
||||
}
|
||||
|
||||
// Repeat tests with paused folder
|
||||
|
@ -24,6 +24,11 @@ message Configuration {
|
||||
}
|
||||
|
||||
message Defaults {
|
||||
FolderConfiguration folder = 1;
|
||||
DeviceConfiguration device = 2;
|
||||
FolderConfiguration folder = 1;
|
||||
DeviceConfiguration device = 2;
|
||||
Ignores ignores = 3;
|
||||
}
|
||||
|
||||
message Ignores {
|
||||
repeated string lines = 1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user