Initial features of accounts tab

It's rough right now, but the accounts tab shows a list of users and
info. Right now the only action available is to apply settings (from
template or another user) to a selection of users. More to come.
pull/20/head
Harvey Tindall 4 years ago
parent a8b4842895
commit cd61989495
No known key found for this signature in database
GPG Key ID: BBC65952848FB1A2

@ -575,22 +575,24 @@ func (app *appContext) SetOmbiDefaults(gc *gin.Context) {
} }
type defaultsReq struct { type defaultsReq struct {
Username string `json:"username"` From string `json:"from"`
Homescreen bool `json:"homescreen"` 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 var req defaultsReq
gc.BindJSON(&req) gc.BindJSON(&req)
app.info.Printf("Getting user defaults from \"%s\"", req.Username) userID := req.ID
user, status, err := app.jf.userByName(req.Username, false) user, status, err := app.jf.userById(userID, false)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get user from Jellyfin: Code %d", status) app.err.Printf("Failed to get user from Jellyfin: Code %d", status)
app.debug.Printf("Error: %s", err) app.debug.Printf("Error: %s", err)
respond(500, "Couldn't get user", gc) respond(500, "Couldn't get user", gc)
return return
} }
userID := user["Id"].(string) app.info.Printf("Getting user defaults from \"%s\"", user["Name"].(string))
policy := user["Policy"].(map[string]interface{}) policy := user["Policy"].(map[string]interface{})
app.storage.policy = policy app.storage.policy = policy
app.storage.storePolicy() app.storage.storePolicy()
@ -615,6 +617,81 @@ func (app *appContext) SetDefaults(gc *gin.Context) {
gc.JSON(200, map[string]bool{"success": true}) gc.JSON(200, map[string]bool{"success": true})
} }
func (app *appContext) ApplySettings(gc *gin.Context) {
var req defaultsReq
gc.BindJSON(&req)
applyingFrom := "template"
var policy, configuration, displayprefs map[string]interface{}
if req.From == "template" {
if len(app.storage.policy) == 0 {
respond(500, "No policy template available", gc)
return
}
app.storage.loadPolicy()
policy = app.storage.policy
if req.Homescreen {
if len(app.storage.configuration) == 0 || len(app.storage.displayprefs) == 0 {
respond(500, "No homescreen template available", gc)
return
}
configuration = app.storage.configuration
displayprefs = app.storage.displayprefs
}
} else if req.From == "user" {
applyingFrom = "user"
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
}
applyingFrom = "\"" + user["Name"].(string) + "\""
policy = user["Policy"].(map[string]interface{})
if req.Homescreen {
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
}
configuration = user["Configuration"].(map[string]interface{})
}
}
app.info.Printf("Applying settings to %d user(s) from %s", len(req.ApplyTo), applyingFrom)
errors := map[string]map[string]string{
"policy": map[string]string{},
"homescreen": map[string]string{},
}
for _, id := range req.ApplyTo {
status, err := app.jf.setPolicy(id, policy)
if !(status == 200 || status == 204) || err != nil {
errors["policy"][id] = fmt.Sprintf("%d: %s", status, err)
}
if req.Homescreen {
status, err = app.jf.setConfiguration(id, configuration)
errorString := ""
if !(status == 200 || status == 204) || err != nil {
errorString += fmt.Sprintf("Configuration %d: %s ", status, err)
} else {
status, err = app.jf.setDisplayPreferences(id, displayprefs)
if !(status == 200 || status == 204) || err != nil {
errorString += fmt.Sprintf("Displayprefs %d: %s ", status, err)
}
}
if errorString != "" {
errors["homescreen"][id] = errorString
}
}
}
code := 200
if len(errors["policy"]) == len(req.ApplyTo) || len(errors["homescreen"]) == len(req.ApplyTo) {
code = 500
}
gc.JSON(code, errors)
}
func (app *appContext) GetConfig(gc *gin.Context) { func (app *appContext) GetConfig(gc *gin.Context) {
app.info.Println("Config requested") app.info.Println("Config requested")
resp := map[string]interface{}{} resp := map[string]interface{}{}

@ -0,0 +1,135 @@
document.getElementById('selectAll').onclick = function() {
const checkboxes = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]');
for (check of checkboxes) {
check.checked = this.checked;
}
checkCheckboxes();
};
function checkCheckboxes() {
const defaultsButton = document.getElementById('accountsTabSetDefaults');
const checkboxes = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]');
let checked = false;
for (check of checkboxes) {
if (check.checked) {
checked = true;
break;
}
}
if (!checked) {
defaultsButton.classList.add('unfocused');
} else if (defaultsButton.classList.contains('unfocused')) {
defaultsButton.classList.remove('unfocused');
}
}
var jfUsers = [];
function populateUsers() {
const acList = document.getElementById('accountsList');
acList.innerHTML = `
<div class="d-flex align-items-center">
<strong>Getting Users...</strong>
<div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
</div>`;
acList.parentNode.querySelector('thead').classList.add('unfocused');
const accountsList = document.createElement('tbody');
accountsList.id = 'accountsList';
const template = function(id, username, email, lastActive, admin) {
let isAdmin = "No";
if (admin) {
isAdmin = "Yes";
}
return `
<td scope="row"><input class="form-check-input" type="checkbox" value="" id="select_${id}" onclick="checkCheckboxes();"></td>
<td>${username}</td>
<td>${email}</td>
<td>${lastActive}</td>
<td>${isAdmin}</td>
<td><i class="fa fa-eye icon-button" id="viewConfig_${id}"></i></td>`;
};
let req = new XMLHttpRequest();
req.responseType = 'json';
req.open("GET", "/getUsers", true);
req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
req.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200) {
jfUsers = req.response['users'];
for (user of jfUsers) {
let tr = document.createElement('tr');
tr.innerHTML = template(user['id'], user['name'], user['email'], user['last_active'], user['admin']);
accountsList.appendChild(tr);
}
const header = acList.parentNode.querySelector('thead');
if (header.classList.contains('unfocused')) {
header.classList.remove('unfocused');
}
acList.replaceWith(accountsList);
}
}
};
req.send();
}
document.getElementById('selectAll').checked = false;
document.getElementById('accountsTabSetDefaults').onclick = function() {
const checkboxes = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]');
let userIDs = [];
for (check of checkboxes) {
if (check.checked) {
userIDs.push(check.id.replace('select_', ''));
}
}
if (userIDs.length == 0) {
return;
}
let radioList = document.getElementById('defaultUserRadios');
radioList.textContent = '';
let first = true;
for (user of jfUsers) {
let radio = document.createElement('div');
radio.classList.add('radio');
let checked = 'checked';
if (first) {
first = false;
} else {
checked = '';
}
radio.innerHTML = `
<label><input type="radio" name="defaultRadios" id="select_${user['id']}" style="margin-right: 1rem;" ${checked}>${user['name']}</label>`;
radioList.appendChild(radio);
}
let userstring = 'user';
if (userIDs.length > 1) {
userstring += 's';
}
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.`;
document.getElementById('storeHomescreenLabel').textContent = `Apply homescreen layout`;
if (document.getElementById('defaultsSourceSection').classList.contains('unfocused')) {
document.getElementById('defaultsSourceSection').classList.remove('unfocused');
}
document.getElementById('defaultsSource').value = 'userTemplate';
document.getElementById('defaultUserRadios').classList.add('unfocused');
document.getElementById('storeDefaults').onclick = function() {
storeDefaults(userIDs);
};
userDefaultsModal.show();
};
document.getElementById('defaultsSource').addEventListener('change', function() {
const radios = document.getElementById('defaultUserRadios');
if (this.value == 'userTemplate') {
radios.classList.add('unfocused');
} else if (radios.classList.contains('unfocused')) {
radios.classList.remove('unfocused');
}
})

@ -1,3 +1,36 @@
const tabs = {
invitesEl: document.getElementById('invitesTab'),
accountsEl: document.getElementById('accountsTab'),
invitesTabButton: document.getElementById('invitesTabButton'),
accountsTabButton: document.getElementById('accountsTabButton'),
invites: function() {
if (tabs.invitesEl.classList.contains('unfocused')) {
tabs.accountsEl.classList.add('unfocused');
tabs.invitesEl.classList.remove('unfocused');
}
if (tabs.invitesTabButton.classList.contains("text-muted")) {
tabs.invitesTabButton.classList.remove("text-muted");
tabs.accountsTabButton.classList.add("text-muted");
}
},
accounts: function() {
populateUsers();
if (tabs.accountsEl.classList.contains('unfocused')) {
tabs.invitesEl.classList.add('unfocused');
tabs.accountsEl.classList.remove('unfocused');
}
if (tabs.accountsTabButton.classList.contains("text-muted")) {
tabs.accountsTabButton.classList.remove("text-muted");
tabs.invitesTabButton.classList.add("text-muted");
}
}
};
tabs.invitesTabButton.onclick = tabs.invites;
tabs.accountsTabButton.onclick = tabs.accounts;
tabs.invites();
// Used for theme change animation // Used for theme change animation
function whichTransitionEvent() { function whichTransitionEvent() {
let t; let t;
@ -638,7 +671,7 @@ document.getElementById('openDefaultsWizard').onclick = function() {
checked = ''; checked = '';
} }
radio.innerHTML = radio.innerHTML =
`<label><input type="radio" name="defaultRadios" id="default_${user['name']}" style="margin-right: 1rem;" ${checked}>${user['name']}</label>`; `<label><input type="radio" name="defaultRadios" id="select_${user['id']}" style="margin-right: 1rem;" ${checked}>${user['name']}</label>`;
radioList.appendChild(radio); radioList.appendChild(radio);
} }
let button = document.getElementById('openDefaultsWizard'); let button = document.getElementById('openDefaultsWizard');
@ -655,6 +688,19 @@ document.getElementById('openDefaultsWizard').onclick = function() {
submitButton.classList.add('btn-primary'); submitButton.classList.add('btn-primary');
} }
settingsModal.hide(); settingsModal.hide();
document.getElementById('defaultsTitle').textContent = `New user defaults`;
document.getElementById('userDefaultsDescription').textContent = `
Create an account and configure it to your liking, then choose it from below to store the settings as a template for all new users.`;
document.getElementById('storeHomescreenLabel').textContent = `Store homescreen layout`;
document.getElementById('defaultsSource').value = 'fromUser';
document.getElementById('defaultsSourceSection').classList.add('unfocused');
document.getElementById('storeDefaults').onclick = function() {
storeDefaults('all');
};
const list = document.getElementById('defaultUserRadios');
if (list.classList.contains('unfocused')) {
list.classList.remove('unfocused');
}
userDefaultsModal.show(); userDefaultsModal.show();
} }
} }
@ -662,54 +708,81 @@ document.getElementById('openDefaultsWizard').onclick = function() {
req.send(); req.send();
}; };
document.getElementById('storeDefaults').onclick = function () { function storeDefaults(users) {
this.disabled = true; this.disabled = true;
this.innerHTML = this.innerHTML =
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="margin-right: 0.5rem;"></span>' + '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="margin-right: 0.5rem;"></span>' +
'Loading...'; 'Loading...';
let button = document.getElementById('storeDefaults'); let button = document.getElementById('storeDefaults');
let radios = document.getElementsByName('defaultRadios'); let radios = document.getElementsByName('defaultRadios');
let id = '';
for (let radio of radios) { for (let radio of radios) {
if (radio.checked) { if (radio.checked) {
let data = { id = radio.id.replace('select_', '');
'username': radio.id.slice(8), break;
'homescreen': false};
if (document.getElementById('storeDefaultHomescreen').checked) {
data['homescreen'] = true;
}
let req = new XMLHttpRequest();
req.open("POST", "/setDefaults", true);
req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
req.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200 || this.status == 204) {
button.textContent = "Success";
if (button.classList.contains('btn-danger')) {
button.classList.remove('btn-danger');
} else if (button.classList.contains('btn-primary')) {
button.classList.remove('btn-primary');
}
button.classList.add('btn-success');
button.disabled = false;
setTimeout(function() { userDefaultsModal.hide(); }, 1000);
} else {
button.textContent = "Failed";
button.classList.remove('btn-primary');
button.classList.add('btn-danger');
setTimeout(function() {
let button = document.getElementById('storeDefaults');
button.textContent = "Submit";
button.classList.remove('btn-danger');
button.classList.add('btn-primary');
button.disabled = false;
}, 1000);
}
}
};
req.send(JSON.stringify(data));
} }
} }
let route = '/setDefaults';
let data = {
'from': 'user',
'id': id,
'homescreen': false
};
if (document.getElementById('defaultsSource').value == 'userTemplate') {
data['from'] = 'template';
}
if (users != 'all') {
data['apply_to'] = users;
route = '/applySettings';
}
if (document.getElementById('storeDefaultHomescreen').checked) {
data['homescreen'] = true;
}
let req = new XMLHttpRequest();
req.open("POST", route, true);
req.responseType = 'json';
req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
req.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200 || this.status == 204) {
button.textContent = "Success";
if (button.classList.contains('btn-danger')) {
button.classList.remove('btn-danger');
} else if (button.classList.contains('btn-primary')) {
button.classList.remove('btn-primary');
}
button.classList.add('btn-success');
button.disabled = false;
setTimeout(function() {
let button = document.getElementById('storeDefaults');
button.textContent = "Submit";
button.classList.remove('btn-success');
button.classList.add('btn-primary');
button.disabled = false;
userDefaultsModal.hide();
}, 1000);
} else {
if ("error" in req.response) {
button.textContent = req.response["error"];
} else if (("policy" in req.response) || ("homescreen" in req.response)) {
button.textContent = "Failed (Check JS Console)";
} else {
button.textContent = "Failed";
}
button.classList.remove('btn-primary');
button.classList.add('btn-danger');
setTimeout(function() {
let button = document.getElementById('storeDefaults');
button.textContent = "Submit";
button.classList.remove('btn-danger');
button.classList.add('btn-primary');
button.disabled = false;
}, 1000);
}
}
};
req.send(JSON.stringify(data));
}; };
var ombiDefaultsModal = ''; var ombiDefaultsModal = '';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -150,16 +150,24 @@
<div class="modal-dialog modal-dialog-centered" role="document"> <div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="defaultsTitle">New user defaults</h5> <h5 class="modal-title" id="defaultsTitle"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Create an account and configure it to your liking, then choose it from below to store the settings as a template for all new users.</p> <p id="userDefaultsDescription"></p>
<div class="mb-3" id="defaultsSourceSection">
<label for="defaultsSource">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="fromUser">Source from existing user</option>
</select>
</div>
<div id="defaultUserRadios"></div> <div id="defaultUserRadios"></div>
<div class="checkbox"> <div class="checkbox">
<label><input type="checkbox" value="" style="margin-right: 1rem;" id="storeDefaultHomescreen" checked>Store homescreen layout</label> <input type="checkbox" value="" style="margin-right: 1rem;" id="storeDefaultHomescreen" checked>
<label for="storeDefaultHomescreen" id="storeHomescreenLabel"></label>
</div> </div>
</div> </div>
<div class="modal-footer" id="defaultsFooter"> <div class="modal-footer" id="defaultsFooter">
@ -240,7 +248,7 @@
</div> </div>
</div> </div>
<div class="pageContainer"> <div class="pageContainer">
<h1><a class="text-button">Invites </a></h1> <h1><a id="invitesTabButton" class="text-button">invites </a><a id="accountsTabButton" class="text-button text-muted">accounts</a></h1>
<div class="btn-group" role="group" id="headerButtons"> <div class="btn-group" role="group" id="headerButtons">
<button type="button" class="btn btn-primary" id="openSettings"> <button type="button" class="btn btn-primary" id="openSettings">
Settings <i class="fa fa-cog"></i> Settings <i class="fa fa-cog"></i>
@ -249,76 +257,105 @@
Logout <i class="fa fa-sign-out"></i> Logout <i class="fa fa-sign-out"></i>
</button> </button>
</div> </div>
<div class="card mb-3 linkGroup"> <div id="invitesTab">
<div class="card-header">Current Invites</div> <div class="card mb-3 tabGroup">
<ul class="list-group list-group-flush" id="invites"> <div class="card-header">Current Invites</div>
</ul> <ul class="list-group list-group-flush" id="invites">
</div> </ul>
<div class="linkForm"> </div>
<div class="card mb-3"> <div class="linkForm">
<div class="card-header">Generate Invite</div> <div class="card mb-3">
<div class="card-body"> <div class="card-header">Generate Invite</div>
<form action="#" method="POST" id="inviteForm" class="container"> <div class="card-body">
<div class="row align-items-start"> <form action="#" method="POST" id="inviteForm" class="container">
<div class="col-sm"> <div class="row align-items-start">
<div class="form-group"> <div class="col-sm">
<label for="days">Days</label> <div class="form-group">
<select class="form-control form-select" id="days" name="days"> <label for="days">Days</label>
</select> <select class="form-control form-select" id="days" name="days">
</div> </select>
<div class="form-group"> </div>
<label for="hours">Hours</label> <div class="form-group">
<select class="form-control form-select" id="hours" name="hours"> <label for="hours">Hours</label>
</select> <select class="form-control form-select" id="hours" name="hours">
</div> </select>
<div class="form-group"> </div>
<label for="minutes">Minutes</label> <div class="form-group">
<select class="form-control form-select" id="minutes" name="minutes"> <label for="minutes">Minutes</label>
</select> <select class="form-control form-select" id="minutes" name="minutes">
</select>
</div>
</div> </div>
</div> <div class="col">
<div class="col"> <div class="form-group">
<div class="form-group"> <label for="multiUseCount">
<label for="multiUseCount"> Multiple uses
Multiple uses </label>
</label> <div class="input-group">
<div class="input-group"> <div class="input-group-text">
<div class="input-group-text"> <input class="form-check-input" type="checkbox" onchange="document.getElementById('multiUseCount').disabled = !this.checked; document.getElementById('noUseLimit').disabled = !this.checked" aria-label="Checkbox to allow choice of invite usage limit" name="multiple-uses" id="multiUseEnabled">
<input class="form-check-input" type="checkbox" onchange="document.getElementById('multiUseCount').disabled = !this.checked; document.getElementById('noUseLimit').disabled = !this.checked" aria-label="Checkbox to allow choice of invite usage limit" name="multiple-uses" id="multiUseEnabled"> </div>
<input type="number" class="form-control" name="remaining-uses" id="multiUseCount">
</div> </div>
<input type="number" class="form-control" name="remaining-uses" id="multiUseCount">
</div> </div>
</div> <div class="form-group form-check" style="margin-top: 1rem; margin-bottom: 1rem;">
<div class="form-group form-check" style="margin-top: 1rem; margin-bottom: 1rem;"> <input class="form-check-input" type="checkbox" value="" name="no-limit" id="noUseLimit" onchange="document.getElementById('multiUseCount').disabled = this.checked; if (this.checked) { document.getElementById('noLimitWarning').style = 'display: block;' } else { document.getElementById('noLimitWarning').style = 'display: none;'; }">
<input class="form-check-input" type="checkbox" value="" name="no-limit" id="noUseLimit" onchange="document.getElementById('multiUseCount').disabled = this.checked; if (this.checked) { document.getElementById('noLimitWarning').style = 'display: block;' } else { document.getElementById('noLimitWarning').style = 'display: none;'; }"> <label class="form-check-label" for="noUseLimit">
<label class="form-check-label" for="noUseLimit"> No use limit
No use limit </label>
</label> <div id="noLimitWarning" class="form-text" style="display: none;">Warning: Unlimited usage invites pose a risk if published online.</div>
<div id="noLimitWarning" class="form-text" style="display: none;">Warning: Unlimited usage invites pose a risk if published online.</div> </div>
</div> {{ if .email_enabled }}
{{ if .email_enabled }} <div class="form-group">
<div class="form-group"> <label for="send_to_address">Send invite to address</label>
<label for="send_to_address">Send invite to address</label> <div class="input-group">
<div class="input-group"> <div class="input-group-text">
<div class="input-group-text"> <input class="form-check-input" type="checkbox" onchange="document.getElementById('send_to_address').disabled = !this.checked;" aria-label="Checkbox to allow input of email address" id="send_to_address_enabled">
<input class="form-check-input" type="checkbox" onchange="document.getElementById('send_to_address').disabled = !this.checked;" aria-label="Checkbox to allow input of email address" id="send_to_address_enabled"> </div>
<input type="email" class="form-control" placeholder="example@example.com" id="send_to_address" disabled>
</div> </div>
<input type="email" class="form-control" placeholder="example@example.com" id="send_to_address" disabled>
</div> </div>
{{ end }}
</div> </div>
{{ end }}
</div> </div>
</div> <div class="row">
<div class="row"> <div class="col">
<div class="col"> <div class="form-group d-flex float-right">
<div class="form-group d-flex float-right"> <button type="submit" id="generateSubmit" class="btn btn-primary" style="margin-top: 1rem;">
<button type="submit" id="generateSubmit" class="btn btn-primary" style="margin-top: 1rem;"> Generate
Generate </button>
</button> </div>
</div> </div>
</div> </div>
</div> </form>
</form> </div>
</div>
</div>
</div>
<div id="accountsTab" class="unfocused">
<div class="card mb-3 tabGroup">
<div class="card-header">
Accounts
<div class="btn-group d-flex float-right" role="group" aria-label="Account control buttons">
<button type="button" class="btn btn-secondary">Add User</button>
<button type="button" class="btn btn-primary unfocused" id="accountsTabSetDefaults">Set Defaults</button>
</div>
</div>
<div class="card-body">
<table class="table table-hover table-striped table-borderless">
<thead>
<tr>
<th scope="col"><input class="form-check-input" type="checkbox" value="" id="selectAll"></th>
<th scope="col">Username</th>
<th scope="col">Email Address</th>
<th scope="col">Last Active</th>
<th scope="col">Admin?</th>
<th scope="col"><i>View settings</i></th>
</tr>
</thead>
<tbody id="accountsList">
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
@ -401,6 +438,7 @@
const notifications_enabled = false; const notifications_enabled = false;
{{ end }} {{ end }}
</script> </script>
<script src="accounts.js"></script>
<script src="admin.js"></script> <script src="admin.js"></script>
</body> </body>
</html> </html>

@ -322,7 +322,7 @@ func start(asDaemon, firstCall bool) {
app.storage.policy_path = app.config.Section("files").Key("user_template").String() app.storage.policy_path = app.config.Section("files").Key("user_template").String()
app.storage.loadPolicy() app.storage.loadPolicy()
// app.storage.configuration_path = filepath.Join(app.data_path, "user_configuration.json") // app.storage.configuration_path = filepath.Join(app.data_path, "user_configuration.json")
app.storage.policy_path = app.config.Section("files").Key("user_configuration").String() app.storage.configuration_path = app.config.Section("files").Key("user_configuration").String()
app.storage.loadConfiguration() app.storage.loadConfiguration()
// app.storage.displayprefs_path = filepath.Join(app.data_path, "user_displayprefs.json") // app.storage.displayprefs_path = filepath.Join(app.data_path, "user_displayprefs.json")
app.storage.displayprefs_path = app.config.Section("files").Key("user_displayprefs").String() app.storage.displayprefs_path = app.config.Section("files").Key("user_displayprefs").String()
@ -442,6 +442,7 @@ func start(asDaemon, firstCall bool) {
api.GET("/getUsers", app.GetUsers) api.GET("/getUsers", app.GetUsers)
api.POST("/modifyUsers", app.ModifyEmails) api.POST("/modifyUsers", app.ModifyEmails)
api.POST("/setDefaults", app.SetDefaults) api.POST("/setDefaults", app.SetDefaults)
api.POST("/applySettings", app.ApplySettings)
api.GET("/getConfig", app.GetConfig) api.GET("/getConfig", app.GetConfig)
api.POST("/modifyConfig", app.ModifyConfig) api.POST("/modifyConfig", app.ModifyConfig)
if app.config.Section("ombi").Key("enabled").MustBool(false) { if app.config.Section("ombi").Key("enabled").MustBool(false) {

@ -10,7 +10,7 @@ h1 {
/*margin: 20%;*/ /*margin: 20%;*/
margin-bottom: 5%; margin-bottom: 5%;
} }
.linkGroup { .tabGroup {
/*margin: 20%;*/ /*margin: 20%;*/
margin-bottom: 5%; margin-bottom: 5%;
margin-top: 5%; margin-top: 5%;
@ -84,6 +84,7 @@ body.modal-open {
font-weight: inherit; font-weight: inherit;
line-height: inherit; line-height: inherit;
font-family: inherit; font-family: inherit;
margin-right: 1rem;
} }
} }
@ -94,3 +95,11 @@ body.modal-open {
.text-button { .text-button {
@extend %scut-link-unstyled; @extend %scut-link-unstyled;
} }
.text-button:hover {
@extend %scut-link-unstyled;
}
.unfocused {
display: none;
}

@ -1,2 +1,15 @@
@import "../../node_modules/bootstrap4/scss/bootstrap"; @import "../../node_modules/bootstrap4/scss/bootstrap";
.icon-button {
color: $text-muted;
}
.icon-button:hover {
color: inherit;
}
.icon-button:active {
color: $text-muted;
}
@import "../base.scss"; @import "../base.scss";

@ -1,2 +1,15 @@
@import "../../node_modules/bootstrap/scss/bootstrap"; @import "../../node_modules/bootstrap/scss/bootstrap";
.icon-button {
color: $text-muted;
}
.icon-button:hover {
color: inherit;
}
.icon-button:active {
color: $text-muted;
}
@import "../base.scss"; @import "../base.scss";

Loading…
Cancel
Save