mirror of
https://github.com/hrfee/jellyfin-accounts.git
synced 2024-12-22 09:00:14 +00:00
Fix admin, convert invite form, change readme
This commit is contained in:
parent
d1cd83f5ff
commit
acad3b1853
@ -1,11 +1,13 @@
|
||||
# ![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).
|
||||
* Provides a web interface for creating invite codes, and a simple account creation form
|
||||
* Sends out emails when a user requests a password reset
|
||||
* 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)
|
||||
* 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)
|
||||
## Interface
|
||||
<p align="center">
|
||||
|
@ -208,7 +208,7 @@
|
||||
"no_username": {
|
||||
"name": "Use email addresses as username",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"requires_restart": true,
|
||||
"depends_true": "method",
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
|
@ -27,9 +27,9 @@ function addItem(invite) {
|
||||
listItem.id = invite[0]
|
||||
listItem.classList.add('list-group-item', 'd-flex', 'justify-content-between', 'd-inline-block');
|
||||
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');
|
||||
codeLink.setAttribute('style', 'margin-right: 2%;');
|
||||
codeLink.setAttribute('style', 'margin-right: 0.5rem;');
|
||||
codeLink.appendChild(document.createTextNode(invite[0].replace(/-/g, '‑')));
|
||||
listCode.appendChild(codeLink);
|
||||
listItem.appendChild(listCode);
|
||||
@ -39,7 +39,7 @@ function addItem(invite) {
|
||||
listText.appendChild(document.createTextNode(invite[1]));
|
||||
listRight.appendChild(listText);
|
||||
if (invite[2] == 0) {
|
||||
var inviteCode = window.location.href + 'invite/' + invite[0];
|
||||
var inviteCode = window.location.href.replace('#', '') + 'invite/' + invite[0];
|
||||
codeLink.href = inviteCode;
|
||||
// listCode.appendChild(document.createTextNode(" "));
|
||||
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() {
|
||||
var button = document.getElementById('generateSubmit');
|
||||
button.disabled = true;
|
||||
@ -623,6 +598,13 @@ document.getElementById('openSettings').onclick = 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 tooltips = to_trigger.map(function(el) {
|
||||
return new bootstrap.Tooltip(el);
|
||||
|
25
jellyfin_accounts/data/static/serialize.js
Normal file
25
jellyfin_accounts/data/static/serialize.js
Normal 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;
|
||||
};
|
@ -197,16 +197,14 @@
|
||||
<div class="form-group">
|
||||
<label for="send_to_address">Send invite to address</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<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 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>
|
||||
{% 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>
|
||||
</div>
|
||||
</div>
|
||||
@ -215,6 +213,7 @@
|
||||
<p>{{ contactMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<script src="serialize.js"></script>
|
||||
<script src="admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -12,12 +12,11 @@
|
||||
<meta name="msapplication-TileColor" content="#603cba">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="stylesheet" href="{{ css_href }}" integrity="{{ css_integrity }}" crossorigin="{{ css_crossorigin }}">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" 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/4.3.1/js/bootstrap.min.js"></script>
|
||||
<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>
|
||||
<!-- Bootstrap 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://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<style>
|
||||
.pageContainer {
|
||||
margin: 5% 20% 5% 20%;
|
||||
@ -61,11 +60,11 @@
|
||||
<p class="contactBox">{{ contactMessage }}</p>
|
||||
<div class="container" id="container">
|
||||
<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-header">Details</div>
|
||||
<div class="card-body">
|
||||
<form action="#" method="POST">
|
||||
<form action="#" method="POST" id="accountForm">
|
||||
<div class="form-group">
|
||||
<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>
|
||||
@ -80,7 +79,7 @@
|
||||
<label for="inputPassword">Password</label>
|
||||
<input type="password" class="form-control" id="inputPassword" name="password" placeholder="Password" required>
|
||||
</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">
|
||||
<span id="createAccount">Create Account</span>
|
||||
</button>
|
||||
@ -108,7 +107,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="serialize.js"></script>
|
||||
<script>
|
||||
var successBox = new bootstrap.Modal(document.getElementById('successBox'));
|
||||
var code = window.location.href.split('/').pop();
|
||||
function toggleSpinner () {
|
||||
var submitButton = document.getElementById('submitButton');
|
||||
@ -131,25 +132,24 @@
|
||||
}
|
||||
submitButton.replaceChild(newSpan, oldSpan);
|
||||
};
|
||||
$("form").submit(function() {
|
||||
document.getElementById('accountForm').onsubmit = function() {
|
||||
toggleSpinner();
|
||||
var send = $("form").serializeObject();
|
||||
var send = serializeForm('accountForm');
|
||||
send['code'] = code;
|
||||
{% if not username %}
|
||||
send['email'] = send['username'];
|
||||
{% endif %}
|
||||
send = JSON.stringify(send);
|
||||
$.ajax('/newUser', {
|
||||
data : send,
|
||||
contentType : 'application/json',
|
||||
type : 'POST',
|
||||
crossDomain : true,
|
||||
complete : function(response){
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("POST", "/newUser", true);
|
||||
req.responseType = 'json';
|
||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
req.onreadystatechange = function() {
|
||||
if (this.readyState == 4) {
|
||||
toggleSpinner();
|
||||
var data = response['responseJSON'];
|
||||
var data = this.response;
|
||||
if ('error' in data) {
|
||||
var text = document.createTextNode(data['error']);
|
||||
// <div class="alert alert-danger" id="errorBox"></div>
|
||||
var error = document.createElement('button');
|
||||
error.classList.add('btn', 'btn-outline-danger');
|
||||
error.setAttribute('disabled', '');
|
||||
@ -177,13 +177,14 @@
|
||||
};
|
||||
};
|
||||
if (valid == true) {
|
||||
$('#successBox').modal('show');
|
||||
successBox.show();
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
};
|
||||
req.send(send);
|
||||
return false;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user