Initial features for move to profiles

user templates will become profiles. You will be able to make multiple,
and assign them  to invites individually. This commit migrates the
separate template files into one profile entry called "Default", and
lets you select them on invites. No way to create profiles has been
added yet.
pull/20/head
Harvey Tindall 4 years ago
parent 49b056f1d6
commit c4acb43cb8
No known key found for this signature in database
GPG Key ID: BBC65952848FB1A2

@ -293,20 +293,28 @@ 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 invite.Profile != "" {
profile, ok := app.storage.profiles[invite.Profile]
if !ok {
profile = app.storage.profiles["Default"]
}
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(app.storage.configuration) != 0 && len(app.storage.displayprefs) != 0 {
status, err = app.jf.setConfiguration(id, app.storage.configuration)
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, app.storage.displayprefs)
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) {
app.storage.emails[id] = req.Email
app.storage.storeEmails()
@ -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,7 +509,14 @@ func (app *appContext) GetInvites(gc *gin.Context) {
}
invites = append(invites, invite)
}
resp := map[string][]map[string]interface{}{
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)

@ -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")))
// }

@ -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,

@ -137,7 +137,9 @@ var aboutModal = createModal('aboutModal');
var deleteModal = createModal('deleteModal');
var newUserModal = createModal('newUserModal');
// Parsed invite: [<code>, <expires in _>, <1: Empty invite (no delete/link), 0: Actual invite>, <email address>, <remaining uses>, [<used-by>], <date created>, <notify on expiry>, <notify on creation>]
var availableProfiles = [];
// god this is an ugly way to do it
// Parsed invite: [<code>, <expires in _>, <1: Empty invite (no delete/link), 0: Actual invite>, <email address>, <remaining uses>, [<used-by>], <date created>, <notify on expiry>, <notify on creation>, <selectedProfile>]
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;
}
@ -268,6 +273,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 = `
<label class="input-group-text" for="profile_${parsedInvite[0]}">Profile: </label>
<select class="form-select" id="profile_${parsedInvite[0]}" onchange="setProfile(this)">
`;
let match = false;
for (profile of availableProfiles) {
let selected = "";
if (profile == parsedInvite[9]) {
selected = "selected";
match = true;
}
prof += `<option value="${profile}" ${selected}>${profile}</option>`;
}
if (!match) {
prof += `<option value="" selected></option>`;
}
prof += `</select>`;
profileBox.innerHTML = prof;
leftList.appendChild(profileBox);
// 9 is profileName! availableProfiles
if (typeof(parsedInvite[6]) != 'undefined') {
let createdDate = document.createElement('li');
createdDate.classList.add('list-group-item', 'py-1');
@ -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");

@ -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)

@ -8,13 +8,20 @@ import (
type Storage struct {
timePattern string
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path 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

Loading…
Cancel
Save