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

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.
This commit is contained in:
Harvey Tindall 2020-09-17 16:51:19 +01:00
parent a8b4842895
commit cd61989495
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
12 changed files with 481 additions and 122 deletions

87
api.go
View File

@ -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{}{}

135
data/static/accounts.js Normal file
View File

@ -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');
}
})

View File

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

View File

@ -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">
</ul>
</div>
<div class="linkForm">
<div class="card mb-3">
<div class="card-header">Generate Invite</div>
<div class="card-body">
<form action="#" method="POST" id="inviteForm" class="container">
<div class="row align-items-start">
<div class="col-sm">
<div class="form-group">
<label for="days">Days</label>
<select class="form-control form-select" id="days" name="days">
</select>
</div>
<div class="form-group">
<label for="hours">Hours</label>
<select class="form-control form-select" id="hours" name="hours">
</select>
</div>
<div class="form-group">
<label for="minutes">Minutes</label>
<select class="form-control form-select" id="minutes" name="minutes">
</select>
</div>
</div>
<div class="col">
<div class="form-group">
<label for="multiUseCount">
Multiple uses
</label>
<div class="input-group">
<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">
</div>
<input type="number" class="form-control" name="remaining-uses" id="multiUseCount">
</div>
</div>
<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;'; }">
<label class="form-check-label" for="noUseLimit">
No use limit
</label>
<div id="noLimitWarning" class="form-text" style="display: none;">Warning: Unlimited usage invites pose a risk if published online.</div>
</div>
{{ if .email_enabled }}
<div class="form-group">
<label for="send_to_address">Send invite to address</label>
<div class="input-group">
<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">
</div>
<input type="email" class="form-control" placeholder="example@example.com" id="send_to_address" disabled>
</div>
</div>
{{ end }}
</div>
</div>
<div class="row">
<div class="col">
<div class="form-group d-flex float-right">
<button type="submit" id="generateSubmit" class="btn btn-primary" style="margin-top: 1rem;">
Generate
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div> </div>
<div class="linkForm"> <div id="accountsTab" class="unfocused">
<div class="card mb-3"> <div class="card mb-3 tabGroup">
<div class="card-header">Generate Invite</div> <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"> <div class="card-body">
<form action="#" method="POST" id="inviteForm" class="container"> <table class="table table-hover table-striped table-borderless">
<div class="row align-items-start"> <thead>
<div class="col-sm"> <tr>
<div class="form-group"> <th scope="col"><input class="form-check-input" type="checkbox" value="" id="selectAll"></th>
<label for="days">Days</label> <th scope="col">Username</th>
<select class="form-control form-select" id="days" name="days"> <th scope="col">Email Address</th>
</select> <th scope="col">Last Active</th>
</div> <th scope="col">Admin?</th>
<div class="form-group"> <th scope="col"><i>View settings</i></th>
<label for="hours">Hours</label> </tr>
<select class="form-control form-select" id="hours" name="hours"> </thead>
</select> <tbody id="accountsList">
</div> </tbody>
<div class="form-group"> </table>
<label for="minutes">Minutes</label>
<select class="form-control form-select" id="minutes" name="minutes">
</select>
</div>
</div>
<div class="col">
<div class="form-group">
<label for="multiUseCount">
Multiple uses
</label>
<div class="input-group">
<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">
</div>
<input type="number" class="form-control" name="remaining-uses" id="multiUseCount">
</div>
</div>
<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;'; }">
<label class="form-check-label" for="noUseLimit">
No use limit
</label>
<div id="noLimitWarning" class="form-text" style="display: none;">Warning: Unlimited usage invites pose a risk if published online.</div>
</div>
{{ if .email_enabled }}
<div class="form-group">
<label for="send_to_address">Send invite to address</label>
<div class="input-group">
<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">
</div>
<input type="email" class="form-control" placeholder="example@example.com" id="send_to_address" disabled>
</div>
</div>
{{ end }}
</div>
</div>
<div class="row">
<div class="col">
<div class="form-group d-flex float-right">
<button type="submit" id="generateSubmit" class="btn btn-primary" style="margin-top: 1rem;">
Generate
</button>
</div>
</div>
</div>
</form>
</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>

View File

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

View File

@ -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;
}

View File

@ -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";

View File

@ -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";