jellyfin-lookin theme, changes from master, bump to 0.3.0

Now uses a customized bootstrap that looks something like Jellyfin. Some
small ui changes were needed. This be overridden by downloading bs5's css and using the custom_css
option if you don't like it. sass file is included for your own modification. Changes made to master have been added also.
This commit is contained in:
2020-07-04 22:17:49 +01:00
parent 81bb2520ad
commit ade935da4e
21 changed files with 10512 additions and 6771 deletions

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python3
__version__ = "0.2.6"
__version__ = "0.3.0"
import secrets
import configparser
@@ -35,6 +35,9 @@ parser.add_argument(
),
action="store_true",
)
parser.add_argument(
"-i", "--install", help="attempt to install a system service.", action="store_true"
)
args, leftovers = parser.parse_known_args()
@@ -70,10 +73,11 @@ else:
temp_config = configparser.RawConfigParser()
temp_config.read(config_path)
def create_log(name):
log = logging.getLogger(name)
handler = logging.StreamHandler(sys.stdout)
if temp_config.getboolean('ui', 'debug'):
if temp_config.getboolean("ui", "debug"):
log.setLevel(logging.DEBUG)
handler.setLevel(logging.DEBUG)
else:
@@ -89,6 +93,7 @@ def create_log(name):
log = create_log("main")
def load_config(config_path, data_dir):
config = configparser.RawConfigParser()
config.read(config_path)
@@ -139,6 +144,7 @@ def load_config(config_path, data_dir):
config["jellyfin"]["public_server"] = config["jellyfin"]["server"]
return config
config = load_config(config_path, data_dir)
web_log = create_log("waitress")
@@ -180,38 +186,20 @@ data_store = JSONStorage(
)
def default_css():
css = {}
css[
"href"
] = "https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
css[
"integrity"
] = "sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
css["crossorigin"] = "anonymous"
return css
css = {}
css = default_css()
css_file = "bs5-jf.css"
if "custom_css" in config["files"]:
if config["files"]["custom_css"] != "":
try:
shutil.copy(
config["files"]["custom_css"], (local_dir / "static" / "bootstrap.css")
)
log.debug("Loaded custom CSS")
css["href"] = "/bootstrap.css"
css["integrity"] = ""
css["crossorigin"] = ""
css_path = Path(config["files"]["custom_css"])
shutil.copy(css_path, (local_dir / "static" / css_path.name))
log.debug('Loaded custom CSS "{css_path.name}"')
css_file = css_path.name
except FileNotFoundError:
log.error(
f'Custom CSS {config["files"]["custom_css"]} not found, using default.'
)
def resp(success=True, code=500):
if success:
r = jsonify({"success": True})
@@ -226,7 +214,29 @@ def resp(success=True, code=500):
def main():
if args.get_defaults:
if args.install:
executable = sys.argv[0]
print(f'Assuming executable path "{executable}".')
options = ["systemd"]
for i, opt in enumerate(options):
print(f"{i+1}: {opt}")
success = False
while not success:
try:
method = options[int(input(">: ")) - 1]
success = True
except IndexError:
pass
if method == "systemd":
with open(local_dir / "services" / "jf-accounts.service", "r") as f:
data = f.read()
data = data.replace("{executable}", executable)
service_path = str(Path("jf-accounts.service").resolve())
with open(service_path, "w") as f:
f.write(data)
print(f"service written to the current directory\n({service_path}).")
print("Place this in the appropriate directory, and reload daemons.")
elif args.get_defaults:
import json
from jellyfin_accounts.jf_api import Jellyfin

View File

@@ -0,0 +1,8 @@
[Unit]
Description=A basic account management system for Jellyfin.
[Service]
ExecStart={executable}
[Install]
WantedBy=default.target

View File

@@ -44,7 +44,7 @@ function addItem(invite) {
// listCode.appendChild(document.createTextNode(" "));
var codeCopy = document.createElement('i');
codeCopy.onclick = function(){toClipboard(inviteCode)};
codeCopy.classList.add('fa', 'fa-clipboard');
codeCopy.classList.add('fa', 'fa-clipboard', 'icon-button');
listCode.appendChild(codeCopy);
if (typeof(invite[3]) != 'undefined') {
var sentTo = document.createElement('span');
@@ -145,7 +145,7 @@ function addOptions(le, sel) {
function toClipboard(str) {
const el = document.createElement('textarea');
el.value = str;
el.setAttribute('readonly', '');
el.setAttribute('readOnly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
@@ -392,30 +392,45 @@ document.getElementById('openUsers').onclick = function () {
var users = req.response['users'];
for (var i = 0; i < users.length; i++) {
var user = users[i]
var entry = document.createElement('p');
var entry = document.createElement('div');
entry.classList.add('form-group', 'list-group-item', 'py-1');
entry.id = 'user_' + user['name'];
entry.appendChild(document.createTextNode(user['name']));
var address = document.createElement('span');
address.setAttribute('style', 'margin-left: 2%; margin-right: 2%; color: grey;');
var label = document.createElement('label');
label.classList.add('d-inline-block');
label.setAttribute('for', 'address_' + user['email']);
label.appendChild(document.createTextNode(user['name']));
entry.appendChild(label);
var address = document.createElement('input');
address.setAttribute('type', 'email');
address.readOnly = true;
address.classList.add('form-control-plaintext', 'text-muted', 'd-inline-block');
//address.setAttribute('style', 'margin-left: 2%; margin-right: 2%; color: grey;');
address.classList.add('addressText');
address.id = 'address_' + user['email'];
if (typeof(user['email']) != 'undefined') {
address.appendChild(document.createTextNode(user['email']));
address.value = user['email'];
address.setAttribute('style', 'width: auto; margin-left: 2%;');
};
var editButton = document.createElement('i');
editButton.classList.add('fa', 'fa-edit');
editButton.classList.add('fa', 'fa-edit', 'd-inline-block', 'icon-button');
editButton.setAttribute('style', 'margin-left: 2%;');
editButton.onclick = function() {
this.classList.remove('fa', 'fa-edit');
var input = document.createElement('input');
input.setAttribute('type', 'email');
input.setAttribute('style', 'margin-left: 2%; color: grey;');
var addressElement = this.parentNode.getElementsByClassName('addressText')[0];
if (addressElement.textContent != '') {
input.value = addressElement.textContent;
} else {
input.placeholder = 'Email Address';
// var input = document.createElement('input');
// input.setAttribute('type', 'email');
// input.classList.add('email-input');
//var addressElement = this.parentNode.getElementsByClassName('addressText')[0];
var addressElement = this.parentNode.getElementsByClassName('form-control-plaintext')[0];
addressElement.classList.remove('form-control-plaintext', 'text-muted');
addressElement.classList.add('form-control');
addressElement.readOnly = false;
if (addressElement.value == '') {
// input.value = addressElement.textContent;
// } else {
addressElement.placeholder = 'Email Address';
address.setAttribute('style', 'width: auto; margin-left: 2%;');
};
this.parentNode.replaceChild(input, addressElement);
// this.parentNode.replaceChild(input, addressElement);
if (document.getElementById('saveUsers') == null) {
var footer = document.getElementById('userFooter')
var saveUsers = document.createElement('input');
@@ -451,8 +466,8 @@ document.getElementById('openUsers').onclick = function () {
footer.appendChild(saveUsers);
};
};
entry.appendChild(address);
entry.appendChild(editButton);
entry.appendChild(address);
list.appendChild(entry);
};
var button = document.getElementById('openUsers');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,35 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#603cba">
<meta name="theme-color" content="#ffffff">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#603cba">
<meta name="theme-color" content="#ffffff">
<title>404</title>
<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>
<style>
.messageBox {
margin: 20%;
}
</style>
</head>
<body>
<div class="messageBox">
<h1>Page not found.</h1>
<p>
{{ contactMessage }}
</p>
</div>
</body>
<title>404</title>
<link rel="stylesheet" type="text/css" href="{{ css_file }}">
<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>
.messageBox {
margin: 20%;
}
</style>
</head>
<body>
<div class="messageBox">
<h1>Page not found.</h1>
<p>
{{ contactMessage }}
</p>
</div>
</body>
</html>

View File

@@ -14,7 +14,7 @@
<meta name="theme-color" content="#ffffff">
<!-- Bootstrap CSS -->
<link rel="stylesheet" type="text/css" href="bs5-jf.css">
<link rel="stylesheet" type="text/css" href="{{ css_file }}">
<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">
@@ -46,12 +46,6 @@
margin-top: 5%;
color: grey;
}
.fa-clipboard {
color: grey;
}
.fa-clipboard:hover {
color: black;
}
</style>
<title>Admin</title>
</head>
@@ -173,13 +167,13 @@
<button type="button" class="btn btn-secondary" id="openSettings">
Settings <i class="fa fa-cog"></i>
</button>
<div class="card bg-light mb-3 linkGroup">
<div class="card mb-3 linkGroup">
<div class="card-header">Current Invites</div>
<ul class="list-group list-group-flush" id="invites">
</ul>
</div>
<div class="linkForm">
<div class="card bg-light mb-3">
<div class="card mb-3">
<div class="card-header">Generate Invite</div>
<div class="card-body">
<form action="#" method="POST" id="inviteForm">

View File

@@ -13,7 +13,7 @@
<meta name="theme-color" content="#ffffff">
<!-- 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">
<link rel="stylesheet" type="text/css" href="{{ css_file }}">
<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">
@@ -61,7 +61,7 @@
<div class="container" id="container">
<div class="row" id="cardContainer">
<div class="col-sm">
<div class="card bg-light mb-3">
<div class="card mb-3">
<div class="card-header">Details</div>
<div class="card-body">
<form action="#" method="POST" id="accountForm">
@@ -90,7 +90,7 @@
</div>
{% if validate %}
<div class="col-sm" id="requirementBox">
<div class="card bg-light mb-3 requirementBox">
<div class="card mb-3 requirementBox">
<div class="card-header">Password Requirements</div>
<div class="card-body">
<ul class="list-group">

View File

@@ -1,25 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Invalid Code</title>
<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>
<style>
.messageBox {
margin: 20%;
}
</style>
</head>
<body>
<div class="messageBox">
<h1>Invalid Code.</h1>
<p>The above code is either incorrect, or has expired.</p>
<p>{{ contactMessage }}</p>
</div>
</body>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Invalid Code</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" type="text/css" href="{{ css_file }}">
<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>
.messageBox {
margin: 20%;
}
</style>
</head>
<body>
<div class="messageBox">
<h1>Invalid Code.</h1>
<p>The above code is either incorrect, or has expired.</p>
<p>{{ contactMessage }}</p>
</div>
</body>
</html>

View File

@@ -1,7 +1,7 @@
from pathlib import Path
from flask import Flask, send_from_directory, render_template
from jellyfin_accounts import app, g, css, data_store
from jellyfin_accounts import app, g, css_file, data_store
from jellyfin_accounts import web_log as log
from jellyfin_accounts.web_api import config, checkInvite, validator
@@ -11,9 +11,7 @@ def page_not_found(e):
return (
render_template(
"404.html",
css_href=css["href"],
css_integrity=css["integrity"],
css_crossorigin=css["crossorigin"],
css_file=css_file,
contactMessage=config["ui"]["contact_message"],
),
404,
@@ -25,9 +23,7 @@ def admin():
# return app.send_static_file('admin.html')
return render_template(
"admin.html",
css_href=css["href"],
css_integrity=css["integrity"],
css_crossorigin=css["crossorigin"],
css_file=css_file,
contactMessage="",
email_enabled=config.getboolean("invite_emails", "enabled"),
)
@@ -40,9 +36,7 @@ def static_proxy(path):
return (
render_template(
"404.html",
css_href=css["href"],
css_integrity=css["integrity"],
css_crossorigin=css["crossorigin"],
css_file=css_file,
contactMessage=config["ui"]["contact_message"],
),
404,
@@ -59,9 +53,7 @@ def inviteProxy(path):
email = ""
return render_template(
"form.html",
css_href=css["href"],
css_integrity=css["integrity"],
css_crossorigin=css["crossorigin"],
css_file=css_file,
contactMessage=config["ui"]["contact_message"],
helpMessage=config["ui"]["help_message"],
successMessage=config["ui"]["success_message"],
@@ -77,8 +69,6 @@ def inviteProxy(path):
log.debug("Attempted use of invalid invite")
return render_template(
"invalidCode.html",
css_href=css["href"],
css_integrity=css["integrity"],
css_crossorigin=css["crossorigin"],
css_file=css_file,
contactMessage=config["ui"]["contact_message"],
)