diff --git a/admin/api/settings.go b/admin/api/settings.go index 8aa49d83..8c0c5636 100644 --- a/admin/api/settings.go +++ b/admin/api/settings.go @@ -9,6 +9,7 @@ import ( "os" "path" "reflect" + "strconv" "time" "srs.epita.fr/fic-server/admin/sync" @@ -38,9 +39,34 @@ func declareSettingsRoutes(router *gin.RouterGroup) { c.JSON(http.StatusOK, true) }) + router.GET("/settings-next", listNextSettings) + + apiNextSettingsRoutes := router.Group("/settings-next/:ts") + apiNextSettingsRoutes.Use(NextSettingsHandler) + apiNextSettingsRoutes.GET("", getNextSettings) + apiNextSettingsRoutes.DELETE("", deleteNextSettings) + router.POST("/reset", reset) } +func NextSettingsHandler(c *gin.Context) { + ts, err := strconv.ParseInt(string(c.Params.ByName("ts")), 10, 64) + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid next settings identifier"}) + return + } + + nsf, err := settings.ReadNextSettingsFile(path.Join(settings.SettingsDir, fmt.Sprintf("%d.json", ts)), ts) + if err != nil { + c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Next settings not found"}) + return + } + + c.Set("next-settings", nsf) + + c.Next() +} + func getROSettings(c *gin.Context) { syncMtd := "Disabled" if sync.GlobalImporter != nil { @@ -142,14 +168,102 @@ func saveSettings(c *gin.Context) { return } - if err := settings.SaveSettings(path.Join(settings.SettingsDir, settings.SettingsFile), config); err != nil { - log.Println("Unable to SaveSettings:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to save settings: %s", err.Error())}) + // Is this a future setting? + if c.Request.URL.Query().Has("t") { + t, err := time.Parse(time.RFC3339, c.Request.URL.Query().Get("t")) + if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) + return + } + + // Load current settings to perform diff later + init_settings, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)) + if err != nil { + log.Println("Unable to ReadSettings:", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to read settings: %s", err.Error())}) + return + } + + current_settings := init_settings + // Apply already registered settings + nsu, err := settings.MergeNextSettingsUntil(&t) + if err == nil { + current_settings = settings.MergeSettings(*init_settings, nsu) + } else { + log.Println("Unable to MergeNextSettingsUntil:", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to merge next settings: %s", err.Error())}) + return + } + + // Keep only diff + diff := settings.DiffSettings(current_settings, config) + + hasItems := false + for _, _ = range diff { + hasItems = true + break + } + + if !hasItems { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "No difference to apply."}) + return + } + + if !c.Request.URL.Query().Has("erase") { + // Check if there is already diff to apply at the given time + if nsf, err := settings.ReadNextSettingsFile(path.Join(settings.SettingsDir, fmt.Sprintf("%d.json", t.Unix())), t.Unix()); err == nil { + for k, v := range nsf.Values { + if _, ok := diff[k]; !ok { + diff[k] = v + } + } + } + } + + // Save the diff + settings.SaveSettings(path.Join(settings.SettingsDir, fmt.Sprintf("%d.json", t.Unix())), diff) + + // Return current settings + c.JSON(http.StatusOK, current_settings) + } else { + // Just apply settings right now! + if err := settings.SaveSettings(path.Join(settings.SettingsDir, settings.SettingsFile), config); err != nil { + log.Println("Unable to SaveSettings:", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to save settings: %s", err.Error())}) + return + } + + ApplySettings(config) + c.JSON(http.StatusOK, config) + } +} + +func listNextSettings(c *gin.Context) { + nsf, err := settings.ListNextSettingsFiles() + if err != nil { + log.Println("Unable to ListNextSettingsFiles:", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list next settings files: %s", err.Error())}) return } - ApplySettings(config) - c.JSON(http.StatusOK, config) + c.JSON(http.StatusOK, nsf) +} + +func getNextSettings(c *gin.Context) { + c.JSON(http.StatusOK, c.MustGet("next-settings").(*settings.NextSettingsFile)) +} + +func deleteNextSettings(c *gin.Context) { + nsf := c.MustGet("next-settings").(*settings.NextSettingsFile) + + err := os.Remove(path.Join(settings.SettingsDir, fmt.Sprintf("%d.json", nsf.Id))) + if err != nil { + log.Println("Unable to remove the file:", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to remove the file: %s", err.Error())}) + return + } + + c.JSON(http.StatusOK, true) } func ApplySettings(config *settings.Settings) { diff --git a/admin/static/js/app.js b/admin/static/js/app.js index fc06d574..06f94728 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -234,6 +234,11 @@ angular.module("FICApp") 'update': {method: 'PUT'}, }) }) + .factory("NextSettings", function($resource) { + return $resource("api/settings-next/:tsId", { tsId: '@id'}, { + 'update': {method: 'PUT'}, + }) + }) .factory("SettingsChallenge", function($resource) { return $resource("api/challenge.json", null, { 'update': {method: 'PUT'}, @@ -510,7 +515,23 @@ angular.module("FICApp") $scope.monitor = Monitor.get(); }) - .controller("SettingsController", function($scope, $rootScope, Settings, SettingsChallenge, $location, $http, $interval) { + .controller("SettingsController", function($scope, $rootScope, NextSettings, Settings, SettingsChallenge, $location, $http, $interval) { + $scope.nextsettings = NextSettings.query(); + $scope.erase = false; + $scope.editNextSettings = function(ns) { + $scope.activateTime = ns.date; + $scope.erase = true; + Object.keys(ns.values).forEach(function(k) { + $scope.config[k] = ns.values[k]; + }); + $scope.config.enableExerciceDepend = $scope.config.unlockedChallengeDepth >= 0; + } + $scope.deleteNextSettings = function(ns) { + ns.$delete().then(function() { + $scope.nextsettings = NextSettings.query(); + }) + } + $scope.displayDangerousActions = false; $scope.config = Settings.get(); $scope.dist_config = {}; @@ -524,6 +545,7 @@ angular.module("FICApp") }) $scope.challenge = SettingsChallenge.get(); $scope.duration = 360; + $scope.activateTime = "0001-01-01T00:00:00Z"; $scope.challenge.$promise.then(function(c) { if (c.duration) $scope.duration = c.duration; @@ -549,17 +571,24 @@ angular.module("FICApp") var nStart = this.config.start; var nEnd = this.config.end; var nGen = this.config.generation; - var aTime = this.config.activateTime; var state = this.config.enableExerciceDepend; this.config.unlockedChallengeDepth = (this.config.enableExerciceDepend?this.config.unlockedChallengeDepth:-1) - this.config.$update(function(response) { + var updateQuery = {}; + if (this.activateTime && this.activateTime != '0001-01-01T00:00:00Z') { + updateQuery['t'] = this.activateTime; + } + if (this.erase) { + updateQuery['erase'] = true; + this.erase = false; + } + this.config.$update(updateQuery, function(response) { $scope.dist_config = Object.assign({}, response); $scope.addToast('success', msg); + $scope.nextsettings = NextSettings.query(); response.enableExerciceDepend = response.unlockedChallengeDepth >= 0; $rootScope.settings.start = new Date(nStart); $rootScope.settings.end = new Date(nEnd); $rootScope.settings.generation = new Date(nGen); - $rootScope.settings.activateTime = new Date(aTime); }, function(response) { $scope.addToast('danger', 'An error occurs when saving settings:', response.data.errmsg); }); @@ -582,12 +611,12 @@ angular.module("FICApp") }); } $scope.updateActivateTime = function() { - $rootScope.settings.activateTime = this.config.activateTime; + $rootScope.settings.activateTime = this.activateTime; } $scope.updActivateTime = function(modulo) { var ts = Math.floor((new Date(this.config.end) - Date.now() - (60000 * modulo / 2)) / (60000 * modulo)) * (60000 * modulo); var d = new Date(this.config.end) - ts; - this.config.activateTime = new Date(d).toISOString(); + this.activateTime = new Date(d).toISOString(); this.updateActivateTime(); } $scope.reset = function(type) { diff --git a/admin/static/views/settings.html b/admin/static/views/settings.html index 7fda4224..5b961969 100644 --- a/admin/static/views/settings.html +++ b/admin/static/views/settings.html @@ -6,7 +6,7 @@