mirror of
				https://github.com/hrfee/jellyfin-accounts.git
				synced 2025-10-31 01:59:34 +00:00 
			
		
		
		
	Fix admin, convert invite form, change readme
This commit is contained in:
		
							parent
							
								
									d1cd83f5ff
								
							
						
					
					
						commit
						acad3b1853
					
				| @ -1,11 +1,13 @@ | ||||
| #  | ||||
| 
 | ||||
| **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