mirror of
https://github.com/hrfee/jellyfin-accounts.git
synced 2024-12-22 09:00:14 +00:00
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:
parent
81bb2520ad
commit
ade935da4e
3
.gitignore
vendored
3
.gitignore
vendored
@ -11,7 +11,8 @@ pw-reset/
|
|||||||
jfa/
|
jfa/
|
||||||
colors.txt
|
colors.txt
|
||||||
theme.css
|
theme.css
|
||||||
jellyfin_accounts/data/static/bootstrap-jf.css
|
jellyfin_accounts/__pycache__/
|
||||||
old/
|
old/
|
||||||
.jf-accounts/
|
.jf-accounts/
|
||||||
requirements.txt
|
requirements.txt
|
||||||
|
package-lock.json
|
||||||
|
@ -15,7 +15,7 @@ A basic account management system for [Jellyfin](https://github.com/jellyfin/jel
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/master/images/jfa.gif" width="48%" style="margin-right: 1.5%;" alt="Admin page"></img>
|
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/master/images/admin.png" width="48%" style="margin-right: 1.5%;" alt="Admin page"></img>
|
||||||
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/master/images/create.png" width="48%" style="margin-left: 1.5%;" alt="Account creation page"></img>
|
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/master/images/create.png" width="48%" style="margin-left: 1.5%;" alt="Account creation page"></img>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
__version__ = "0.2.6"
|
__version__ = "0.3.0"
|
||||||
|
|
||||||
import secrets
|
import secrets
|
||||||
import configparser
|
import configparser
|
||||||
@ -35,6 +35,9 @@ parser.add_argument(
|
|||||||
),
|
),
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-i", "--install", help="attempt to install a system service.", action="store_true"
|
||||||
|
)
|
||||||
|
|
||||||
args, leftovers = parser.parse_known_args()
|
args, leftovers = parser.parse_known_args()
|
||||||
|
|
||||||
@ -70,10 +73,11 @@ else:
|
|||||||
temp_config = configparser.RawConfigParser()
|
temp_config = configparser.RawConfigParser()
|
||||||
temp_config.read(config_path)
|
temp_config.read(config_path)
|
||||||
|
|
||||||
|
|
||||||
def create_log(name):
|
def create_log(name):
|
||||||
log = logging.getLogger(name)
|
log = logging.getLogger(name)
|
||||||
handler = logging.StreamHandler(sys.stdout)
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
if temp_config.getboolean('ui', 'debug'):
|
if temp_config.getboolean("ui", "debug"):
|
||||||
log.setLevel(logging.DEBUG)
|
log.setLevel(logging.DEBUG)
|
||||||
handler.setLevel(logging.DEBUG)
|
handler.setLevel(logging.DEBUG)
|
||||||
else:
|
else:
|
||||||
@ -89,6 +93,7 @@ def create_log(name):
|
|||||||
|
|
||||||
log = create_log("main")
|
log = create_log("main")
|
||||||
|
|
||||||
|
|
||||||
def load_config(config_path, data_dir):
|
def load_config(config_path, data_dir):
|
||||||
config = configparser.RawConfigParser()
|
config = configparser.RawConfigParser()
|
||||||
config.read(config_path)
|
config.read(config_path)
|
||||||
@ -139,6 +144,7 @@ def load_config(config_path, data_dir):
|
|||||||
config["jellyfin"]["public_server"] = config["jellyfin"]["server"]
|
config["jellyfin"]["public_server"] = config["jellyfin"]["server"]
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
config = load_config(config_path, data_dir)
|
config = load_config(config_path, data_dir)
|
||||||
|
|
||||||
web_log = create_log("waitress")
|
web_log = create_log("waitress")
|
||||||
@ -180,38 +186,20 @@ data_store = JSONStorage(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def default_css():
|
css_file = "bs5-jf.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()
|
|
||||||
if "custom_css" in config["files"]:
|
if "custom_css" in config["files"]:
|
||||||
if config["files"]["custom_css"] != "":
|
if config["files"]["custom_css"] != "":
|
||||||
try:
|
try:
|
||||||
shutil.copy(
|
css_path = Path(config["files"]["custom_css"])
|
||||||
config["files"]["custom_css"], (local_dir / "static" / "bootstrap.css")
|
shutil.copy(css_path, (local_dir / "static" / css_path.name))
|
||||||
)
|
log.debug('Loaded custom CSS "{css_path.name}"')
|
||||||
log.debug("Loaded custom CSS")
|
css_file = css_path.name
|
||||||
css["href"] = "/bootstrap.css"
|
|
||||||
css["integrity"] = ""
|
|
||||||
css["crossorigin"] = ""
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
log.error(
|
log.error(
|
||||||
f'Custom CSS {config["files"]["custom_css"]} not found, using default.'
|
f'Custom CSS {config["files"]["custom_css"]} not found, using default.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def resp(success=True, code=500):
|
def resp(success=True, code=500):
|
||||||
if success:
|
if success:
|
||||||
r = jsonify({"success": True})
|
r = jsonify({"success": True})
|
||||||
@ -226,7 +214,29 @@ def resp(success=True, code=500):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
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
|
import json
|
||||||
from jellyfin_accounts.jf_api import Jellyfin
|
from jellyfin_accounts.jf_api import Jellyfin
|
||||||
|
|
||||||
|
8
jellyfin_accounts/data/services/jf-accounts.service
Normal file
8
jellyfin_accounts/data/services/jf-accounts.service
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=A basic account management system for Jellyfin.
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart={executable}
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
@ -44,7 +44,7 @@ function addItem(invite) {
|
|||||||
// listCode.appendChild(document.createTextNode(" "));
|
// listCode.appendChild(document.createTextNode(" "));
|
||||||
var codeCopy = document.createElement('i');
|
var codeCopy = document.createElement('i');
|
||||||
codeCopy.onclick = function(){toClipboard(inviteCode)};
|
codeCopy.onclick = function(){toClipboard(inviteCode)};
|
||||||
codeCopy.classList.add('fa', 'fa-clipboard');
|
codeCopy.classList.add('fa', 'fa-clipboard', 'icon-button');
|
||||||
listCode.appendChild(codeCopy);
|
listCode.appendChild(codeCopy);
|
||||||
if (typeof(invite[3]) != 'undefined') {
|
if (typeof(invite[3]) != 'undefined') {
|
||||||
var sentTo = document.createElement('span');
|
var sentTo = document.createElement('span');
|
||||||
@ -145,7 +145,7 @@ function addOptions(le, sel) {
|
|||||||
function toClipboard(str) {
|
function toClipboard(str) {
|
||||||
const el = document.createElement('textarea');
|
const el = document.createElement('textarea');
|
||||||
el.value = str;
|
el.value = str;
|
||||||
el.setAttribute('readonly', '');
|
el.setAttribute('readOnly', '');
|
||||||
el.style.position = 'absolute';
|
el.style.position = 'absolute';
|
||||||
el.style.left = '-9999px';
|
el.style.left = '-9999px';
|
||||||
document.body.appendChild(el);
|
document.body.appendChild(el);
|
||||||
@ -392,30 +392,45 @@ document.getElementById('openUsers').onclick = function () {
|
|||||||
var users = req.response['users'];
|
var users = req.response['users'];
|
||||||
for (var i = 0; i < users.length; i++) {
|
for (var i = 0; i < users.length; i++) {
|
||||||
var user = users[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.id = 'user_' + user['name'];
|
||||||
entry.appendChild(document.createTextNode(user['name']));
|
var label = document.createElement('label');
|
||||||
var address = document.createElement('span');
|
label.classList.add('d-inline-block');
|
||||||
address.setAttribute('style', 'margin-left: 2%; margin-right: 2%; color: grey;');
|
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.classList.add('addressText');
|
||||||
address.id = 'address_' + user['email'];
|
address.id = 'address_' + user['email'];
|
||||||
if (typeof(user['email']) != 'undefined') {
|
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');
|
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() {
|
editButton.onclick = function() {
|
||||||
this.classList.remove('fa', 'fa-edit');
|
this.classList.remove('fa', 'fa-edit');
|
||||||
var input = document.createElement('input');
|
// var input = document.createElement('input');
|
||||||
input.setAttribute('type', 'email');
|
// input.setAttribute('type', 'email');
|
||||||
input.setAttribute('style', 'margin-left: 2%; color: grey;');
|
// input.classList.add('email-input');
|
||||||
var addressElement = this.parentNode.getElementsByClassName('addressText')[0];
|
//var addressElement = this.parentNode.getElementsByClassName('addressText')[0];
|
||||||
if (addressElement.textContent != '') {
|
var addressElement = this.parentNode.getElementsByClassName('form-control-plaintext')[0];
|
||||||
input.value = addressElement.textContent;
|
addressElement.classList.remove('form-control-plaintext', 'text-muted');
|
||||||
} else {
|
addressElement.classList.add('form-control');
|
||||||
input.placeholder = 'Email Address';
|
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) {
|
if (document.getElementById('saveUsers') == null) {
|
||||||
var footer = document.getElementById('userFooter')
|
var footer = document.getElementById('userFooter')
|
||||||
var saveUsers = document.createElement('input');
|
var saveUsers = document.createElement('input');
|
||||||
@ -451,8 +466,8 @@ document.getElementById('openUsers').onclick = function () {
|
|||||||
footer.appendChild(saveUsers);
|
footer.appendChild(saveUsers);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
entry.appendChild(address);
|
|
||||||
entry.appendChild(editButton);
|
entry.appendChild(editButton);
|
||||||
|
entry.appendChild(address);
|
||||||
list.appendChild(entry);
|
list.appendChild(entry);
|
||||||
};
|
};
|
||||||
var button = document.getElementById('openUsers');
|
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
@ -1,35 +1,34 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<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="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="32x32" href="/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||||
<link rel="manifest" href="/site.webmanifest">
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
||||||
<meta name="msapplication-TileColor" content="#603cba">
|
<meta name="msapplication-TileColor" content="#603cba">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
<title>404</title>
|
<title>404</title>
|
||||||
<link rel="stylesheet" href="{{ css_href }}" integrity="{{ css_integrity }}" crossorigin="{{ css_crossorigin }}">
|
<link rel="stylesheet" type="text/css" href="{{ css_file }}">
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
<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/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" 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>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.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://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
|
<style>
|
||||||
<style>
|
.messageBox {
|
||||||
.messageBox {
|
margin: 20%;
|
||||||
margin: 20%;
|
}
|
||||||
}
|
</style>
|
||||||
</style>
|
</head>
|
||||||
</head>
|
<body>
|
||||||
<body>
|
<div class="messageBox">
|
||||||
<div class="messageBox">
|
<h1>Page not found.</h1>
|
||||||
<h1>Page not found.</h1>
|
<p>
|
||||||
<p>
|
{{ contactMessage }}
|
||||||
{{ contactMessage }}
|
</p>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</body>
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<!-- 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://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>
|
<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">
|
<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%;
|
margin-top: 5%;
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
.fa-clipboard {
|
|
||||||
color: grey;
|
|
||||||
}
|
|
||||||
.fa-clipboard:hover {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<title>Admin</title>
|
<title>Admin</title>
|
||||||
</head>
|
</head>
|
||||||
@ -173,13 +167,13 @@
|
|||||||
<button type="button" class="btn btn-secondary" id="openSettings">
|
<button type="button" class="btn btn-secondary" id="openSettings">
|
||||||
Settings <i class="fa fa-cog"></i>
|
Settings <i class="fa fa-cog"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="card bg-light mb-3 linkGroup">
|
<div class="card mb-3 linkGroup">
|
||||||
<div class="card-header">Current Invites</div>
|
<div class="card-header">Current Invites</div>
|
||||||
<ul class="list-group list-group-flush" id="invites">
|
<ul class="list-group list-group-flush" id="invites">
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="linkForm">
|
<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-header">Generate Invite</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="#" method="POST" id="inviteForm">
|
<form action="#" method="POST" id="inviteForm">
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<!-- 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://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>
|
<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">
|
<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="container" id="container">
|
||||||
<div class="row" id="cardContainer">
|
<div class="row" id="cardContainer">
|
||||||
<div class="col-sm">
|
<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-header">Details</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form action="#" method="POST" id="accountForm">
|
<form action="#" method="POST" id="accountForm">
|
||||||
@ -90,7 +90,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if validate %}
|
{% if validate %}
|
||||||
<div class="col-sm" id="requirementBox">
|
<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-header">Password Requirements</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<title>Invalid Code</title>
|
<title>Invalid Code</title>
|
||||||
<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" type="text/css" href="{{ css_file }}">
|
||||||
<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">
|
||||||
<style>
|
<style>
|
||||||
.messageBox {
|
.messageBox {
|
||||||
margin: 20%;
|
margin: 20%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="messageBox">
|
<div class="messageBox">
|
||||||
<h1>Invalid Code.</h1>
|
<h1>Invalid Code.</h1>
|
||||||
<p>The above code is either incorrect, or has expired.</p>
|
<p>The above code is either incorrect, or has expired.</p>
|
||||||
<p>{{ contactMessage }}</p>
|
<p>{{ contactMessage }}</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Flask, send_from_directory, render_template
|
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 import web_log as log
|
||||||
from jellyfin_accounts.web_api import config, checkInvite, validator
|
from jellyfin_accounts.web_api import config, checkInvite, validator
|
||||||
|
|
||||||
@ -11,9 +11,7 @@ def page_not_found(e):
|
|||||||
return (
|
return (
|
||||||
render_template(
|
render_template(
|
||||||
"404.html",
|
"404.html",
|
||||||
css_href=css["href"],
|
css_file=css_file,
|
||||||
css_integrity=css["integrity"],
|
|
||||||
css_crossorigin=css["crossorigin"],
|
|
||||||
contactMessage=config["ui"]["contact_message"],
|
contactMessage=config["ui"]["contact_message"],
|
||||||
),
|
),
|
||||||
404,
|
404,
|
||||||
@ -25,9 +23,7 @@ def admin():
|
|||||||
# return app.send_static_file('admin.html')
|
# return app.send_static_file('admin.html')
|
||||||
return render_template(
|
return render_template(
|
||||||
"admin.html",
|
"admin.html",
|
||||||
css_href=css["href"],
|
css_file=css_file,
|
||||||
css_integrity=css["integrity"],
|
|
||||||
css_crossorigin=css["crossorigin"],
|
|
||||||
contactMessage="",
|
contactMessage="",
|
||||||
email_enabled=config.getboolean("invite_emails", "enabled"),
|
email_enabled=config.getboolean("invite_emails", "enabled"),
|
||||||
)
|
)
|
||||||
@ -40,9 +36,7 @@ def static_proxy(path):
|
|||||||
return (
|
return (
|
||||||
render_template(
|
render_template(
|
||||||
"404.html",
|
"404.html",
|
||||||
css_href=css["href"],
|
css_file=css_file,
|
||||||
css_integrity=css["integrity"],
|
|
||||||
css_crossorigin=css["crossorigin"],
|
|
||||||
contactMessage=config["ui"]["contact_message"],
|
contactMessage=config["ui"]["contact_message"],
|
||||||
),
|
),
|
||||||
404,
|
404,
|
||||||
@ -59,9 +53,7 @@ def inviteProxy(path):
|
|||||||
email = ""
|
email = ""
|
||||||
return render_template(
|
return render_template(
|
||||||
"form.html",
|
"form.html",
|
||||||
css_href=css["href"],
|
css_file=css_file,
|
||||||
css_integrity=css["integrity"],
|
|
||||||
css_crossorigin=css["crossorigin"],
|
|
||||||
contactMessage=config["ui"]["contact_message"],
|
contactMessage=config["ui"]["contact_message"],
|
||||||
helpMessage=config["ui"]["help_message"],
|
helpMessage=config["ui"]["help_message"],
|
||||||
successMessage=config["ui"]["success_message"],
|
successMessage=config["ui"]["success_message"],
|
||||||
@ -77,8 +69,6 @@ def inviteProxy(path):
|
|||||||
log.debug("Attempted use of invalid invite")
|
log.debug("Attempted use of invalid invite")
|
||||||
return render_template(
|
return render_template(
|
||||||
"invalidCode.html",
|
"invalidCode.html",
|
||||||
css_href=css["href"],
|
css_file=css_file,
|
||||||
css_integrity=css["integrity"],
|
|
||||||
css_crossorigin=css["crossorigin"],
|
|
||||||
contactMessage=config["ui"]["contact_message"],
|
contactMessage=config["ui"]["contact_message"],
|
||||||
)
|
)
|
||||||
|
8
jf-accounts.service
Normal file
8
jf-accounts.service
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=A basic account management system for Jellyfin.
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/home/hrfee/.cache/pypoetry/virtualenvs/jellyfin-accounts-r2jcKHws-py3.8/bin/jf-accounts
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
783
package-lock.json
generated
783
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "jellyfin-accounts"
|
name = "jellyfin-accounts"
|
||||||
version = "0.2.6"
|
version = "0.3.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
description = "A simple account management system for Jellyfin"
|
description = "A simple account management system for Jellyfin"
|
||||||
authors = ["Harvey Tindall <harveyltindall@gmail.com>"]
|
authors = ["Harvey Tindall <harveyltindall@gmail.com>"]
|
||||||
@ -9,7 +9,7 @@ homepage = "https://github.com/hrfee/jellyfin-accounts"
|
|||||||
repository = "https://github.com/hrfee/jellyfin-accounts"
|
repository = "https://github.com/hrfee/jellyfin-accounts"
|
||||||
keywords = ["jellyfin", "jf-accounts"]
|
keywords = ["jellyfin", "jf-accounts"]
|
||||||
include = ["jellyfin_accounts/data/*"]
|
include = ["jellyfin_accounts/data/*"]
|
||||||
exclude = ["images/*"]
|
exclude = ["images/*", "scss/*"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
|
15
scss/README.md
Normal file
15
scss/README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
## SCSS
|
||||||
|
|
||||||
|
* `bs5-jf.scss` contains the source for the customizations to bootstrap. To customize the UI, you can make modifications to this file and then compile it.
|
||||||
|
|
||||||
|
Note: assumes that bootstrap is installed in `../node_modules/bootstrap` relative to itself.
|
||||||
|
* Compilation requires a sass compiler of your choice, and `postcss-cli`, `autoprefixer` + `clean-css-cli` from npm.
|
||||||
|
* If you're using `sassc`, run `./compile.sh bs5-jf.scss` in this directory. This will create a .css file, and minified .css file.
|
||||||
|
* For `node-sass`, replace the `sassc` line in `compile.sh` with
|
||||||
|
```
|
||||||
|
node-sass --output-style expanded --precision 6 $1 $css_file
|
||||||
|
```
|
||||||
|
and run as above.
|
||||||
|
* If you're building from source, copy the minified css to `<jf-accounts git directory>/jellyfin_accounts/data/static/bs5-jf.css`.
|
||||||
|
* If you're just customizing your install, set `custom_css` in your config as the path to your minified css.
|
||||||
|
|
9438
scss/bs5-jf.css
Normal file
9438
scss/bs5-jf.css
Normal file
File diff suppressed because one or more lines are too long
7
scss/bs5-jf.min.css
vendored
Normal file
7
scss/bs5-jf.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
scss/bs5-jf.min.css.map
Normal file
1
scss/bs5-jf.min.css.map
Normal file
File diff suppressed because one or more lines are too long
119
scss/bs5-jf.scss
119
scss/bs5-jf.scss
@ -1,27 +1,40 @@
|
|||||||
$jf-blue: rgb(0, 164, 220);
|
$jf-blue: rgb(0, 164, 220);
|
||||||
$jf-blue-hover: rgba(0, 164, 220, 0.2);
|
$jf-blue-hover: rgba(0, 164, 220, 0.2);
|
||||||
$jf-blue-focus: rgb(12, 176, 232);
|
$jf-blue-focus: rgb(12, 176, 232);
|
||||||
|
$jf-blue-light: #4bb3dd;
|
||||||
|
|
||||||
$jf-red: rgb(204, 0, 0);
|
$jf-red: rgb(204, 0, 0);
|
||||||
|
$jf-red-light: #e12026;
|
||||||
|
$jf-yellower: #ffc107;
|
||||||
|
$jf-yellow: #e1b222;
|
||||||
|
$jf-orange: #ff870f;
|
||||||
|
$jf-green: #6fbd45;
|
||||||
|
$jf-green-dark: #008040;
|
||||||
|
|
||||||
|
|
||||||
$jf-black: rgb(16, 16, 16);
|
$jf-black: #101010; // 16 16 16
|
||||||
$jf-gray-90: rgb(32, 32, 32);
|
$jf-gray-90: #202020; // 32 32 32
|
||||||
$jf-gray-80: rgb(36, 36, 36); // jf-card
|
$jf-gray-80: #242424; // jf-card 36 36 36
|
||||||
$jf-gray-70: rgb(41, 41, 41); // jf-input
|
$jf-gray-70: #292929; // jf-input 41 41 41
|
||||||
$jf-gray-60: rgb(48, 48, 48); // jf-button
|
$jf-gray-60: #303030; // jf-button 48 48 48
|
||||||
$jf-gray-50: rgb(56, 56, 56); // jf-button-focus
|
$jf-gray-50: #383838; // jf-button-focus 56 56 56
|
||||||
$jf-text-bold: rgba(255, 255, 255, 0.87);
|
$jf-text-bold: rgba(255, 255, 255, 0.87);
|
||||||
$jf-text-primary: rgba(255, 255, 255, 0.8);
|
$jf-text-primary: rgba(255, 255, 255, 0.8);
|
||||||
$jf-text-secondary: rgb(153, 153, 153);
|
$jf-text-secondary: rgb(153, 153, 153);
|
||||||
|
|
||||||
$theme-colors: (
|
$primary: $jf-blue;
|
||||||
"primary": $jf-blue,
|
$secondary: $jf-gray-50;
|
||||||
"secondary": $jf-gray-50,
|
$success: $jf-green-dark;
|
||||||
"success": $jf-blue-focus,
|
$danger: $jf-red-light;
|
||||||
"danger": $jf-red,
|
$light: $jf-text-primary;
|
||||||
"light": $jf-text-primary,
|
$dark: $jf-gray-90;
|
||||||
"dark": $jf-gray-90
|
$info: $jf-yellow;
|
||||||
);
|
$warning: $jf-yellower;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$enable-gradients: false;
|
||||||
|
$enable-shadows: false;
|
||||||
|
|
||||||
$enable-rounded: false;
|
$enable-rounded: false;
|
||||||
$body-bg: $jf-black;
|
$body-bg: $jf-black;
|
||||||
@ -32,11 +45,14 @@ $component-active-bg: $jf-blue-focus;
|
|||||||
$text-muted: $jf-text-secondary;
|
$text-muted: $jf-text-secondary;
|
||||||
$link-color: $jf-blue-focus;
|
$link-color: $jf-blue-focus;
|
||||||
$btn-link-disabled-color: $jf-text-secondary;
|
$btn-link-disabled-color: $jf-text-secondary;
|
||||||
$input-bg: $jf-gray-70;
|
$input-bg: $jf-gray-90;
|
||||||
$input-color: $jf-text-primary;
|
$input-color: $jf-text-primary;
|
||||||
$input-focus-bg: $jf-gray-60;
|
$input-focus-bg: $jf-gray-60;
|
||||||
$input-focus-border-color: $jf-blue-focus;
|
$input-focus-border-color: $jf-blue-focus;
|
||||||
$input-disabled-bg: $jf-gray-90;
|
$input-disabled-bg: $jf-gray-70;
|
||||||
|
input:disabled {
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
$input-border-color: $jf-gray-60;
|
$input-border-color: $jf-gray-60;
|
||||||
$input-placeholder-color: $text-muted;
|
$input-placeholder-color: $text-muted;
|
||||||
|
|
||||||
@ -45,7 +61,76 @@ $form-check-input-border: $jf-gray-50;
|
|||||||
$form-check-input-checked-color: $jf-blue-focus;
|
$form-check-input-checked-color: $jf-blue-focus;
|
||||||
$form-check-input-checked-bg-color: $jf-blue-hover;
|
$form-check-input-checked-bg-color: $jf-blue-hover;
|
||||||
|
|
||||||
|
$input-group-addon-bg: $input-bg;
|
||||||
|
|
||||||
// roughly @ line 696
|
$form-select-disabled-color: $jf-text-secondary;
|
||||||
|
$form-select-disabled-bg: $input-disabled-bg;
|
||||||
|
$form-select-indicator-color: $jf-gray-50;
|
||||||
|
|
||||||
|
$card-bg: $jf-gray-80;
|
||||||
|
$card-border-color: null;
|
||||||
|
|
||||||
|
$tooltip-color: $jf-text-bold;
|
||||||
|
$tooltip-bg: $jf-gray-50;
|
||||||
|
|
||||||
|
$modal-content-bg: $jf-gray-80;
|
||||||
|
$modal-content-border-color: $jf-gray-50;
|
||||||
|
$modal-header-border-color: null;
|
||||||
|
$modal-footer-border-color: null;
|
||||||
|
|
||||||
|
$list-group-bg: $card-bg;
|
||||||
|
$list-group-border-color: $jf-gray-50;
|
||||||
|
$list-group-hover-bg: $jf-blue-hover;
|
||||||
|
$list-group-active-bg: $jf-blue-focus;
|
||||||
|
$list-group-action-color: $jf-text-primary;
|
||||||
|
$list-group-action-hover-color: $jf-text-bold;
|
||||||
|
$list-group-action-active-color: $jf-text-bold;
|
||||||
|
$list-group-action-active-bg: $jf-blue-focus;
|
||||||
|
|
||||||
|
// idk why but i had to put these above and below the import
|
||||||
|
.list-group-item-danger {
|
||||||
|
color: $jf-text-bold;
|
||||||
|
background-color: $danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item-success {
|
||||||
|
color: $jf-text-bold;
|
||||||
|
background-color: $success;
|
||||||
|
}
|
||||||
|
|
||||||
@import "../node_modules/bootstrap/scss/bootstrap";
|
@import "../node_modules/bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
|
.btn-primary, .btn-outline-primary:hover, .btn-outline-primary:active {
|
||||||
|
color: $jf-text-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
color: $jf-text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close:hover, .close:active {
|
||||||
|
color: $jf-text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button:hover {
|
||||||
|
color: $jf-text-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-bright {
|
||||||
|
color: $jf-text-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item-danger {
|
||||||
|
color: $jf-text-bold;
|
||||||
|
background-color: $danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item-success {
|
||||||
|
color: $jf-text-bold;
|
||||||
|
background-color: $success;
|
||||||
|
}
|
||||||
|
|
||||||
|
10
scss/compile.sh
Executable file
10
scss/compile.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
css_file=$(echo $1 | sed 's/scss/css/g')
|
||||||
|
min_file=$(echo $1 | sed 's/scss/min.css/g')
|
||||||
|
sassc -t expanded -p 6 $1 $css_file
|
||||||
|
echo "Compiled."
|
||||||
|
postcss $css_file --replace --use autoprefixer
|
||||||
|
echo "Prefixed."
|
||||||
|
echo "Written to $css_file."
|
||||||
|
cleancss --level 1 --format breakWith=lf --source-map --source-map-inline-sources --output $min_file $css_file
|
||||||
|
echo "Minified version written to $min_file."
|
Loading…
Reference in New Issue
Block a user