Fix admin, convert invite form, change readme

This commit is contained in:
Harvey Tindall 2020-07-03 22:22:47 +01:00
parent d1cd83f5ff
commit acad3b1853
6 changed files with 68 additions and 59 deletions

View File

@ -1,11 +1,13 @@
# ![jellyfin-accounts](https://raw.githubusercontent.com/hrfee/jellyfin-accounts/master/images/jellyfin-accounts-banner-wide.svg) # ![jellyfin-accounts](https://raw.githubusercontent.com/hrfee/jellyfin-accounts/master/images/jellyfin-accounts-banner-wide.svg)
**This branch uses the bootstrap 5 alpha, which works well enough for the most part, but can sometimes be a bit glitchy. Also no more jquery.**
A basic account management system for [Jellyfin](https://github.com/jellyfin/jellyfin). A basic account management system for [Jellyfin](https://github.com/jellyfin/jellyfin).
* Provides a web interface for creating invite codes, and a simple account creation form * Provides a web interface for creating invite codes, and a simple account creation form
* Sends out emails when a user requests a password reset * Sends out emails when a user requests a password reset
* Uses a basic python jellyfin API client for communication with the server. * Uses a basic python jellyfin API client for communication with the server.
* Uses [Flask](https://github.com/pallets/flask), [HTTPAuth](https://github.com/miguelgrinberg/Flask-HTTPAuth), [itsdangerous](https://github.com/pallets/itsdangerous), and [Waitress](https://github.com/Pylons/waitress) * Uses [Flask](https://github.com/pallets/flask), [HTTPAuth](https://github.com/miguelgrinberg/Flask-HTTPAuth), [itsdangerous](https://github.com/pallets/itsdangerous), and [Waitress](https://github.com/Pylons/waitress)
* Frontend uses [Bootstrap](https://getbootstrap.com), [jQuery](https://jquery.com) and [jQuery-serialize-object](https://github.com/macek/jquery-serialize-object) * Frontend uses [Bootstrap 5](https://v5.getbootstrap.com)
* Password resets are handled using smtplib, requests, and [jinja](https://github.com/pallets/jinja) * Password resets are handled using smtplib, requests, and [jinja](https://github.com/pallets/jinja)
## Interface ## Interface
<p align="center"> <p align="center">

View File

@ -208,7 +208,7 @@
"no_username": { "no_username": {
"name": "Use email addresses as username", "name": "Use email addresses as username",
"required": false, "required": false,
"requires_restart": false, "requires_restart": true,
"depends_true": "method", "depends_true": "method",
"type": "bool", "type": "bool",
"value": false, "value": false,

View File

@ -27,9 +27,9 @@ function addItem(invite) {
listItem.id = invite[0] listItem.id = invite[0]
listItem.classList.add('list-group-item', 'd-flex', 'justify-content-between', 'd-inline-block'); listItem.classList.add('list-group-item', 'd-flex', 'justify-content-between', 'd-inline-block');
var listCode = document.createElement('div'); var listCode = document.createElement('div');
listCode.classList.add('d-flex', 'align-items-center', 'text-monospace'); listCode.classList.add('d-flex', 'align-items-center', 'font-monospace');
var codeLink = document.createElement('a'); var codeLink = document.createElement('a');
codeLink.setAttribute('style', 'margin-right: 2%;'); codeLink.setAttribute('style', 'margin-right: 0.5rem;');
codeLink.appendChild(document.createTextNode(invite[0].replace(/-/g, ''))); codeLink.appendChild(document.createTextNode(invite[0].replace(/-/g, '')));
listCode.appendChild(codeLink); listCode.appendChild(codeLink);
listItem.appendChild(listCode); listItem.appendChild(listCode);
@ -39,7 +39,7 @@ function addItem(invite) {
listText.appendChild(document.createTextNode(invite[1])); listText.appendChild(document.createTextNode(invite[1]));
listRight.appendChild(listText); listRight.appendChild(listText);
if (invite[2] == 0) { if (invite[2] == 0) {
var inviteCode = window.location.href + 'invite/' + invite[0]; var inviteCode = window.location.href.replace('#', '') + 'invite/' + invite[0];
codeLink.href = inviteCode; codeLink.href = inviteCode;
// listCode.appendChild(document.createTextNode(" ")); // listCode.appendChild(document.createTextNode(" "));
var codeCopy = document.createElement('i'); var codeCopy = document.createElement('i');
@ -162,31 +162,6 @@ function toClipboard(str) {
} }
}; };
function serializeForm(id) {
var form = document.getElementById(id);
var formData = {};
for (var i = 0; i < form.elements.length; i++) {
var el = form.elements[i];
if (el.type != 'submit') {
var name = el.name;
if (name == '') {
name = el.id;
};
switch (el.type) {
case 'checkbox':
formData[name] = el.checked;
break;
case 'text':
case 'password':
case 'select-one':
case 'email':
formData[name] = el.value;
break;
};
};
};
return formData;
};
document.getElementById('inviteForm').onsubmit = function() { document.getElementById('inviteForm').onsubmit = function() {
var button = document.getElementById('generateSubmit'); var button = document.getElementById('generateSubmit');
button.disabled = true; button.disabled = true;
@ -623,6 +598,13 @@ document.getElementById('openSettings').onclick = function () {
}; };
document.getElementById('settingsMenu').addEventListener('shown.bs.modal', function() { document.getElementById('settingsMenu').addEventListener('shown.bs.modal', function() {
// Hack to ensure anything dependent on checkboxes are disabled if necessary
var checkboxes = document.getElementById('settingsMenu').querySelectorAll('input[type="checkbox"]');
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].click();
checkboxes[i].click();
};
// Initialize tooltips
var to_trigger = [].slice.call(document.querySelectorAll('a[data-toggle="tooltip"]')); var to_trigger = [].slice.call(document.querySelectorAll('a[data-toggle="tooltip"]'));
var tooltips = to_trigger.map(function(el) { var tooltips = to_trigger.map(function(el) {
return new bootstrap.Tooltip(el); return new bootstrap.Tooltip(el);

View File

@ -0,0 +1,25 @@
function serializeForm(id) {
var form = document.getElementById(id);
var formData = {};
for (var i = 0; i < form.elements.length; i++) {
var el = form.elements[i];
if (el.type != 'submit') {
var name = el.name;
if (name == '') {
name = el.id;
};
switch (el.type) {
case 'checkbox':
formData[name] = el.checked;
break;
case 'text':
case 'password':
case 'select-one':
case 'email':
formData[name] = el.value;
break;
};
};
};
return formData;
};

View File

@ -197,16 +197,14 @@
<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-prepend"> <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 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>
</div> </div>
<input type="email" class="form-control" placeholder="example@example.com" id="send_to_address" disabled> <input type="email" class="form-control" placeholder="example@example.com" id="send_to_address" disabled>
</div> </div>
</div> </div>
{% endif %} {% endif %}
<button type="submit" id="generateSubmit" class="btn btn-primary">Generate</button> <button type="submit" id="generateSubmit" class="btn btn-primary" style="margin-top: 1rem;">Generate</button>
</form> </form>
</div> </div>
</div> </div>
@ -215,6 +213,7 @@
<p>{{ contactMessage }}</p> <p>{{ contactMessage }}</p>
</div> </div>
</div> </div>
<script src="serialize.js"></script>
<script src="admin.js"></script> <script src="admin.js"></script>
</body> </body>
</html> </html>

View File

@ -12,12 +12,11 @@
<meta name="msapplication-TileColor" content="#603cba"> <meta name="msapplication-TileColor" content="#603cba">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="{{ css_href }}" integrity="{{ css_integrity }}" crossorigin="{{ css_crossorigin }}"> <!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-serialize-object/2.5.0/jquery.serialize-object.min.js" integrity="sha256-E8KRdFk/LTaaCBoQIV/rFNc0s3ICQQiOHFT4Cioifa8=" crossorigin="anonymous"></script>
<style> <style>
.pageContainer { .pageContainer {
margin: 5% 20% 5% 20%; margin: 5% 20% 5% 20%;
@ -61,11 +60,11 @@
<p class="contactBox">{{ contactMessage }}</p> <p class="contactBox">{{ contactMessage }}</p>
<div class="container" id="container"> <div class="container" id="container">
<div class="row" id="cardContainer"> <div class="row" id="cardContainer">
<div class="col-sm" id="accountForm"> <div class="col-sm">
<div class="card bg-light mb-3"> <div class="card bg-light mb-3">
<div class="card-header">Details</div> <div class="card-header">Details</div>
<div class="card-body"> <div class="card-body">
<form action="#" method="POST"> <form action="#" method="POST" id="accountForm">
<div class="form-group"> <div class="form-group">
<label for="inputEmail">Email</label> <label for="inputEmail">Email</label>
<input type="email" class="form-control" id="{% if username %}inputEmail{% else %}inputUsername{% endif %}" name="{% if username %}email{% else %}username{% endif %}" placeholder="Email" value="{{ email }}" required> <input type="email" class="form-control" id="{% if username %}inputEmail{% else %}inputUsername{% endif %}" name="{% if username %}email{% else %}username{% endif %}" placeholder="Email" value="{{ email }}" required>
@ -80,7 +79,7 @@
<label for="inputPassword">Password</label> <label for="inputPassword">Password</label>
<input type="password" class="form-control" id="inputPassword" name="password" placeholder="Password" required> <input type="password" class="form-control" id="inputPassword" name="password" placeholder="Password" required>
</div> </div>
<div class="btn-group" role="group" aria-label="Button & Error" id="errorBox"> <div class="btn-group" role="group" aria-label="Button & Error" id="errorBox" style="margin-top: 1rem;">
<button type="submit" class="btn btn-outline-primary" id="submitButton"> <button type="submit" class="btn btn-outline-primary" id="submitButton">
<span id="createAccount">Create Account</span> <span id="createAccount">Create Account</span>
</button> </button>
@ -108,7 +107,9 @@
</div> </div>
</div> </div>
</div> </div>
<script src="serialize.js"></script>
<script> <script>
var successBox = new bootstrap.Modal(document.getElementById('successBox'));
var code = window.location.href.split('/').pop(); var code = window.location.href.split('/').pop();
function toggleSpinner () { function toggleSpinner () {
var submitButton = document.getElementById('submitButton'); var submitButton = document.getElementById('submitButton');
@ -131,25 +132,24 @@
} }
submitButton.replaceChild(newSpan, oldSpan); submitButton.replaceChild(newSpan, oldSpan);
}; };
$("form").submit(function() { document.getElementById('accountForm').onsubmit = function() {
toggleSpinner(); toggleSpinner();
var send = $("form").serializeObject(); var send = serializeForm('accountForm');
send['code'] = code; send['code'] = code;
{% if not username %} {% if not username %}
send['email'] = send['username']; send['email'] = send['username'];
{% endif %} {% endif %}
send = JSON.stringify(send); send = JSON.stringify(send);
$.ajax('/newUser', { var req = new XMLHttpRequest();
data : send, req.open("POST", "/newUser", true);
contentType : 'application/json', req.responseType = 'json';
type : 'POST', req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
crossDomain : true, req.onreadystatechange = function() {
complete : function(response){ if (this.readyState == 4) {
toggleSpinner(); toggleSpinner();
var data = response['responseJSON']; var data = this.response;
if ('error' in data) { if ('error' in data) {
var text = document.createTextNode(data['error']); var text = document.createTextNode(data['error']);
// <div class="alert alert-danger" id="errorBox"></div>
var error = document.createElement('button'); var error = document.createElement('button');
error.classList.add('btn', 'btn-outline-danger'); error.classList.add('btn', 'btn-outline-danger');
error.setAttribute('disabled', ''); error.setAttribute('disabled', '');
@ -177,13 +177,14 @@
}; };
}; };
if (valid == true) { if (valid == true) {
$('#successBox').modal('show'); successBox.show();
}; };
} };
} };
}); };
req.send(send);
return false; return false;
}); };
</script> </script>
</body> </body>
</html> </html>