Skip to content

Commit

Permalink
Profiles replace user templates
Browse files Browse the repository at this point in the history
Profile functionality is essentially complete, and they can be created
in settings. Only thing currently missing is a way to set a default
profile.
  • Loading branch information
hrfee committed Sep 22, 2020
1 parent 49ef3df commit 903a61d
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 73 deletions.
91 changes: 80 additions & 11 deletions api.go
Expand Up @@ -469,6 +469,71 @@ func (app *appContext) SetProfile(gc *gin.Context) {
gc.JSON(200, map[string]bool{"success": true})
}

func (app *appContext) GetProfiles(gc *gin.Context) {
app.storage.loadProfiles()
app.debug.Println("Profiles requested")
out := map[string]map[string]interface{}{}
for name, p := range app.storage.profiles {
out[name] = map[string]interface{}{
"admin": p.Admin,
"libraries": p.LibraryAccess,
"fromUser": p.FromUser,
}
}
fmt.Println(out)
gc.JSON(200, out)
}

type newProfileReq struct {
Name string `json:"name"`
ID string `json:"id"`
Homescreen bool `json:"homescreen"`
}

func (app *appContext) CreateProfile(gc *gin.Context) {
fmt.Println("Profile creation requested")
var req newProfileReq
gc.BindJSON(&req)
user, status, err := app.jf.userById(req.ID, false)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get user from Jellyfin: Code %d", status)
app.debug.Printf("Error: %s", err)
respond(500, "Couldn't get user", gc)
return
}
profile := Profile{
FromUser: user["Name"].(string),
Policy: user["Policy"].(map[string]interface{}),
}
app.debug.Printf("Creating profile from user \"%s\"", user["Name"].(string))
if req.Homescreen {
profile.Configuration = user["Configuration"].(map[string]interface{})
profile.Displayprefs, status, err = app.jf.getDisplayPreferences(req.ID)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get DisplayPrefs: Code %d", status)
app.debug.Printf("Error: %s", err)
respond(500, "Couldn't get displayprefs", gc)
return
}
}
app.storage.loadProfiles()
app.storage.profiles[req.Name] = profile
app.storage.storeProfiles()
app.storage.loadProfiles()
gc.JSON(200, map[string]bool{"success": true})
}

func (app *appContext) DeleteProfile(gc *gin.Context) {
req := map[string]string{}
gc.BindJSON(&req)
name := req["name"]
if _, ok := app.storage.profiles[name]; ok {
delete(app.storage.profiles, name)
}
app.storage.storeProfiles()
gc.JSON(200, map[string]bool{"success": true})
}

func (app *appContext) GetInvites(gc *gin.Context) {
app.debug.Println("Invites requested")
current_time := time.Now()
Expand Down Expand Up @@ -726,12 +791,13 @@ func (app *appContext) SetOmbiDefaults(gc *gin.Context) {

type defaultsReq struct {
From string `json:"from"`
Profile string `json:"profile"`
ApplyTo []string `json:"apply_to"`
ID string `json:"id"`
Homescreen bool `json:"homescreen"`
}

func (app *appContext) SetDefaults(gc *gin.Context) {
/*func (app *appContext) SetDefaults(gc *gin.Context) {
var req defaultsReq
gc.BindJSON(&req)
userID := req.ID
Expand Down Expand Up @@ -765,28 +831,31 @@ func (app *appContext) SetDefaults(gc *gin.Context) {
app.debug.Println("DisplayPrefs template stored")
}
gc.JSON(200, map[string]bool{"success": true})
}
}*/

func (app *appContext) ApplySettings(gc *gin.Context) {
app.info.Println("User settings change requested")
var req defaultsReq
gc.BindJSON(&req)
applyingFrom := "template"
applyingFrom := "profile"
var policy, configuration, displayprefs map[string]interface{}
if req.From == "template" {
if len(app.storage.policy) == 0 {
respond(500, "No policy template available", gc)
if req.From == "profile" {
app.storage.loadProfiles()
if _, ok := app.storage.profiles[req.Profile]; !ok || len(app.storage.profiles[req.Profile].Policy) == 0 {
app.err.Printf("Couldn't find profile \"%s\" or profile was empty", req.Profile)
respond(500, "Couldn't find profile", gc)
return
}
app.storage.loadPolicy()
policy = app.storage.policy
if req.Homescreen {
if len(app.storage.configuration) == 0 || len(app.storage.displayprefs) == 0 {
if len(app.storage.profiles[req.Profile].Configuration) == 0 || len(app.storage.profiles[req.Profile].Displayprefs) == 0 {
app.err.Printf("No homescreen saved in profile \"%s\"", req.Profile)
respond(500, "No homescreen template available", gc)
return
}
configuration = app.storage.configuration
displayprefs = app.storage.displayprefs
configuration = app.storage.profiles[req.Profile].Configuration
displayprefs = app.storage.profiles[req.Profile].Displayprefs
}
policy = app.storage.profiles[req.Profile].Policy
} else if req.From == "user" {
applyingFrom = "user"
user, status, err := app.jf.userById(req.ID, false)
Expand Down
39 changes: 33 additions & 6 deletions data/templates/admin.html
Expand Up @@ -119,12 +119,21 @@ <h5 class="modal-title" id="defaultsTitle"></h5>
<div class="modal-body">
<p id="userDefaultsDescription"></p>
<div class="mb-3" id="defaultsSourceSection">
<label for="defaultsSource">Use settings from:</label>
<label for="defaultsSource" class="form-label">Use settings from:</label>
<select class="form-select" id="defaultsSource" aria-label="User settings source">
<option value="userTemplate" selected>Use existing user template</option>
<option value="profile" selected>Profile</option>
<option value="fromUser">Source from existing user</option>
</select>
</div>
<div class="mb-3 unfocused" id="profileSelectBox">
<label for="profileSelect" class="form-label">Profile</label>
<select class="form-select" id="profileSelect" aria-label="Profile to apply">
</select>
</div>
<div class="mb-3 unfocused" id="newProfileBox">
<label for="newProfileName" class="form-label">Name</label>
<input type="text" class="form-control" id="newProfileName" aria-describedby="Profile Name">
</div>
<div id="defaultUserRadios"></div>
<div class="form-check" style="margin-top: 1rem;">
<input class="form-check-input" type="checkbox" value="" id="storeDefaultHomescreen" checked>
Expand Down Expand Up @@ -407,14 +416,14 @@ <h2><a id="settingsTabButton" class="nl nav-link">Settings</a></h2>
<div class="" id="settingsLeft">
<ul class="list-group list-group-flush" style="margin-bottom: 1rem;">
<p>Note: <sup class="text-danger">*</sup> Indicates required field, <sup class="text-danger">R</sup> Indicates changes require a restart.</p>
<button type="button" class="list-group-item list-group-item-action" id="openAbout">
<button type="button" class="list-group-item list-group-item-action static" id="openAbout">
About <i class="fa fa-info-circle settingIcon"></i>
</button>
<button type="button" class="list-group-item list-group-item-action" id="openDefaultsWizard">
New User Defaults <i class="fa fa-user settingIcon"></i>
<button type="button" class="list-group-item list-group-item-action" id="profiles_button">
User Profiles <i class="fa fa-user settingIcon"></i>
</button>
{{ if .ombiEnabled }}
<button type="button" class="list-group-item list-group-item-action" id="openOmbiDefaults">
<button type="button" class="list-group-item list-group-item-action static" id="openOmbiDefaults">
Ombi User Defaults <i class="fa fa-chain-broken settingIcon"></i>
</button>
{{ end }}
Expand All @@ -425,6 +434,24 @@ <h2><a id="settingsTabButton" class="nl nav-link">Settings</a></h2>
</div>
<div class="col">
<div class="" id="settingsContent">
<div id="profiles" class="unfocused">
<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>
<table class="table table-striped table-borderless">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">From</th>
<th scope="col">Admin?</th>
<th scope="col">Libraries</th>
<th scope="col"><button class="btn btn-outline-primary" onclick="createProfile()">Create</button></th>
</tr>
</thead>
<tbody id="profileList">
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
Expand Down
5 changes: 4 additions & 1 deletion main.go
Expand Up @@ -458,13 +458,16 @@ func start(asDaemon, firstCall bool) {
api.POST("/newUserAdmin", app.NewUserAdmin)
api.POST("/generateInvite", app.GenerateInvite)
api.GET("/getInvites", app.GetInvites)
api.GET("/getProfiles", app.GetProfiles)
api.POST("/setProfile", app.SetProfile)
api.POST("/createProfile", app.CreateProfile)
api.POST("/deleteProfile", app.DeleteProfile)
api.POST("/setNotify", app.SetNotify)
api.POST("/deleteInvite", app.DeleteInvite)
api.POST("/deleteUser", app.DeleteUser)
api.GET("/getUsers", app.GetUsers)
api.POST("/modifyEmails", app.ModifyEmails)
api.POST("/setDefaults", app.SetDefaults)
// api.POST("/setDefaults", app.SetDefaults)
api.POST("/applySettings", app.ApplySettings)
api.GET("/getConfig", app.GetConfig)
api.POST("/modifyConfig", app.ModifyConfig)
Expand Down
39 changes: 35 additions & 4 deletions storage.go
Expand Up @@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"io/ioutil"
"strconv"
"time"
)

Expand All @@ -17,9 +18,12 @@ type Storage struct {
// 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"`
Admin bool `json:"admin,omitempty"`
LibraryAccess string `json:"libraries,omitempty"`
FromUser string `json:"fromUser,omitempty"`
Policy map[string]interface{} `json:"policy,omitempty"`
Configuration map[string]interface{} `json:"configuration,omitempty"`
Displayprefs map[string]interface{} `json:"displayprefs,omitempty"`
}

type Invite struct {
Expand Down Expand Up @@ -84,7 +88,34 @@ func (st *Storage) storeOmbiTemplate() error {
}

func (st *Storage) loadProfiles() error {
return loadJSON(st.profiles_path, &st.profiles)
err := loadJSON(st.profiles_path, &st.profiles)
for name, profile := range st.profiles {
change := false
if profile.Policy["IsAdministrator"] != nil {
profile.Admin = profile.Policy["IsAdministrator"].(bool)
change = true
}
if profile.Policy["EnabledFolders"] != nil {
length := len(profile.Policy["EnabledFolders"].([]interface{}))
if length == 0 {
profile.LibraryAccess = "All"
} else {
profile.LibraryAccess = strconv.Itoa(length)
}
change = true
}
if profile.FromUser == "" {
profile.FromUser = "Unknown"
change = true
}
if change {
st.profiles[name] = profile
}
}
if err != nil {
panic(err)
}
return err
}

func (st *Storage) storeProfiles() error {
Expand Down
19 changes: 16 additions & 3 deletions ts/accounts.ts
Expand Up @@ -247,23 +247,36 @@ function populateRadios(): void {
if (userIDs.length > 1) {
userString += "s";
}
populateProfiles(true);
const profileSelect = document.getElementById('profileSelect') as HTMLSelectElement;
profileSelect.textContent = '';
for (let i = 0; i < availableProfiles.length; i++) {
profileSelect.innerHTML += `
<option value="${availableProfiles[i]}" ${(i == 0) ? "selected" : ""}>${availableProfiles[i]}</option>
`;
}
document.getElementById('defaultsTitle').textContent = `Apply settings to ${userIDs.length} ${userString}`;
document.getElementById('userDefaultsDescription').textContent = `
Create an account and configure it to your liking, then choose it from below to apply to your selected users.
Apply settings from an existing profile or source settings from a user.
`;
document.getElementById('storeHomescreenLabel').textContent = `Apply homescreen layout`;
Focus(document.getElementById('defaultsSourceSection'));
(<HTMLSelectElement>document.getElementById('defaultsSource')).value = 'userTemplate';
(<HTMLSelectElement>document.getElementById('defaultsSource')).value = 'profile';
Focus(document.getElementById('profileSelectBox'));
Unfocus(document.getElementById('defaultUserRadios'));
Unfocus(document.getElementById('newProfileBox'));
document.getElementById('storeDefaults').onclick = (): void => storeDefaults(userIDs);
userDefaultsModal.show();
};

(<HTMLSelectElement>document.getElementById('defaultsSource')).addEventListener('change', function (): void {
const radios = document.getElementById('defaultUserRadios');
if (this.value == 'userTemplate') {
const profileBox = document.getElementById('profileSelectBox');
if (this.value == 'profile') {
Unfocus(radios);
Focus(profileBox);
} else {
Unfocus(profileBox);
Focus(radios);
}
});
Expand Down
22 changes: 10 additions & 12 deletions ts/admin.ts
Expand Up @@ -206,25 +206,23 @@ function storeDefaults(users: string | Array<string>): void {
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="margin-right: 0.5rem;"></span>' +
'Loading...';
const button = document.getElementById('storeDefaults') as HTMLButtonElement;
const radio = document.querySelector('input[name=defaultRadios]:checked') as HTMLInputElement
let id = radio.id.replace("default_", "");
let route = "/setDefaults";
let data = {
"from": "user",
"id": id,
"homescreen": false
};
if ((document.getElementById('defaultsSource') as HTMLSelectElement).value == 'userTemplate') {
data["from"] = "template";
let data = { "homescreen": false };
if ((document.getElementById('defaultsSource') as HTMLSelectElement).value == 'profile') {
data["from"] = "profile";
data["profile"] = (document.getElementById('profileSelect') as HTMLSelectElement).value;
} else {
const radio = document.querySelector('input[name=defaultRadios]:checked') as HTMLInputElement
let id = radio.id.replace("default_", "");
data["from"] = "user";
data["id"] = id;
}
if (users != "all") {
data["apply_to"] = users;
route = "/applySettings";
}
if ((document.getElementById('storeDefaultHomescreen') as HTMLInputElement).checked) {
data["homescreen"] = true;
}
_post(route, data, function (): void {
_post("/applySettings", data, function (): void {
if (this.readyState == 4) {
if (this.status == 200 || this.status == 204) {
button.textContent = "Success";
Expand Down

0 comments on commit 903a61d

Please sign in to comment.