diff --git a/api.go b/api.go index cfab30b..e5c60a5 100644 --- a/api.go +++ b/api.go @@ -293,18 +293,26 @@ func (app *appContext) NewUser(gc *gin.Context) { if user["Id"] != nil { id = user["Id"].(string) } - if len(app.storage.policy) != 0 { - status, err = app.jf.setPolicy(id, app.storage.policy) - if !(status == 200 || status == 204) { - app.err.Printf("%s: Failed to set user policy: Code %d", req.Code, status) + if invite.Profile != "" { + profile, ok := app.storage.profiles[invite.Profile] + if !ok { + profile = app.storage.profiles["Default"] } - } - if len(app.storage.configuration) != 0 && len(app.storage.displayprefs) != 0 { - status, err = app.jf.setConfiguration(id, app.storage.configuration) - if (status == 200 || status == 204) && err == nil { - status, err = app.jf.setDisplayPreferences(id, app.storage.displayprefs) - } else { - app.err.Printf("%s: Failed to set configuration template: Code %d", req.Code, status) + if len(profile.Policy) != 0 { + app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile) + status, err = app.jf.setPolicy(id, profile.Policy) + if !(status == 200 || status == 204) { + app.err.Printf("%s: Failed to set user policy: Code %d", req.Code, status) + } + } + if len(profile.Configuration) != 0 && len(profile.Displayprefs) != 0 { + app.debug.Printf("Applying homescreen from profile \"%s\"", invite.Profile) + status, err = app.jf.setConfiguration(id, profile.Configuration) + if (status == 200 || status == 204) && err == nil { + status, err = app.jf.setDisplayPreferences(id, profile.Displayprefs) + } else { + app.err.Printf("%s: Failed to set configuration template: Code %d", req.Code, status) + } } } if app.config.Section("password_resets").Key("enabled").MustBool(false) { @@ -379,6 +387,7 @@ type generateInviteReq struct { MultipleUses bool `json:"multiple-uses"` NoLimit bool `json:"no-limit"` RemainingUses int `json:"remaining-uses"` + Profile string `json:"profile"` } func (app *appContext) GenerateInvite(gc *gin.Context) { @@ -418,11 +427,39 @@ func (app *appContext) GenerateInvite(gc *gin.Context) { app.info.Printf("%s: Sent invite email to %s", invite_code, req.Email) } } + if req.Profile != "" { + if _, ok := app.storage.profiles[req.Profile]; ok { + invite.Profile = req.Profile + } else { + invite.Profile = "Default" + } + } app.storage.invites[invite_code] = invite app.storage.storeInvites() gc.JSON(200, map[string]bool{"success": true}) } +type profileReq struct { + Invite string `json:"invite"` + Profile string `json:"profile"` +} + +func (app *appContext) SetProfile(gc *gin.Context) { + var req profileReq + gc.BindJSON(&req) + app.debug.Printf("%s: Setting profile to \"%s\"", req.Invite, req.Profile) + if _, ok := app.storage.profiles[req.Profile]; !ok { + app.err.Printf("%s: Profile \"%s\" not found", req.Invite, req.Profile) + respond(500, "Profile not found", gc) + return + } + inv := app.storage.invites[req.Invite] + inv.Profile = req.Profile + app.storage.invites[req.Invite] = inv + app.storage.storeInvites() + gc.JSON(200, map[string]bool{"success": true}) +} + func (app *appContext) GetInvites(gc *gin.Context) { app.debug.Println("Invites requested") current_time := time.Now() @@ -431,12 +468,14 @@ func (app *appContext) GetInvites(gc *gin.Context) { var invites []map[string]interface{} for code, inv := range app.storage.invites { _, _, days, hours, minutes, _ := timeDiff(inv.ValidTill, current_time) - invite := make(map[string]interface{}) - invite["code"] = code - invite["days"] = days - invite["hours"] = hours - invite["minutes"] = minutes - invite["created"] = app.formatDatetime(inv.Created) + invite := map[string]interface{}{ + "code": code, + "days": days, + "hours": hours, + "minutes": minutes, + "created": app.formatDatetime(inv.Created), + "profile": inv.Profile, + } if len(inv.UsedBy) != 0 { invite["used-by"] = inv.UsedBy } @@ -470,8 +509,15 @@ func (app *appContext) GetInvites(gc *gin.Context) { } invites = append(invites, invite) } - resp := map[string][]map[string]interface{}{ - "invites": invites, + profiles := make([]string, len(app.storage.profiles)) + i := 0 + for p := range app.storage.profiles { + profiles[i] = p + i++ + } + resp := map[string]interface{}{ + "profiles": profiles, + "invites": invites, } gc.JSON(200, resp) } diff --git a/config.go b/config.go index ae5359d..a045362 100644 --- a/config.go +++ b/config.go @@ -48,7 +48,7 @@ func (app *appContext) loadConfig() error { // } key.SetValue(key.MustString(filepath.Join(app.data_path, (key.Name() + ".json")))) } - for _, key := range []string{"user_configuration", "user_displayprefs", "ombi_template"} { + for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template"} { // if app.config.Section("files").Key(key).MustString("") == "" { // key.SetValue(filepath.Join(app.data_path, (key.Name() + ".json"))) // } diff --git a/config/config-base.json b/config/config-base.json index 35a1280..d2b568b 100644 --- a/config/config-base.json +++ b/config/config-base.json @@ -626,6 +626,14 @@ "value": "", "description": "Location of stored displayPreferences template (also used for homescreen layout) (json)" }, + "user_profiles": { + "name": "User Profiles", + "required": false, + "requires_restart": true, + "type": "text", + "value": "", + "description": "Location of stored user profiles (encompasses template and homescreen) (json)" + }, "custom_css": { "name": "Custom CSS", "required": false, diff --git a/data/static/admin.js b/data/static/admin.js index 5b79ddb..257c11b 100644 --- a/data/static/admin.js +++ b/data/static/admin.js @@ -137,7 +137,9 @@ var aboutModal = createModal('aboutModal'); var deleteModal = createModal('deleteModal'); var newUserModal = createModal('newUserModal'); -// Parsed invite: [, , <1: Empty invite (no delete/link), 0: Actual invite>, , , [], , , ] +var availableProfiles = []; +// god this is an ugly way to do it +// Parsed invite: [, , <1: Empty invite (no delete/link), 0: Actual invite>, , , [], , , , ] function parseInvite(invite, empty = false) { if (empty) { return ["None", "", 1]; @@ -170,6 +172,9 @@ function parseInvite(invite, empty = false) { if ('notify-creation' in invite) { i[8] = invite['notify-creation']; } + if ('profile' in invite) { + i[9] = invite['profile']; + } return i; } @@ -267,6 +272,29 @@ function addItem(parsedInvite) { let leftList = document.createElement('ul'); leftList.classList.add('list-group', 'list-group-flush'); + + let profileBox = document.createElement('li'); + profileBox.classList.add('input-group', 'py-1'); + let prof = ` + + `; + profileBox.innerHTML = prof; + leftList.appendChild(profileBox); + // 9 is profileName! availableProfiles if (typeof(parsedInvite[6]) != 'undefined') { let createdDate = document.createElement('li'); @@ -438,6 +466,7 @@ function generateInvites(empty = false) { req.onreadystatechange = function() { if (this.readyState == 4) { var data = this.response; + availableProfiles = data['profiles']; if (data['invites'] == null || data['invites'].length == 0) { document.getElementById('invites').textContent = ''; addItem(parseInvite([], true)); @@ -1117,7 +1146,29 @@ document.getElementById('settingsSave').onclick = function() { } } -// Diable 'Generate' button if days, hours, minutes are all zero +function setProfile(select) { + if (select.value == "") { + return; + } + let invite = select.id.replace("profile_", ""); + let req = new XMLHttpRequest(); + let send = { + "invite": invite, + "profile": select.value + }; + req.open("POST", "/setProfile", true); + req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":")); + req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + req.onreadystatechange = function() { + if (this.readyState == 4 && this.status != 200) { + generateInvites(false); + } + }; + req.send(JSON.stringify(send)); +} + + +// Disable 'Generate' button if days, hours, minutes are all zero function checkDuration() { let boxVals = [document.getElementById("days").value, document.getElementById("hours").value, document.getElementById("minutes").value]; let submit = document.getElementById("generateSubmit"); diff --git a/main.go b/main.go index 70fca6c..607ab3f 100644 --- a/main.go +++ b/main.go @@ -328,6 +328,15 @@ func start(asDaemon, firstCall bool) { app.storage.displayprefs_path = app.config.Section("files").Key("user_displayprefs").String() app.storage.loadDisplayprefs() + app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String() + app.storage.loadProfiles() + + if !(len(app.storage.policy) == 0 && len(app.storage.configuration) == 0 && len(app.storage.displayprefs) == 0) { + app.info.Println("Migrating user template files to new profile format") + app.storage.migrateToProfile() + app.storage.storeProfiles() + } + if app.config.Section("ombi").Key("enabled").MustBool(false) { app.storage.ombi_path = app.config.Section("files").Key("ombi_template").String() app.storage.loadOmbiTemplate() @@ -438,6 +447,7 @@ func start(asDaemon, firstCall bool) { api.POST("/newUserAdmin", app.NewUserAdmin) api.POST("/generateInvite", app.GenerateInvite) api.GET("/getInvites", app.GetInvites) + api.POST("/setProfile", app.SetProfile) api.POST("/setNotify", app.SetNotify) api.POST("/deleteInvite", app.DeleteInvite) api.POST("/deleteUser", app.DeleteUser) diff --git a/storage.go b/storage.go index 1af4cc9..c37d983 100644 --- a/storage.go +++ b/storage.go @@ -7,14 +7,21 @@ import ( ) type Storage struct { - timePattern string - invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path string - invites Invites - emails, policy, configuration, displayprefs, ombi_template map[string]interface{} + timePattern string + invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path string + invites Invites + profiles map[string]Profile + emails, policy, configuration, displayprefs, ombi_template map[string]interface{} } // timePattern: %Y-%m-%dT%H:%M:%S.%f +type Profile struct { + Policy map[string]interface{} `json:"policy"` + Configuration map[string]interface{} `json:"configuration"` + Displayprefs map[string]interface{} `json:"displayprefs"` +} + type Invite struct { Created time.Time `json:"created"` NoLimit bool `json:"no-limit"` @@ -23,6 +30,7 @@ type Invite struct { Email string `json:"email"` UsedBy [][]string `json:"used-by"` Notify map[string]map[string]bool `json:"notify"` + Profile string `json:"profile"` } type Invites map[string]Invite @@ -75,6 +83,27 @@ func (st *Storage) storeOmbiTemplate() error { return storeJSON(st.ombi_path, st.ombi_template) } +func (st *Storage) loadProfiles() error { + return loadJSON(st.profiles_path, &st.profiles) +} + +func (st *Storage) storeProfiles() error { + return storeJSON(st.profiles_path, st.profiles) +} + +func (st *Storage) migrateToProfile() error { + st.loadPolicy() + st.loadConfiguration() + st.loadDisplayprefs() + st.loadProfiles() + st.profiles["Default"] = Profile{ + Policy: st.policy, + Configuration: st.configuration, + Displayprefs: st.displayprefs, + } + return st.storeProfiles() +} + func loadJSON(path string, obj interface{}) error { var file []byte var err error