Repository: syncthing/syncthing (language: go)

Issue:
Web GUI loses config changes when doing multiple modifications (e.g. on slow hardware or remotely)
I assume this problem has affected the Web GUI since basically forever, or at least I myself have always had this issue when operating it. Basically, if you do multiple modifications quickly where each of them is supposed to save changes to the config, some of them will likely be lost, especially when dealing with slow hardware or slow connections (e.g. when operating the GUI remotely).

To reproduce (on slow hardware or a remote connection):

1. Edit multiple folders quickly one after another (e.g. by changing their folder type to something else).
2. Wait for the GUI to become responsive again.
3. It is very likely that only some of the folders will have their type changed.
4. The rest of the modifications have been lost and need to be re-done.

I think a solution to this could be to simply force the GUI not to accept any input before actually saving the previous modifications to the config. One method to achieve this may be to show a "saving changes" pop-up or similar so that the user would know that something is happening while also being prevented from operating the GUI. Of course, this method isn't flawless as it's possible to have the same GUI open in multiple browser tabs and try to make changes from there, etc. but still I believe that it would be much better than the current state where modifications can be lost very easily.

I may try to do something about this problem myself, but of course, if someone more skilled is willing to do work in this direction, it would be very welcome too.

Issues related to this problem: https://github.com/syncthing/syncthing/issues/8589 (and possibly others, I will add them here if I find any)


Discussion / hints:

Fix patch:
diff --git a/gui/default/assets/lang/lang-en.json b/gui/default/assets/lang/lang-en.json
index 1c0b99fd417..4e3bba0ded5 100644
--- a/gui/default/assets/lang/lang-en.json
+++ b/gui/default/assets/lang/lang-en.json
@@ -325,6 +325,7 @@
     "Revert": "Revert",
     "Revert Local Changes": "Revert Local Changes",
     "Save": "Save",
+    "Saving changes": "Saving changes",
     "Scan Time Remaining": "Scan Time Remaining",
     "Scanning": "Scanning",
     "See external versioning help for supported templated command line parameters.": "See external versioning help for supported templated command line parameters.",
@@ -391,6 +392,7 @@
     "Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing is listening on the following network addresses for connection attempts from other devices:",
     "Syncthing is not listening for connection attempts from other devices on any address.  Only outgoing connections from this device may work.": "Syncthing is not listening for connection attempts from other devices on any address.  Only outgoing connections from this device may work.",
     "Syncthing is restarting.": "Syncthing is restarting.",
+    "Syncthing is saving changes.": "Syncthing is saving changes.",
     "Syncthing is upgrading.": "Syncthing is upgrading.",
     "Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.",
     "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…",
diff --git a/gui/default/index.html b/gui/default/index.html
index c3bf309e6a2..dff6ee5e453 100644
--- a/gui/default/index.html
+++ b/gui/default/index.html
@@ -978,6 +978,7 @@ <h4 class="panel-title">
   <ng-include src="'syncthing/core/restartingDialogView.html'"></ng-include>
   <ng-include src="'syncthing/core/upgradingDialogView.html'"></ng-include>
   <ng-include src="'syncthing/core/shutdownDialogView.html'"></ng-include>
+  <ng-include src="'syncthing/core/savingChangesDialogView.html'"></ng-include>
   <ng-include src="'syncthing/device/idqrModalView.html'"></ng-include>
   <ng-include src="'syncthing/device/editDeviceModalView.html'"></ng-include>
   <ng-include src="'syncthing/device/globalChangesModalView.html'"></ng-include>
diff --git a/gui/default/syncthing/core/savingChangesDialogView.html b/gui/default/syncthing/core/savingChangesDialogView.html
new file mode 100644
index 00000000000..81b0b49d153
--- /dev/null
+++ b/gui/default/syncthing/core/savingChangesDialogView.html
@@ -0,0 +1,8 @@
+<modal id="savingChanges" status="info" icon="fas fa-hourglass-half" heading="{{'Saving changes' | translate}}" large="no" closeable="no">
+  <div class="modal-body">
+    <p>
+      <span translate>Syncthing is saving changes.</span>
+      <span translate>Please wait</span>...
+    </p>
+  </div>
+</modal>
diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js
index 6e93b507cfc..975cb463850 100755
--- a/gui/default/syncthing/core/syncthingController.js
+++ b/gui/default/syncthing/core/syncthingController.js
@@ -1513,13 +1513,17 @@ angular.module('syncthing.core')
         };
 
         $scope.saveConfig = function () {
+            $('#savingChanges').modal();
             var cfg = JSON.stringify($scope.config);
             var opts = {
                 headers: {
                     'Content-Type': 'application/json'
                 }
             };
-            return $http.put(urlbase + '/config', cfg, opts).finally(refreshConfig).catch($scope.emitHTTPError);
+            return $http.put(urlbase + '/config', cfg, opts).finally(function () {
+                refreshConfig();
+                $('#savingChanges').modal("hide");
+            }).catch($scope.emitHTTPError);
         };
 
         $scope.urVersions = function () {


Test patch: