1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-28 03:50:10 +00:00

Compare commits

...

3 Commits

Author SHA1 Message Date
9bd7fca95e
fix table sizing on bootstrap 4; fix profile application on single-use
invites

use table-sm and no form-check-input so profile menu looks normal on
bs4. Profile was being read after being marked as used on invites, so
single-use invites were deleted and no profile was applied.
2020-09-23 19:12:58 +01:00
89e70f6f7a
Add default profile functionality
Default profile can be selected in settings.
2020-09-23 18:48:00 +01:00
6b25215768
apply settings even if section doesn't exist
in response to issue #7. Not sure why this wasn't the original
behaviour, nor how I hadn't noticed it earlier
2020-09-23 17:20:48 +01:00
7 changed files with 93 additions and 25 deletions

51
api.go
View File

@ -271,8 +271,9 @@ func (app *appContext) NewUser(gc *gin.Context) {
respond(401, "Unknown error", gc) respond(401, "Unknown error", gc)
return return
} }
app.checkInvite(req.Code, true, req.Username) app.storage.loadProfiles()
invite := app.storage.invites[req.Code] invite := app.storage.invites[req.Code]
app.checkInvite(req.Code, true, req.Username)
if app.config.Section("notifications").Key("enabled").MustBool(false) { if app.config.Section("notifications").Key("enabled").MustBool(false) {
for address, settings := range invite.Notify { for address, settings := range invite.Notify {
if settings["notify-creation"] { if settings["notify-creation"] {
@ -296,6 +297,7 @@ func (app *appContext) NewUser(gc *gin.Context) {
id = user["Id"].(string) id = user["Id"].(string)
} }
if invite.Profile != "" { if invite.Profile != "" {
app.debug.Printf("Applying settings from profile \"%s\"", invite.Profile)
profile, ok := app.storage.profiles[invite.Profile] profile, ok := app.storage.profiles[invite.Profile]
if !ok { if !ok {
profile = app.storage.profiles["Default"] profile = app.storage.profiles["Default"]
@ -472,7 +474,9 @@ func (app *appContext) SetProfile(gc *gin.Context) {
func (app *appContext) GetProfiles(gc *gin.Context) { func (app *appContext) GetProfiles(gc *gin.Context) {
app.storage.loadProfiles() app.storage.loadProfiles()
app.debug.Println("Profiles requested") app.debug.Println("Profiles requested")
out := map[string]map[string]interface{}{} out := map[string]interface{}{
"default_profile": app.storage.defaultProfile,
}
for name, p := range app.storage.profiles { for name, p := range app.storage.profiles {
out[name] = map[string]interface{}{ out[name] = map[string]interface{}{
"admin": p.Admin, "admin": p.Admin,
@ -484,6 +488,27 @@ func (app *appContext) GetProfiles(gc *gin.Context) {
gc.JSON(200, out) gc.JSON(200, out)
} }
func (app *appContext) SetDefaultProfile(gc *gin.Context) {
req := map[string]string{}
gc.BindJSON(&req)
app.info.Printf("Setting default profile to \"%s\"", req["name"])
if _, ok := app.storage.profiles[req["name"]]; !ok {
app.err.Printf("Profile not found: \"%s\"", req["name"])
respond(500, "Profile not found", gc)
return
}
for name, profile := range app.storage.profiles {
if name == req["name"] {
profile.Admin = true
app.storage.profiles[name] = profile
} else {
profile.Admin = false
}
}
app.storage.defaultProfile = req["name"]
gc.JSON(200, map[string]bool{"success": true})
}
type newProfileReq struct { type newProfileReq struct {
Name string `json:"name"` Name string `json:"name"`
ID string `json:"id"` ID string `json:"id"`
@ -584,10 +609,17 @@ func (app *appContext) GetInvites(gc *gin.Context) {
invites = append(invites, invite) invites = append(invites, invite)
} }
profiles := make([]string, len(app.storage.profiles)) profiles := make([]string, len(app.storage.profiles))
i := 0 if len(app.storage.profiles) != 0 {
for p := range app.storage.profiles { profiles[0] = app.storage.defaultProfile
profiles[i] = p i := 1
i++ if len(app.storage.profiles) > 1 {
for p := range app.storage.profiles {
if p != app.storage.defaultProfile {
profiles[i] = p
i++
}
}
}
} }
resp := map[string]interface{}{ resp := map[string]interface{}{
"profiles": profiles, "profiles": profiles,
@ -950,8 +982,11 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
gc.BindJSON(&req) gc.BindJSON(&req)
tempConfig, _ := ini.Load(app.config_path) tempConfig, _ := ini.Load(app.config_path)
for section, settings := range req { for section, settings := range req {
_, err := tempConfig.GetSection(section) if section != "restart-program" {
if section != "restart-program" && err == nil { _, err := tempConfig.GetSection(section)
if err != nil {
tempConfig.NewSection(section)
}
for setting, value := range settings.(map[string]interface{}) { for setting, value := range settings.(map[string]interface{}) {
tempConfig.Section(section).Key(setting).SetValue(value.(string)) tempConfig.Section(section).Key(setting).SetValue(value.(string))
} }

View File

@ -177,7 +177,7 @@
"bs5": { "bs5": {
"name": "Use Bootstrap 5", "name": "Use Bootstrap 5",
"required": false, "required": false,
"requires_restart": false, "requires_restart": true,
"type": "bool", "type": "bool",
"value": false, "value": false,
"description": "Use Bootstrap 5 (currently in alpha). This also removes the need for jQuery, so the page should load faster." "description": "Use Bootstrap 5 (currently in alpha). This also removes the need for jQuery, so the page should load faster."
@ -608,7 +608,7 @@
"requires_restart": true, "requires_restart": true,
"type": "text", "type": "text",
"value": "", "value": "",
"description": "Location of stored user policy template (json)." "description": "Deprecated. Location of stored user policy template (json)."
}, },
"user_configuration": { "user_configuration": {
"name": "userConfiguration", "name": "userConfiguration",
@ -616,7 +616,7 @@
"requires_restart": true, "requires_restart": true,
"type": "text", "type": "text",
"value": "", "value": "",
"description": "Location of stored user configuration template (used for setting homescreen layout) (json)" "description": "Deprecated. Location of stored user configuration template (used for setting homescreen layout) (json)"
}, },
"user_displayprefs": { "user_displayprefs": {
"name": "displayPreferences", "name": "displayPreferences",
@ -624,7 +624,7 @@
"requires_restart": true, "requires_restart": true,
"type": "text", "type": "text",
"value": "", "value": "",
"description": "Location of stored displayPreferences template (also used for homescreen layout) (json)" "description": "Deprecated. Location of stored displayPreferences template (also used for homescreen layout) (json)"
}, },
"user_profiles": { "user_profiles": {
"name": "User Profiles", "name": "User Profiles",
@ -632,7 +632,7 @@
"requires_restart": true, "requires_restart": true,
"type": "text", "type": "text",
"value": "", "value": "",
"description": "Location of stored user profiles (encompasses template and homescreen) (json)" "description": "Location of stored user profiles (encompasses template and configuration and displayprefs) (json)"
}, },
"custom_css": { "custom_css": {
"name": "Custom CSS", "name": "Custom CSS",

View File

@ -216,7 +216,7 @@
"bs5": { "bs5": {
"name": "Use Bootstrap 5", "name": "Use Bootstrap 5",
"required": false, "required": false,
"requires_restart": false, "requires_restart": true,
"type": "bool", "type": "bool",
"value": false, "value": false,
"description": "Use Bootstrap 5 (currently in alpha). This also removes the need for jQuery, so the page should load faster." "description": "Use Bootstrap 5 (currently in alpha). This also removes the need for jQuery, so the page should load faster."
@ -678,6 +678,7 @@
"user_template", "user_template",
"user_configuration", "user_configuration",
"user_displayprefs", "user_displayprefs",
"user_profiles",
"custom_css" "custom_css"
], ],
"meta": { "meta": {
@ -714,7 +715,7 @@
"requires_restart": true, "requires_restart": true,
"type": "text", "type": "text",
"value": "", "value": "",
"description": "Location of stored user policy template (json)." "description": "Deprecated. Location of stored user policy template (json)."
}, },
"user_configuration": { "user_configuration": {
"name": "userConfiguration", "name": "userConfiguration",
@ -722,7 +723,7 @@
"requires_restart": true, "requires_restart": true,
"type": "text", "type": "text",
"value": "", "value": "",
"description": "Location of stored user configuration template (used for setting homescreen layout) (json)" "description": "Deprecated. Location of stored user configuration template (used for setting homescreen layout) (json)"
}, },
"user_displayprefs": { "user_displayprefs": {
"name": "displayPreferences", "name": "displayPreferences",
@ -730,7 +731,15 @@
"requires_restart": true, "requires_restart": true,
"type": "text", "type": "text",
"value": "", "value": "",
"description": "Location of stored displayPreferences template (also used for homescreen layout) (json)" "description": "Deprecated. 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 configuration and displayprefs) (json)"
}, },
"custom_css": { "custom_css": {
"name": "Custom CSS", "name": "Custom CSS",

View File

@ -437,10 +437,11 @@
<div id="profiles" class="unfocused"> <div id="profiles" class="unfocused">
<div class="card card-body"> <div class="card card-body">
<p>Profiles are applied to users when they create an account. They include things like access rights and homescreen layout. You can create them here.</p> <p>Profiles are applied to users when they create an account. They include things like access rights and homescreen layout. You can create them here.</p>
<table class="table table-striped table-borderless"> <table class="table table-sm table-striped table-borderless">
<thead> <thead>
<tr> <tr>
<th scope="col">Name</th> <th scope="col">Name</th>
<th scope="col">Default</th>
<th scope="col">From</th> <th scope="col">From</th>
<th scope="col">Admin?</th> <th scope="col">Admin?</th>
<th scope="col">Libraries</th> <th scope="col">Libraries</th>

View File

@ -460,6 +460,7 @@ func start(asDaemon, firstCall bool) {
api.GET("/getInvites", app.GetInvites) api.GET("/getInvites", app.GetInvites)
api.GET("/getProfiles", app.GetProfiles) api.GET("/getProfiles", app.GetProfiles)
api.POST("/setProfile", app.SetProfile) api.POST("/setProfile", app.SetProfile)
api.POST("/setDefaultProfile", app.SetDefaultProfile)
api.POST("/createProfile", app.CreateProfile) api.POST("/createProfile", app.CreateProfile)
api.POST("/deleteProfile", app.DeleteProfile) api.POST("/deleteProfile", app.DeleteProfile)
api.POST("/setNotify", app.SetNotify) api.POST("/setNotify", app.SetNotify)

View File

@ -12,6 +12,7 @@ type Storage struct {
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path string invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path string
invites Invites invites Invites
profiles map[string]Profile profiles map[string]Profile
defaultProfile string
emails, policy, configuration, displayprefs, ombi_template map[string]interface{} emails, policy, configuration, displayprefs, ombi_template map[string]interface{}
} }
@ -24,6 +25,7 @@ type Profile struct {
Policy map[string]interface{} `json:"policy,omitempty"` Policy map[string]interface{} `json:"policy,omitempty"`
Configuration map[string]interface{} `json:"configuration,omitempty"` Configuration map[string]interface{} `json:"configuration,omitempty"`
Displayprefs map[string]interface{} `json:"displayprefs,omitempty"` Displayprefs map[string]interface{} `json:"displayprefs,omitempty"`
Default bool `json:"default,omitempty"`
} }
type Invite struct { type Invite struct {
@ -90,6 +92,9 @@ func (st *Storage) storeOmbiTemplate() error {
func (st *Storage) loadProfiles() error { func (st *Storage) loadProfiles() error {
err := loadJSON(st.profiles_path, &st.profiles) err := loadJSON(st.profiles_path, &st.profiles)
for name, profile := range st.profiles { for name, profile := range st.profiles {
if profile.Default {
st.defaultProfile = name
}
change := false change := false
if profile.Policy["IsAdministrator"] != nil { if profile.Policy["IsAdministrator"] != nil {
profile.Admin = profile.Policy["IsAdministrator"].(bool) profile.Admin = profile.Policy["IsAdministrator"].(bool)
@ -112,8 +117,10 @@ func (st *Storage) loadProfiles() error {
st.profiles[name] = profile st.profiles[name] = profile
} }
} }
if err != nil { if st.defaultProfile == "" {
panic(err) for n := range st.profiles {
st.defaultProfile = n
}
} }
return err return err
} }

View File

@ -143,10 +143,12 @@ const populateProfiles = (noTable?: boolean): void => _get("/getProfiles", null,
if (this.readyState == 4 && this.status == 200) { if (this.readyState == 4 && this.status == 200) {
const profileList = document.getElementById('profileList'); const profileList = document.getElementById('profileList');
profileList.textContent = ''; profileList.textContent = '';
availableProfiles = []; availableProfiles = [this.response["default_profile"]];
for (let name in this.response) { for (let name in this.response) {
availableProfiles.push(name); if (name != availableProfiles[0]) {
if (!noTable) { availableProfiles.push(name);
}
if (!noTable && name != "default_profile") {
const profile: Profile = { const profile: Profile = {
Admin: this.response[name]["admin"], Admin: this.response[name]["admin"],
LibraryAccess: this.response[name]["libraries"], LibraryAccess: this.response[name]["libraries"],
@ -154,16 +156,28 @@ const populateProfiles = (noTable?: boolean): void => _get("/getProfiles", null,
}; };
profileList.innerHTML += ` profileList.innerHTML += `
<td nowrap="nowrap" class="align-middle"><strong>${name}</strong></td> <td nowrap="nowrap" class="align-middle"><strong>${name}</strong></td>
<td nowrap="nowrap" class="align-middle"><input class="${(bsVersion == 5) ? "form-check-input" : ""}" type="radio" name="defaultProfile" onclick="setDefaultProfile('${name}')" ${(name == availableProfiles[0]) ? "checked" : ""}></td>
<td nowrap="nowrap" class="align-middle">${profile.FromUser}</td> <td nowrap="nowrap" class="align-middle">${profile.FromUser}</td>
<td nowrap="nowrap" class="align-middle">${profile.Admin ? "Yes" : "No"}</td> <td nowrap="nowrap" class="align-middle">${profile.Admin ? "Yes" : "No"}</td>
<td nowrap="nowrap" class="align-middle">${profile.LibraryAccess}</td> <td nowrap="nowrap" class="align-middle">${profile.LibraryAccess}</td>
<td nowrap="nowrap" class="align-middle"><button class="btn btn-outline-danger" onclick="deleteProfile('${name}')">Delete</button></td> <td nowrap="nowrap" class="align-middle"><button class="btn btn-outline-danger" id="defaultProfile_${name}" onclick="deleteProfile('${name}')">Delete</button></td>
`; `;
} }
} }
} }
}); });
const setDefaultProfile = (name: string): void => _post("/setDefaultProfile", { "name": name }, function (): void {
if (this.readyState == 4) {
if (this.status != 200) {
(document.getElementById(`defaultProfile_${availableProfiles[0]}`) as HTMLInputElement).checked = true;
(document.getElementById(`defaultProfile_${name}`) as HTMLInputElement).checked = false;
} else {
generateInvites();
}
}
});
const deleteProfile = (name: string): void => _post("/deleteProfile", { "name": name }, function (): void { const deleteProfile = (name: string): void => _post("/deleteProfile", { "name": name }, function (): void {
if (this.readyState == 4 && this.status == 200) { if (this.readyState == 4 && this.status == 200) {
populateProfiles(); populateProfiles();
@ -224,10 +238,11 @@ function storeProfile(): void {
addAttr(button, "btn-primary"); addAttr(button, "btn-primary");
rmAttr(button, "btn-success"); rmAttr(button, "btn-success");
button.disabled = false; button.disabled = false;
populateProfiles();
userDefaultsModal.hide(); userDefaultsModal.hide();
}, 1000); }, 1000);
populateProfiles();
generateInvites();
} else { } else {
if ("error" in this.response) { if ("error" in this.response) {
button.textContent = this.response["error"]; button.textContent = this.response["error"];