feat(gui, config): support simple folder grouping (fixes #2070) (#10563)

Adds a very simple way to group up folders to help with organising from
the GUI. 

Signed-off-by: Ben Norcombe <bennorcombe@pm.me>
This commit is contained in:
Ben Norcombe
2026-04-07 17:05:41 +02:00
committed by GitHub
parent 5d877f65f5
commit 6a26d56ad9
6 changed files with 517 additions and 460 deletions
@@ -81,12 +81,14 @@ angular.module('syncthing.core')
$scope.model = {};
$scope.myID = '';
$scope.devices = {};
$scope.devicesGrouped = {};
$scope.discoveryCache = {};
$scope.protocolChanged = false;
$scope.reportData = {};
$scope.reportDataPreview = '';
$scope.reportPreview = false;
$scope.folders = {};
$scope.foldersGrouped = {};
$scope.seenError = '';
$scope.upgradeInfo = null;
$scope.deviceStats = {};
@@ -559,21 +561,44 @@ angular.module('syncthing.core')
$scope.config.options._urAcceptedStr = "" + $scope.config.options.urAccepted;
$scope.devices = deviceMap($scope.config.devices);
$scope.devicesGrouped = {};
for (var id in $scope.devices) {
$scope.completion[id] = {
_total: 100,
_needBytes: 0,
_needItems: 0
};
if ($scope.devicesGrouped[$scope.devices[id].group] === undefined) {
$scope.devicesGrouped[$scope.devices[id].group] = [];
}
$scope.devicesGrouped[$scope.devices[id].group].push($scope.devices[id]);
};
$scope.folders = folderMap($scope.config.folders);
$scope.foldersGrouped = {};
Object.keys($scope.folders).forEach(function (folder) {
refreshFolder(folder);
$scope.folders[folder].devices.forEach(function (deviceCfg) {
refreshCompletion(deviceCfg.deviceID, folder);
});
if ($scope.foldersGrouped[$scope.folders[folder].group] === undefined) {
$scope.foldersGrouped[$scope.folders[folder].group] = [];
}
$scope.foldersGrouped[$scope.folders[folder].group].push($scope.folders[folder]);
});
// Sort with blank group first if any then alphabetically
const blankSort = (a, b) => {
if (a[0] === "" && b[0] !== "") return -1;
if (b[0] === "" && a[0] !== "") return 1;
return a[0].localeCompare(b[0]);
};
$scope.foldersGrouped = Object.fromEntries(Object.entries($scope.foldersGrouped).sort(blankSort));
$scope.devicesGrouped = Object.fromEntries(Object.entries($scope.devicesGrouped).sort(blankSort));
refreshNoAuthWarning();
setDefaultTheme();
@@ -57,6 +57,16 @@
<p translate ng-if="currentDevice.deviceID == myID" class="help-block">Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.</p>
<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 class="form-group" ng-class="{'has-error': deviceEditor.deviceGroup.$invalid && deviceEditor.deviceGroup.$dirty && !editingDeviceDefaults()}">
<label for="deviceGroup"><span translate>Device Group</span></label>
<input name="deviceGroup" id="deviceGroup" class="form-control" type="text" ng-model="currentDevice.group" value="{{currentDevice.group}}" list="device-group-list"/>
<datalist id="device-group-list">
<option ng-repeat="(group, device) in devicesGrouped" value="{{group}}" />
</datalist>
<p class="help-block">
<span translate ng-if="deviceEditor.deviceGroup.$valid || deviceEditor.deviceGroup.$pristine">Optional group for the device. Can be different on each device.</span>
</p>
</div>
</div>
<div ng-if="!editingDeviceDefaults()" id="device-sharing" class="tab-pane">
<div class="row">
@@ -18,6 +18,16 @@
<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 class="form-group" ng-class="{'has-error': folderEditor.folderGroup.$invalid && folderEditor.folderGroup.$dirty && !editingFolderDefaults()}">
<label for="folderGroup"><span translate>Folder Group</span></label>
<input name="folderGroup" id="folderGroup" class="form-control" type="text" ng-model="currentFolder.group" value="{{currentFolder.group}}" list="folder-group-list"/>
<datalist id="folder-group-list">
<option ng-repeat="(group, folders) in foldersGrouped" value="{{group}}" />
</datalist>
<p class="help-block">
<span translate ng-if="folderEditor.folderGroup.$valid || folderEditor.folderGroup.$pristine">Optional group for the folder. Can be different on each device.</span>
</p>
</div>
<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="has(['existing', 'new-pending'], currentFolder._editing)" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required="" aria-required="true" unique-folder value="{{currentFolder.id}}" />