mirror of
https://github.com/hrfee/jellyfin-accounts.git
synced 2024-12-22 17:10:11 +00:00
Merge pull request #25 from hrfee/dynamic-settings
Full settings added to web UI
This commit is contained in:
commit
46751e3743
6
.gitignore
vendored
6
.gitignore
vendored
@ -4,11 +4,13 @@ MANIFEST.in
|
|||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
test.txt
|
test.txt
|
||||||
data/node_modules/
|
jellyfin_accounts/data/node_modules/
|
||||||
|
jellyfin_accounts/data/config-default.ini
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
pw-reset/
|
pw-reset/
|
||||||
jfa/
|
jfa/
|
||||||
colors.txt
|
colors.txt
|
||||||
theme.css
|
theme.css
|
||||||
data/static/bootstrap-jf.css
|
jellyfin_accounts/data/static/bootstrap-jf.css
|
||||||
old/
|
old/
|
||||||
|
.jf-accounts/
|
||||||
|
133
README.md
133
README.md
@ -68,146 +68,23 @@ optional arguments:
|
|||||||
## Setup
|
## Setup
|
||||||
#### New user template
|
#### New user template
|
||||||
* You may want to restrict a user from accessing certain libraries (e.g 4K Movies), display their account on the login screen by default, or set a default homecrseen layout. Jellyfin stores these settings in the user's policy, configuration and displayPreferences.
|
* You may want to restrict a user from accessing certain libraries (e.g 4K Movies), display their account on the login screen by default, or set a default homecrseen layout. Jellyfin stores these settings in the user's policy, configuration and displayPreferences.
|
||||||
* Make a temporary account and change its settings, then run `jf-accounts --get_defaults`. Choose your user, and this data will be stored at the location you set in `user_template`, `user_configuration` and `user_displayprefs` (or their default locations), and used for all subsequent new accounts.
|
* Make a temporary account and configure it, then in the web UI, go into "Settings => Set new account defaults". Choose the account, and its configuration will be stored for future use.
|
||||||
#### Emails/Password Resets
|
#### Emails/Password Resets
|
||||||
* When someone initiates forget password on Jellyfin, a file named `passwordreset*.json` is created in its configuration directory. This directory is monitored and when created, the program reads the username, expiry time and PIN, puts it into a template and sends it to whatever address is specified in `emails.json`.
|
* When someone initiates forget password on Jellyfin, a file named `passwordreset*.json` is created in its configuration directory. This directory is monitored and when created, the program reads the username, expiry time and PIN, puts it into a template and sends it to whatever address is specified in `emails.json`.
|
||||||
* **The default forget password popup references the `passwordreset*.json` file created. This is confusing for users, so a quick fix is to edit the `MessageForgotPasswordFileCreated` string in Jellyfin's language folder.**
|
* **The default forget password popup references the `passwordreset*.json` file created. This is confusing for users, so a quick fix is to edit the `MessageForgotPasswordFileCreated` string in Jellyfin's language folder.**
|
||||||
* Currently, jellyfin-accounts supports generic SSL/TLS or STARTTLS secured SMTP, and the [mailgun](https://mailgun.com) REST API.
|
* Currently, jellyfin-accounts supports generic SSL/TLS or STARTTLS secured SMTP, and the [mailgun](https://mailgun.com) REST API.
|
||||||
* Email html is created using [mjml](https://mjml.io), and [jinja](https://github.com/pallets/jinja) templating is used. If you wish to create your own, ensure you use the same jinja expressions (`{{ pin }}`, etc.) as used in `data/email.mjml` or `invite-email.mjml`, and also create plain text versions for legacy email clients.
|
* Email html is created using [mjml](https://mjml.io), and [jinja](https://github.com/pallets/jinja) templating is used. If you wish to create your own, ensure you use the same jinja expressions (`{{ pin }}`, etc.) as used in `data/email.mjml` or `invite-email.mjml`, and also create plain text versions for legacy email clients.
|
||||||
|
|
||||||
### Donations
|
|
||||||
I strongly suggest you send your money to [Jellyfin](https://opencollective.com/jellyfin) or a good charity, but for those who want to help me out, a Paypal link is below.
|
|
||||||
|
|
||||||
[Donate](https://www.paypal.me/hrfee)
|
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
* Note: Make sure to put this behind a reverse proxy with HTTPS.
|
* Note: Make sure to put this behind a reverse proxy with HTTPS.
|
||||||
|
|
||||||
On first run, access the setup wizard at `0.0.0.0:8056`. When finished, restart the program.
|
On first run, access the setup wizard at `0.0.0.0:8056`. When finished, restart the program.
|
||||||
|
|
||||||
The configuration is stored at `~/.jf-accounts/config.ini`.
|
The configuration is stored at `~/.jf-accounts/config.ini`. Settings can be changed through the web UI, or by manually editing the file.
|
||||||
|
|
||||||
For detailed descriptions of each setting, see [setup](https://github.com/hrfee/jellyfin-accounts/wiki/Setup).
|
For detailed descriptions of each setting, see [setup](https://github.com/hrfee/jellyfin-accounts/wiki/Setup).
|
||||||
|
|
||||||
|
### Donations
|
||||||
|
I strongly suggest you send your money to [Jellyfin](https://opencollective.com/jellyfin) or a good charity, but for those who want to help me out, a Paypal link is below.
|
||||||
|
|
||||||
```
|
[Donate](https://www.paypal.me/hrfee)
|
||||||
[jellyfin]
|
|
||||||
; It is reccommended to create a limited admin account for this program.
|
|
||||||
username = username
|
|
||||||
password = password
|
|
||||||
; Jellyfin server address. Can be public, or local for security purposes.
|
|
||||||
server = http://jellyfin.local:8096
|
|
||||||
; Publicly accessible Jellyfin address, used on invite form.
|
|
||||||
; Leave blank to use the same address as above.
|
|
||||||
public_server = https://jellyf.in:443
|
|
||||||
client = jf-accounts
|
|
||||||
version = 0.1
|
|
||||||
device = jf-accounts
|
|
||||||
device_id = jf-accounts-0.1
|
|
||||||
|
|
||||||
[ui]
|
|
||||||
; Set 0.0.0.0 to run localhost
|
|
||||||
host = 0.0.0.0
|
|
||||||
port = 8056
|
|
||||||
; Enable this to use Jellyfin users instead of the below username and pw.
|
|
||||||
jellyfin_login = true
|
|
||||||
; Allows only admin users on Jellyfin to access admin page.
|
|
||||||
admin_only = true
|
|
||||||
; Username to use on admin page... (leave blank if using jellyfin_login)
|
|
||||||
username = your username
|
|
||||||
; ..and its corresponding password (leave blank if using jellyfin_login)
|
|
||||||
password = your password
|
|
||||||
|
|
||||||
debug = false
|
|
||||||
|
|
||||||
; Displayed at the bottom of all pages except admin
|
|
||||||
contact_message = Need help? contact me.
|
|
||||||
; Displayed at top of form page.
|
|
||||||
help_message = Enter your details to create an account.
|
|
||||||
; Displayed when an account is created.
|
|
||||||
success_message = Your account has been created. Click below to continue to Jellyfin.
|
|
||||||
|
|
||||||
[password_validation]
|
|
||||||
; Enables password validation.
|
|
||||||
enabled = true
|
|
||||||
; Min. password length
|
|
||||||
min_length = 8
|
|
||||||
; Min. number of uppercase characters
|
|
||||||
upper = 1
|
|
||||||
; Min. number of lowercase characters
|
|
||||||
lower = 0
|
|
||||||
; Min. number of numbers
|
|
||||||
number = 1
|
|
||||||
; Min. number of special characters
|
|
||||||
special = 0
|
|
||||||
|
|
||||||
[email]
|
|
||||||
; When true, disables username input on invite form and sets the Jellyfin username to the email address
|
|
||||||
no_username = false
|
|
||||||
; Leave the rest of this section if you aren't using any email-related features.
|
|
||||||
use_24h = true
|
|
||||||
; Date format follows datetime's strftime.
|
|
||||||
date_format = %d/%m/%y
|
|
||||||
; Displayed at bottom of emails
|
|
||||||
message = Need help? contact me.
|
|
||||||
; Mail methods: mailgun, smtp
|
|
||||||
method = smtp
|
|
||||||
; Address to send from
|
|
||||||
address = jellyfin@jellyf.in
|
|
||||||
; The name of the sender
|
|
||||||
from = Jellyfin
|
|
||||||
|
|
||||||
[password_resets]
|
|
||||||
; Enable to store provided email addresses, monitor jellyfin directory for pw-resets, and send pin
|
|
||||||
enabled = true
|
|
||||||
; Directory to monitor for passwordReset*.json files. Usually the jellyfin config directory
|
|
||||||
watch_directory = /path/to/jellyfin
|
|
||||||
; Path to custom email html. If blank, uses the internal template.
|
|
||||||
email_html =
|
|
||||||
; Path to alternate plaintext email. If blank, uses the internal template.
|
|
||||||
email_text =
|
|
||||||
; Subject of emails
|
|
||||||
subject = Password Reset - Jellyfin
|
|
||||||
|
|
||||||
[invite_emails]
|
|
||||||
; If enabled, allows one to send an invite directly to an email address.
|
|
||||||
enabled = true
|
|
||||||
; Path to custom email html. If blank, uses the internal template.
|
|
||||||
email_html =
|
|
||||||
; Path to alternate plaintext email. If blank, uses the internal template.
|
|
||||||
email_text =
|
|
||||||
subject = Invite - Jellyfin
|
|
||||||
; Base url for jf-accounts. This necessary because most will use a reverse proxy, so the program has no other way of knowing what URL to send.
|
|
||||||
url_base = http://accounts.jellyf.in:8056/invite
|
|
||||||
|
|
||||||
[mailgun]
|
|
||||||
|
|
||||||
api_url = https://api.mailgun.net...
|
|
||||||
api_key = your api key
|
|
||||||
|
|
||||||
[smtp]
|
|
||||||
; Choose between ssl_tls and starttls. Your provider should tell you which to use, but generally SSL/TLS is 465, STARTTLS 587
|
|
||||||
encryption = starttls
|
|
||||||
server = smtp.jellyf.in
|
|
||||||
; Uses SMTP_SSL, so make sure the port is for this, not starttls.
|
|
||||||
port = 465
|
|
||||||
password = smtp password
|
|
||||||
|
|
||||||
[files]
|
|
||||||
; When the below paths are left blank, files are stored in ~/.jf-accounts/.
|
|
||||||
|
|
||||||
; Path to store valid invites.
|
|
||||||
invites =
|
|
||||||
; Path to store emails addresses in JSON
|
|
||||||
emails =
|
|
||||||
; Path to the user policy template. Can be acquired with get-defaults (jf-accounts -g).
|
|
||||||
user_template =
|
|
||||||
; Path to the user configuration template (part of homescreen layout). Can be acquired with get-defaults (jf-accounts -g).
|
|
||||||
user_configuration =
|
|
||||||
; Path to the user display preferences template (part of homescreen layout). Can be acquired with get-defaults (jf-accounts -g).
|
|
||||||
user_displayprefs =
|
|
||||||
; Path to custom bootstrap.css
|
|
||||||
custom_css =
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
115
config-default.ini
Normal file
115
config-default.ini
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
[jellyfin]
|
||||||
|
; settings for connecting to jellyfin
|
||||||
|
; it is recommended to create a limited admin account for this program.
|
||||||
|
username = username
|
||||||
|
password = password
|
||||||
|
; jellyfin server address. can be public, or local for security purposes.
|
||||||
|
server = http://jellyfin.local:8096
|
||||||
|
; publicly accessible jellyfin address for invite form. leave blank to reuse the above address.
|
||||||
|
public_server = https://jellyf.in:443
|
||||||
|
; this and below settings will show on the jellyfin dashboard when the program connects. you may as well leave them alone.
|
||||||
|
client = jf-accounts
|
||||||
|
version = 0.2.5
|
||||||
|
device = jf-accounts
|
||||||
|
device_id = jf-accounts-0.2.5
|
||||||
|
|
||||||
|
[ui]
|
||||||
|
; settings related to the ui and program functionality.
|
||||||
|
; set 0.0.0.0 to run on localhost
|
||||||
|
host = 0.0.0.0
|
||||||
|
port = 8056
|
||||||
|
; enable this to use jellyfin users instead of the below username and pw.
|
||||||
|
jellyfin_login = true
|
||||||
|
; allows only admin users on jellyfin to access the admin page.
|
||||||
|
admin_only = true
|
||||||
|
; username for admin page (leave blank if using jellyfin_login)
|
||||||
|
username = your username
|
||||||
|
; password for admin page (leave blank if using jellyfin_login)
|
||||||
|
password = your password
|
||||||
|
debug = false
|
||||||
|
; displayed at bottom of all pages except admin
|
||||||
|
contact_message = Need help? contact me.
|
||||||
|
; display at top of invite form.
|
||||||
|
help_message = Enter your details to create an account.
|
||||||
|
; displayed when a user creates an account
|
||||||
|
success_message = Your account has been created. Click below to continue to Jellyfin.
|
||||||
|
|
||||||
|
[password_validation]
|
||||||
|
; password validation (minimum length, etc.)
|
||||||
|
enabled = true
|
||||||
|
min_length = 8
|
||||||
|
upper = 1
|
||||||
|
lower = 0
|
||||||
|
number = 1
|
||||||
|
special = 0
|
||||||
|
|
||||||
|
[email]
|
||||||
|
; general email settings. ignore if not using email features.
|
||||||
|
; use email address from invite form as username on jellyfin.
|
||||||
|
no_username = false
|
||||||
|
use_24h = true
|
||||||
|
; date format used in emails. follows datetime.strftime format.
|
||||||
|
date_format = %d/%m/%y
|
||||||
|
; message displayed at bottom of emails.
|
||||||
|
message = Need help? contact me.
|
||||||
|
; method of sending email to use.
|
||||||
|
method = smtp
|
||||||
|
; address to send emails from
|
||||||
|
address = jellyfin@jellyf.in
|
||||||
|
; the name of the sender
|
||||||
|
from = Jellyfin
|
||||||
|
|
||||||
|
[password_resets]
|
||||||
|
; settings for the password reset handler.
|
||||||
|
; enable to store provided email addresses, monitor jellyfin directory for pw-resets, and send reset pins
|
||||||
|
enabled = true
|
||||||
|
; path to the folder jellyfin puts password-reset files.
|
||||||
|
watch_directory = /path/to/jellyfin
|
||||||
|
; path to custom email html
|
||||||
|
email_html =
|
||||||
|
; path to custom email in plain text
|
||||||
|
email_text =
|
||||||
|
; subject of password reset emails.
|
||||||
|
subject = Password Reset - Jellyfin
|
||||||
|
|
||||||
|
[invite_emails]
|
||||||
|
; settings for sending invites directly to users.
|
||||||
|
enabled = true
|
||||||
|
; path to custom email html
|
||||||
|
email_html =
|
||||||
|
; path to custom email in plain text
|
||||||
|
email_text =
|
||||||
|
; subject of invite emails.
|
||||||
|
subject = Invite - Jellyfin
|
||||||
|
; base url for jf-accounts. this is necessary because using a reverse proxy means the program has no way of knowing the url itself.
|
||||||
|
url_base = http://accounts.jellyf.in:8056/invite
|
||||||
|
|
||||||
|
[mailgun]
|
||||||
|
; mailgun api connection settings
|
||||||
|
api_url = https://api.mailgun.net...
|
||||||
|
api_key = your api key
|
||||||
|
|
||||||
|
[smtp]
|
||||||
|
; smtp server connection settings.
|
||||||
|
; your email provider should provide different ports for each encryption method. generally 465 for ssl_tls, 587 for starttls.
|
||||||
|
encryption = starttls
|
||||||
|
; smtp server address.
|
||||||
|
server = smtp.jellyf.in
|
||||||
|
port = 465
|
||||||
|
password = smtp password
|
||||||
|
|
||||||
|
[files]
|
||||||
|
; optional settings for changing storage locations.
|
||||||
|
; location of stored invites (json).
|
||||||
|
invites =
|
||||||
|
; location of stored email addresses (json).
|
||||||
|
emails =
|
||||||
|
; location of stored user policy template (json).
|
||||||
|
user_template =
|
||||||
|
; location of stored user configuration template (used for setting homescreen layout) (json)
|
||||||
|
user_configuration =
|
||||||
|
; location of stored displaypreferences template (also used for homescreen layout) (json)
|
||||||
|
user_displayprefs =
|
||||||
|
; location of custom bootstrap css.
|
||||||
|
custom_css =
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
__version__ = "0.2.2"
|
__version__ = "0.2.5"
|
||||||
|
|
||||||
import secrets
|
import secrets
|
||||||
import configparser
|
import configparser
|
||||||
@ -11,7 +11,7 @@ import signal
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Flask, g
|
from flask import Flask, jsonify, g
|
||||||
from jellyfin_accounts.data_store import JSONStorage
|
from jellyfin_accounts.data_store import JSONStorage
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="jellyfin-accounts")
|
parser = argparse.ArgumentParser(description="jellyfin-accounts")
|
||||||
@ -44,15 +44,20 @@ else:
|
|||||||
data_dir = Path.home() / ".jf-accounts"
|
data_dir = Path.home() / ".jf-accounts"
|
||||||
|
|
||||||
local_dir = (Path(__file__).parent / "data").resolve()
|
local_dir = (Path(__file__).parent / "data").resolve()
|
||||||
|
config_base_path = local_dir / "config-base.json"
|
||||||
|
|
||||||
first_run = False
|
first_run = False
|
||||||
if data_dir.exists() is False or (data_dir / "config.ini").exists() is False:
|
if data_dir.exists() is False or (data_dir / "config.ini").exists() is False:
|
||||||
if not data_dir.exists():
|
if not data_dir.exists():
|
||||||
Path.mkdir(data_dir)
|
Path.mkdir(data_dir)
|
||||||
print(f"Config dir not found, so created at {str(data_dir)}")
|
print(f"Config dir not found, so generating at {str(data_dir)}")
|
||||||
if args.config is None:
|
if args.config is None:
|
||||||
config_path = data_dir / "config.ini"
|
config_path = data_dir / "config.ini"
|
||||||
shutil.copy(str(local_dir / "config-default.ini"), str(config_path))
|
from jellyfin_accounts.generate_ini import generate_ini
|
||||||
|
|
||||||
|
default_path = local_dir / "config-default.ini"
|
||||||
|
generate_ini(config_base_path, default_path, __version__)
|
||||||
|
shutil.copy(str(default_path), str(config_path))
|
||||||
print("Setup through the web UI, or quit and edit the configuration manually.")
|
print("Setup through the web UI, or quit and edit the configuration manually.")
|
||||||
first_run = True
|
first_run = True
|
||||||
else:
|
else:
|
||||||
@ -110,18 +115,21 @@ if "no_username" not in config["email"]:
|
|||||||
config["email"]["no_username"] = "false"
|
config["email"]["no_username"] = "false"
|
||||||
log.debug("Set no_username to false")
|
log.debug("Set no_username to false")
|
||||||
|
|
||||||
with open(config["files"]["invites"], "r") as f:
|
try:
|
||||||
temp_invites = json.load(f)
|
with open(config["files"]["invites"], "r") as f:
|
||||||
if "invites" in temp_invites:
|
temp_invites = json.load(f)
|
||||||
new_invites = {}
|
if "invites" in temp_invites:
|
||||||
log.info("Converting invites.json to new format, temporary.")
|
new_invites = {}
|
||||||
for el in temp_invites["invites"]:
|
log.info("Converting invites.json to new format, temporary.")
|
||||||
i = {"valid_till": el["valid_till"]}
|
for el in temp_invites["invites"]:
|
||||||
if "email" in el:
|
i = {"valid_till": el["valid_till"]}
|
||||||
i["email"] = el["email"]
|
if "email" in el:
|
||||||
new_invites[el["code"]] = i
|
i["email"] = el["email"]
|
||||||
with open(config["files"]["invites"], "w") as f:
|
new_invites[el["code"]] = i
|
||||||
f.write(json.dumps(new_invites, indent=4, default=str))
|
with open(config["files"]["invites"], "w") as f:
|
||||||
|
f.write(json.dumps(new_invites, indent=4, default=str))
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
data_store = JSONStorage(
|
data_store = JSONStorage(
|
||||||
@ -195,6 +203,19 @@ if (
|
|||||||
config["jellyfin"]["public_server"] = config["jellyfin"]["server"]
|
config["jellyfin"]["public_server"] = config["jellyfin"]["server"]
|
||||||
|
|
||||||
|
|
||||||
|
def resp(success=True, code=500):
|
||||||
|
if success:
|
||||||
|
r = jsonify({"success": True})
|
||||||
|
if code == 500:
|
||||||
|
r.status_code = 200
|
||||||
|
else:
|
||||||
|
r.status_code = code
|
||||||
|
else:
|
||||||
|
r = jsonify({"success": False})
|
||||||
|
r.status_code = code
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if args.get_defaults:
|
if args.get_defaults:
|
||||||
import json
|
import json
|
||||||
@ -288,6 +309,7 @@ def main():
|
|||||||
app = Flask(__name__, root_path=str(local_dir))
|
app = Flask(__name__, root_path=str(local_dir))
|
||||||
app.config["DEBUG"] = config.getboolean("ui", "debug")
|
app.config["DEBUG"] = config.getboolean("ui", "debug")
|
||||||
app.config["SECRET_KEY"] = secrets.token_urlsafe(16)
|
app.config["SECRET_KEY"] = secrets.token_urlsafe(16)
|
||||||
|
app.config["JSON_SORT_KEYS"] = False
|
||||||
|
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
|
|
||||||
|
486
jellyfin_accounts/data/config-base.json
Normal file
486
jellyfin_accounts/data/config-base.json
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
{
|
||||||
|
"jellyfin": {
|
||||||
|
"meta": {
|
||||||
|
"name": "Jellyfin",
|
||||||
|
"description": "Settings for connecting to Jellyfin"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "Jellyfin Username",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "username",
|
||||||
|
"description": "It is recommended to create a limited admin account for this program."
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "Jellyfin Password",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "password",
|
||||||
|
"value": "password"
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"name": "Server address",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "http://jellyfin.local:8096",
|
||||||
|
"description": "Jellyfin server address. Can be public, or local for security purposes."
|
||||||
|
},
|
||||||
|
"public_server": {
|
||||||
|
"name": "Public address",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "text",
|
||||||
|
"value": "https://jellyf.in:443",
|
||||||
|
"description": "Publicly accessible Jellyfin address for invite form. Leave blank to reuse the above address."
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"name": "Client Name",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "jf-accounts",
|
||||||
|
"description": "This and below settings will show on the Jellyfin dashboard when the program connects. You may as well leave them alone."
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"name": "Version Number",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "{version}"
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"name": "Device Name",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "jf-accounts"
|
||||||
|
},
|
||||||
|
"device_id": {
|
||||||
|
"name": "Device ID",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "jf-accounts-{version}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ui": {
|
||||||
|
"meta": {
|
||||||
|
"name": "General",
|
||||||
|
"description": "Settings related to the UI and program functionality."
|
||||||
|
},
|
||||||
|
"host": {
|
||||||
|
"name": "Address",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "0.0.0.0",
|
||||||
|
"description": "Set 0.0.0.0 to run on localhost"
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"name": "Port",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "number",
|
||||||
|
"value": 8056
|
||||||
|
},
|
||||||
|
"jellyfin_login": {
|
||||||
|
"name": "Use Jellyfin for authentication",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "bool",
|
||||||
|
"value": true,
|
||||||
|
"description": "Enable this to use Jellyfin users instead of the below username and pw."
|
||||||
|
},
|
||||||
|
"admin_only": {
|
||||||
|
"name": "Allow admin users only",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "jellyfin_login",
|
||||||
|
"type": "bool",
|
||||||
|
"value": true,
|
||||||
|
"description": "Allows only admin users on Jellyfin to access the admin page."
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "Web Username",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_false": "jellyfin_login",
|
||||||
|
"type": "text",
|
||||||
|
"value": "your username",
|
||||||
|
"description": "Username for admin page (Leave blank if using jellyfin_login)"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "Web Password",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_false": "jellyfin_login",
|
||||||
|
"type": "password",
|
||||||
|
"value": "your password",
|
||||||
|
"description": "Password for admin page (Leave blank if using jellyfin_login)"
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"name": "Debug logging",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "bool",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
"contact_message": {
|
||||||
|
"name": "Contact message",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "text",
|
||||||
|
"value": "Need help? contact me.",
|
||||||
|
"description": "Displayed at bottom of all pages except admin"
|
||||||
|
},
|
||||||
|
"help_message": {
|
||||||
|
"name": "Help message",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "text",
|
||||||
|
"value": "Enter your details to create an account.",
|
||||||
|
"description": "Display at top of invite form."
|
||||||
|
},
|
||||||
|
"success_message": {
|
||||||
|
"name": "Success message",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "text",
|
||||||
|
"value": "Your account has been created. Click below to continue to Jellyfin.",
|
||||||
|
"description": "Displayed when a user creates an account"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"password_validation": {
|
||||||
|
"meta": {
|
||||||
|
"name": "Password Validation",
|
||||||
|
"description": "Password validation (minimum length, etc.)"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "Enabled",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "bool",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
"min_length": {
|
||||||
|
"name": "Minimum Length",
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "8"
|
||||||
|
},
|
||||||
|
"upper": {
|
||||||
|
"name": "Minimum uppercase characters",
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
"lower": {
|
||||||
|
"name": "Minimum lowercase characters",
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "0"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"name": "Minimum number count",
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
"special": {
|
||||||
|
"name": "Minimum number of special characters",
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"meta": {
|
||||||
|
"name": "Email",
|
||||||
|
"description": "General email settings. Ignore if not using email features."
|
||||||
|
},
|
||||||
|
"no_username": {
|
||||||
|
"name": "Use email addresses as username",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "method",
|
||||||
|
"type": "bool",
|
||||||
|
"value": false,
|
||||||
|
"description": "Use email address from invite form as username on Jellyfin."
|
||||||
|
},
|
||||||
|
"use_24h": {
|
||||||
|
"name": "Use 24h time",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "method",
|
||||||
|
"type": "bool",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
"date_format": {
|
||||||
|
"name": "Date format",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "method",
|
||||||
|
"type": "text",
|
||||||
|
"value": "%d/%m/%y",
|
||||||
|
"description": "Date format used in emails. Follows datetime.strftime format."
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"name": "Help message",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "method",
|
||||||
|
"type": "text",
|
||||||
|
"value": "Need help? contact me.",
|
||||||
|
"description": "Message displayed at bottom of emails."
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"name": "Email method",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "select",
|
||||||
|
"options": [
|
||||||
|
"smtp",
|
||||||
|
"mailgun"
|
||||||
|
],
|
||||||
|
"value": "smtp",
|
||||||
|
"description": "Method of sending email to use."
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"name": "Sent from (address)",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "method",
|
||||||
|
"type": "email",
|
||||||
|
"value": "jellyfin@jellyf.in",
|
||||||
|
"description": "Address to send emails from"
|
||||||
|
},
|
||||||
|
"from": {
|
||||||
|
"name": "Sent from (name)",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "method",
|
||||||
|
"type": "text",
|
||||||
|
"value": "Jellyfin",
|
||||||
|
"description": "The name of the sender"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"password_resets": {
|
||||||
|
"meta": {
|
||||||
|
"name": "Password Resets",
|
||||||
|
"description": "Settings for the password reset handler."
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "Enabled",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "bool",
|
||||||
|
"value": true,
|
||||||
|
"description": "Enable to store provided email addresses, monitor Jellyfin directory for pw-resets, and send reset pins"
|
||||||
|
},
|
||||||
|
"watch_directory": {
|
||||||
|
"name": "Jellyfin directory",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "/path/to/jellyfin",
|
||||||
|
"description": "Path to the folder Jellyfin puts password-reset files."
|
||||||
|
},
|
||||||
|
"email_html": {
|
||||||
|
"name": "Custom email (HTML)",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Path to custom email html"
|
||||||
|
},
|
||||||
|
"email_text": {
|
||||||
|
"name": "Custom email (plaintext)",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Path to custom email in plain text"
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"name": "Email subject",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "Password Reset - Jellyfin",
|
||||||
|
"description": "Subject of password reset emails."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"invite_emails": {
|
||||||
|
"meta": {
|
||||||
|
"name": "Invite emails",
|
||||||
|
"description": "Settings for sending invites directly to users."
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"name": "Enabled",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "bool",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
"email_html": {
|
||||||
|
"name": "Custom email (HTML)",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Path to custom email HTML"
|
||||||
|
},
|
||||||
|
"email_text": {
|
||||||
|
"name": "Custom email (plaintext)",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Path to custom email in plain text"
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"name": "Email subject",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "Invite - Jellyfin",
|
||||||
|
"description": "Subject of invite emails."
|
||||||
|
},
|
||||||
|
"url_base": {
|
||||||
|
"name": "URL Base",
|
||||||
|
"required": true,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "http://accounts.jellyf.in:8056/invite",
|
||||||
|
"description": "Base URL for jf-accounts. This is necessary because using a reverse proxy means the program has no way of knowing the URL itself."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mailgun": {
|
||||||
|
"meta": {
|
||||||
|
"name": "Mailgun (Email)",
|
||||||
|
"description": "Mailgun API connection settings"
|
||||||
|
},
|
||||||
|
"api_url": {
|
||||||
|
"name": "API URL",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "text",
|
||||||
|
"value": "https://api.mailgun.net..."
|
||||||
|
},
|
||||||
|
"api_key": {
|
||||||
|
"name": "API Key",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "text",
|
||||||
|
"value": "your api key"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smtp": {
|
||||||
|
"meta": {
|
||||||
|
"name": "SMTP (Email)",
|
||||||
|
"description": "SMTP Server connection settings."
|
||||||
|
},
|
||||||
|
"encryption": {
|
||||||
|
"name": "Encryption Method",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "select",
|
||||||
|
"options": [
|
||||||
|
"ssl_tls",
|
||||||
|
"starttls"
|
||||||
|
],
|
||||||
|
"value": "starttls",
|
||||||
|
"description": "Your email provider should provide different ports for each encryption method. Generally 465 for ssl_tls, 587 for starttls."
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"name": "Server address",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "text",
|
||||||
|
"value": "smtp.jellyf.in",
|
||||||
|
"description": "SMTP Server address."
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"name": "Port",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "number",
|
||||||
|
"value": 465
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "Password",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "password",
|
||||||
|
"value": "smtp password"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"meta": {
|
||||||
|
"name": "File Storage",
|
||||||
|
"description": "Optional settings for changing storage locations."
|
||||||
|
},
|
||||||
|
"invites": {
|
||||||
|
"name": "Invite Storage",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Location of stored invites (json)."
|
||||||
|
},
|
||||||
|
"emails": {
|
||||||
|
"name": "Email Addresses",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Location of stored email addresses (json)."
|
||||||
|
},
|
||||||
|
"user_template": {
|
||||||
|
"name": "User Template",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Location of stored user policy template (json)."
|
||||||
|
},
|
||||||
|
"user_configuration": {
|
||||||
|
"name": "userConfiguration",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Location of stored user configuration template (used for setting homescreen layout) (json)"
|
||||||
|
},
|
||||||
|
"user_displayprefs": {
|
||||||
|
"name": "displayPreferences",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Location of stored displayPreferences template (also used for homescreen layout) (json)"
|
||||||
|
},
|
||||||
|
"custom_css": {
|
||||||
|
"name": "Custom CSS",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Location of custom bootstrap CSS."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,118 +0,0 @@
|
|||||||
[jellyfin]
|
|
||||||
; It is reccommended to create a limited admin account for this program.
|
|
||||||
username = username
|
|
||||||
password = password
|
|
||||||
; Jellyfin server address. Can be public, or local for security purposes.
|
|
||||||
server = http://jellyfin.local:8096
|
|
||||||
; Publicly accessible Jellyfin address, used on invite form.
|
|
||||||
; Leave blank to use the same address as above.
|
|
||||||
public_server = https://jellyf.in:443
|
|
||||||
client = jf-accounts
|
|
||||||
version = 0.1
|
|
||||||
device = jf-accounts
|
|
||||||
device_id = jf-accounts-0.1
|
|
||||||
|
|
||||||
[ui]
|
|
||||||
; Set 0.0.0.0 to run localhost
|
|
||||||
host = 0.0.0.0
|
|
||||||
port = 8056
|
|
||||||
; Enable this to use Jellyfin users instead of the below username and pw.
|
|
||||||
jellyfin_login = true
|
|
||||||
; Allows only admin users on Jellyfin to access admin page.
|
|
||||||
admin_only = true
|
|
||||||
; Username to use on admin page... (leave blank if using jellyfin_login)
|
|
||||||
username = your username
|
|
||||||
; ..and its corresponding password (leave blank if using jellyfin_login)
|
|
||||||
password = your password
|
|
||||||
|
|
||||||
debug = false
|
|
||||||
|
|
||||||
; Displayed at the bottom of all pages except admin
|
|
||||||
contact_message = Need help? contact me.
|
|
||||||
; Displayed at top of form page.
|
|
||||||
help_message = Enter your details to create an account.
|
|
||||||
; Displayed when an account is created.
|
|
||||||
success_message = Your account has been created. Click below to continue to Jellyfin.
|
|
||||||
|
|
||||||
[password_validation]
|
|
||||||
; Enables password validation.
|
|
||||||
enabled = true
|
|
||||||
; Min. password length
|
|
||||||
min_length = 8
|
|
||||||
; Min. number of uppercase characters
|
|
||||||
upper = 1
|
|
||||||
; Min. number of lowercase characters
|
|
||||||
lower = 0
|
|
||||||
; Min. number of numbers
|
|
||||||
number = 1
|
|
||||||
; Min. number of special characters
|
|
||||||
special = 0
|
|
||||||
|
|
||||||
[email]
|
|
||||||
; When true, disables username input on invite form and sets the Jellyfin username to the email address
|
|
||||||
no_username = false
|
|
||||||
; Leave the rest of this section if you aren't using any email-related features.
|
|
||||||
use_24h = true
|
|
||||||
; Date format follows datetime's strftime.
|
|
||||||
date_format = %d/%m/%y
|
|
||||||
; Displayed at bottom of emails
|
|
||||||
message = Need help? contact me.
|
|
||||||
; Mail methods: mailgun, smtp
|
|
||||||
method = smtp
|
|
||||||
; Address to send from
|
|
||||||
address = jellyfin@jellyf.in
|
|
||||||
; The name of the sender
|
|
||||||
from = Jellyfin
|
|
||||||
|
|
||||||
[password_resets]
|
|
||||||
; Enable to store provided email addresses, monitor jellyfin directory for pw-resets, and send pin
|
|
||||||
enabled = true
|
|
||||||
; Directory to monitor for passwordReset*.json files. Usually the jellyfin config directory
|
|
||||||
watch_directory = /path/to/jellyfin
|
|
||||||
; Path to custom email html. If blank, uses the internal template.
|
|
||||||
email_html =
|
|
||||||
; Path to alternate plaintext email. If blank, uses the internal template.
|
|
||||||
email_text =
|
|
||||||
; Subject of emails
|
|
||||||
subject = Password Reset - Jellyfin
|
|
||||||
|
|
||||||
[invite_emails]
|
|
||||||
; If enabled, allows one to send an invite directly to an email address.
|
|
||||||
enabled = true
|
|
||||||
; Path to custom email html. If blank, uses the internal template.
|
|
||||||
email_html =
|
|
||||||
; Path to alternate plaintext email. If blank, uses the internal template.
|
|
||||||
email_text =
|
|
||||||
subject = Invite - Jellyfin
|
|
||||||
; Base url for jf-accounts. This necessary because most will use a reverse proxy, so the program has no other way of knowing what URL to send.
|
|
||||||
url_base = http://accounts.jellyf.in:8056/invite
|
|
||||||
|
|
||||||
[mailgun]
|
|
||||||
|
|
||||||
api_url = https://api.mailgun.net...
|
|
||||||
api_key = your api key
|
|
||||||
|
|
||||||
[smtp]
|
|
||||||
; Choose between ssl_tls and starttls. Your provider should tell you which to use, but generally SSL/TLS is 465, STARTTLS 587
|
|
||||||
encryption = starttls
|
|
||||||
server = smtp.jellyf.in
|
|
||||||
; Uses SMTP_SSL, so make sure the port is for this, not starttls.
|
|
||||||
port = 465
|
|
||||||
password = smtp password
|
|
||||||
|
|
||||||
[files]
|
|
||||||
; When the below paths are left blank, files are stored in ~/.jf-accounts/.
|
|
||||||
|
|
||||||
; Path to store valid invites.
|
|
||||||
invites =
|
|
||||||
; Path to store emails addresses in JSON
|
|
||||||
emails =
|
|
||||||
; Path to the user policy template. Can be acquired with get-defaults (jf-accounts -g).
|
|
||||||
user_template =
|
|
||||||
; Path to the user configuration template (part of homescreen layout). Can be acquired with get-defaults (jf-accounts -g).
|
|
||||||
user_configuration =
|
|
||||||
; Path to the user display preferences template (part of homescreen layout). Can be acquired with get-defaults (jf-accounts -g).
|
|
||||||
user_displayprefs =
|
|
||||||
; Path to custom bootstrap.css
|
|
||||||
custom_css =
|
|
||||||
|
|
@ -241,9 +241,6 @@ $("form#loginForm").submit(function() {
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
document.getElementById('openSettings').onclick = function () {
|
|
||||||
$('#settingsMenu').modal('show');
|
|
||||||
}
|
|
||||||
document.getElementById('openDefaultsWizard').onclick = function () {
|
document.getElementById('openDefaultsWizard').onclick = function () {
|
||||||
this.disabled = true;
|
this.disabled = true;
|
||||||
this.innerHTML =
|
this.innerHTML =
|
||||||
@ -294,7 +291,8 @@ document.getElementById('openDefaultsWizard').onclick = function () {
|
|||||||
} else if (submitButton.classList.contains('btn-danger')) {
|
} else if (submitButton.classList.contains('btn-danger')) {
|
||||||
submitButton.classList.remove('btn-danger');
|
submitButton.classList.remove('btn-danger');
|
||||||
submitButton.classList.add('btn-primary');
|
submitButton.classList.add('btn-primary');
|
||||||
}
|
};
|
||||||
|
$('#settingsMenu').modal('hide');
|
||||||
$('#userDefaults').modal('show');
|
$('#userDefaults').modal('show');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,6 +334,7 @@ document.getElementById('storeDefaults').onclick = function () {
|
|||||||
},
|
},
|
||||||
error: function() {
|
error: function() {
|
||||||
button.textContent = 'Failed';
|
button.textContent = 'Failed';
|
||||||
|
config_base_path = local_dir / "config-base.json"
|
||||||
button.classList.remove('btn-primary');
|
button.classList.remove('btn-primary');
|
||||||
button.classList.add('btn-danger');
|
button.classList.add('btn-danger');
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
@ -442,6 +441,7 @@ document.getElementById('openUsers').onclick = function () {
|
|||||||
var button = document.getElementById('openUsers');
|
var button = document.getElementById('openUsers');
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
button.innerHTML = 'Users <i class="fa fa-user"></i>';
|
button.innerHTML = 'Users <i class="fa fa-user"></i>';
|
||||||
|
$('#settingsMenu').modal('hide');
|
||||||
$('#users').modal('show');
|
$('#users').modal('show');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -449,3 +449,218 @@ document.getElementById('openUsers').onclick = function () {
|
|||||||
};
|
};
|
||||||
generateInvites(empty = true);
|
generateInvites(empty = true);
|
||||||
$("#login").modal('show');
|
$("#login").modal('show');
|
||||||
|
|
||||||
|
var config = {};
|
||||||
|
var modifiedConfig = {};
|
||||||
|
|
||||||
|
document.getElementById('openSettings').onclick = function () {
|
||||||
|
restart_setting_changed = false;
|
||||||
|
$.ajax('getConfig', {
|
||||||
|
type : 'GET',
|
||||||
|
dataType : 'json',
|
||||||
|
contentType : 'json',
|
||||||
|
xhrFields : {
|
||||||
|
withCredentials: true
|
||||||
|
},
|
||||||
|
beforeSend : function (xhr) {
|
||||||
|
xhr.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
|
||||||
|
},
|
||||||
|
complete : function(data) {
|
||||||
|
if (data['status'] == 200) {
|
||||||
|
var settingsList = document.getElementById('settingsList');
|
||||||
|
settingsList.textContent = '';
|
||||||
|
config = data['responseJSON'];
|
||||||
|
for (var section of Object.keys(config)) {
|
||||||
|
var sectionCollapse = document.createElement('div');
|
||||||
|
sectionCollapse.classList.add('collapse');
|
||||||
|
sectionCollapse.id = section;
|
||||||
|
|
||||||
|
var sectionTitle = config[section]['meta']['name'];
|
||||||
|
var sectionDescription = config[section]['meta']['description'];
|
||||||
|
var entryListID = section + '_entryList';
|
||||||
|
var sectionFooter = section + '_footer';
|
||||||
|
|
||||||
|
var innerCollapse = `
|
||||||
|
<div class="card card-body">
|
||||||
|
<small class="text-muted">${sectionDescription}</small>
|
||||||
|
<div class="${entryListID}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
sectionCollapse.innerHTML = innerCollapse;
|
||||||
|
|
||||||
|
for (var entry of Object.keys(config[section])) {
|
||||||
|
if (entry != 'meta') {
|
||||||
|
var entryName = config[section][entry]['name'];
|
||||||
|
var required = false;
|
||||||
|
if (config[section][entry]['required']) {
|
||||||
|
entryName += ' <sup class="text-danger">*</sup>';
|
||||||
|
required = true;
|
||||||
|
};
|
||||||
|
if (config[section][entry].hasOwnProperty('description')) {
|
||||||
|
var tooltip = `
|
||||||
|
<a class="text-muted" href="#" data-toggle="tooltip" data-placement="right" title="${config[section][entry]['description']}"><i class="fa fa-question-circle-o"></i></a>
|
||||||
|
`;
|
||||||
|
entryName += ' ';
|
||||||
|
entryName += tooltip;
|
||||||
|
};
|
||||||
|
// if (config[section][entry]['requires_restart']) {
|
||||||
|
// entryName += ' <sup class="text-danger">R</sup>';
|
||||||
|
// };
|
||||||
|
var entryValue = config[section][entry]['value'];
|
||||||
|
var entryType = config[section][entry]['type'];
|
||||||
|
var entryGroup = document.createElement('div');
|
||||||
|
if (entryType == 'bool') {
|
||||||
|
entryGroup.classList.add('form-check');
|
||||||
|
if (entryValue) {
|
||||||
|
var checked = true;
|
||||||
|
} else {
|
||||||
|
var checked = false;
|
||||||
|
};
|
||||||
|
entryGroup.innerHTML = `
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="${section}_${entry}">
|
||||||
|
<label class="form-check-label" for="${section}_${entry}">${entryName}</label>
|
||||||
|
`;
|
||||||
|
entryGroup.getElementsByClassName('form-check-input')[0].required = required;
|
||||||
|
entryGroup.getElementsByClassName('form-check-input')[0].checked = checked;
|
||||||
|
entryGroup.getElementsByClassName('form-check-input')[0].onclick = function() {
|
||||||
|
var state = this.checked;
|
||||||
|
for (var sect of Object.keys(config)) {
|
||||||
|
for (var ent of Object.keys(config[sect])) {
|
||||||
|
if ((sect + '_' + config[sect][ent]['depends_true']) == this.id) {
|
||||||
|
document.getElementById(sect + '_' + ent).disabled = !state;
|
||||||
|
} else if ((sect + '_' + config[sect][ent]['depends_false']) == this.id) {
|
||||||
|
document.getElementById(sect + '_' + ent).disabled = state;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} else if ((entryType == 'text') || (entryType == 'email') || (entryType == 'password') || (entryType == 'number')) {
|
||||||
|
entryGroup.classList.add('form-group');
|
||||||
|
entryGroup.innerHTML = `
|
||||||
|
<label for="${section}_${entry}">${entryName}</label>
|
||||||
|
<input type="${entryType}" class="form-control" id="${section}_${entry}" aria-describedby="${entry}" value="${entryValue}">
|
||||||
|
`;
|
||||||
|
entryGroup.getElementsByClassName('form-control')[0].required = required;
|
||||||
|
} else if (entryType == 'select') {
|
||||||
|
entryGroup.classList.add('form-group');
|
||||||
|
var entryOptions = config[section][entry]['options'];
|
||||||
|
var innerGroup = `
|
||||||
|
<label for="${section}_${entry}">${entryName}</label>
|
||||||
|
<select class="form-control" id="${section}_${entry}">
|
||||||
|
`;
|
||||||
|
for (var i = 0; i < entryOptions.length; i++) {
|
||||||
|
if (entryOptions[i] == entryValue) {
|
||||||
|
var selected = 'selected';
|
||||||
|
} else {
|
||||||
|
var selected = '';
|
||||||
|
}
|
||||||
|
innerGroup += `
|
||||||
|
<option value="${entryOptions[i]}" ${selected}>${entryOptions[i]}</option>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
innerGroup += '</select>';
|
||||||
|
entryGroup.innerHTML = innerGroup;
|
||||||
|
entryGroup.getElementsByClassName('form-control')[0].required = required;
|
||||||
|
|
||||||
|
};
|
||||||
|
sectionCollapse.getElementsByClassName(entryListID)[0].appendChild(entryGroup);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
var sectionButton = document.createElement('button');
|
||||||
|
sectionButton.setAttribute('type', 'button');
|
||||||
|
sectionButton.classList.add('list-group-item', 'list-group-item-action');
|
||||||
|
sectionButton.appendChild(document.createTextNode(sectionTitle));
|
||||||
|
sectionButton.id = section + '_button';
|
||||||
|
sectionButton.setAttribute('data-toggle', 'collapse');
|
||||||
|
sectionButton.setAttribute('data-target', '#' + section);
|
||||||
|
settingsList.appendChild(sectionButton);
|
||||||
|
settingsList.appendChild(sectionCollapse);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
$('#settingsMenu').modal('show');
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#settingsMenu').on('shown.bs.modal', function() {
|
||||||
|
$("a[data-toggle='tooltip']").each(function (i, obj) {
|
||||||
|
$(obj).tooltip();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function sendConfig(modalId) {
|
||||||
|
var modal = document.getElementById(modalId);
|
||||||
|
var send = JSON.stringify(modifiedConfig);
|
||||||
|
$.ajax('/modifyConfig', {
|
||||||
|
data : send,
|
||||||
|
contentType : 'application/json',
|
||||||
|
type : 'POST',
|
||||||
|
xhrFields : {
|
||||||
|
withCredentials: true
|
||||||
|
},
|
||||||
|
beforeSend : function (xhr) {
|
||||||
|
xhr.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
|
||||||
|
},
|
||||||
|
success: function() {
|
||||||
|
if (modalId != 'settingsMenu') {
|
||||||
|
$('#' + modalId).modal('hide');
|
||||||
|
$('#settingsMenu').modal('hide');
|
||||||
|
};
|
||||||
|
},
|
||||||
|
fail: function(xhr, textStatus, errorThrown) {
|
||||||
|
var footer = modal.getElementsByClassName('modal-dialog')[0].getElementsByClassName('modal-content')[0].getElementsByClassName('modal-footer')[0];
|
||||||
|
var alert = document.createElement('div');
|
||||||
|
alert.classList.add('alert', 'alert-danger');
|
||||||
|
alert.setAttribute('role', 'alert');
|
||||||
|
alert.appendChild(document.createTextNode('Error: ' + errorThrown));
|
||||||
|
footer.appendChild(alert);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// placeholder
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('settingsSave').onclick = function() {
|
||||||
|
modifiedConfig = {};
|
||||||
|
// Live config changes have not yet been implemented, so restart always required.
|
||||||
|
// var restart_setting_changed = false;
|
||||||
|
var settings_changed = false;
|
||||||
|
|
||||||
|
for (var section of Object.keys(config)) {
|
||||||
|
for (var entry of Object.keys(config[section])) {
|
||||||
|
if (entry != 'meta') {
|
||||||
|
var entryID = section + '_' + entry;
|
||||||
|
var el = document.getElementById(entryID);
|
||||||
|
if (el.type == 'checkbox') {
|
||||||
|
var value = el.checked.toString();
|
||||||
|
} else {
|
||||||
|
var value = el.value.toString();
|
||||||
|
};
|
||||||
|
if (value != config[section][entry]['value'].toString()) {
|
||||||
|
if (!modifiedConfig.hasOwnProperty(section)) {
|
||||||
|
modifiedConfig[section] = {};
|
||||||
|
};
|
||||||
|
modifiedConfig[section][entry] = value;
|
||||||
|
settings_changed = true;
|
||||||
|
// if (config[section][entry]['requires_restart']) {
|
||||||
|
// restart_setting_changed = true;
|
||||||
|
// };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// if (restart_setting_changed) {
|
||||||
|
if (settings_changed) {
|
||||||
|
document.getElementById('applyRestarts').onclick = function(){sendConfig('restartModal');};
|
||||||
|
$('#settingsMenu').modal('hide');
|
||||||
|
$('#restartModal').modal({
|
||||||
|
backdrop: 'static',
|
||||||
|
show: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// sendConfig('settingsMenu');
|
||||||
|
$('#settingsMenu').modal('hide');
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
@ -93,20 +93,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
<li class="list-group-item">
|
<p>Note: <sup class="text-danger">*</sup> Indicates required field.</p>
|
||||||
<button type="button" class="btn btn-secondary" id="openUsers">
|
<button type="button" class="list-group-item list-group-item-action" id="openUsers">
|
||||||
Users <i class="fa fa-user"></i>
|
Users <i class="fa fa-user"></i>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
<button type="button" class="list-group-item list-group-item-action" id="openDefaultsWizard">
|
||||||
<li class="list-group-item">
|
New account defaults
|
||||||
<button type="button" class="btn btn-secondary" id="openDefaultsWizard">
|
</button>
|
||||||
New account defaults
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
<div class="list-group list-group-flush" id="settingsList">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer" id="settingsFooter">
|
<div class="modal-footer" id="settingsFooter">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="settingsSave">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -153,6 +153,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" id="restartModal" tabindex="-1" role="dialog" aria-labelledby"Restart Warning" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Warning</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>A restart is needed to apply settings. This must be done manually. Apply now?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="applyRestarts" data-dismiss="alert">Apply</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="pageContainer">
|
<div class="pageContainer">
|
||||||
<h1>
|
<h1>
|
||||||
Accounts admin
|
Accounts admin
|
||||||
|
35
jellyfin_accounts/generate_ini.py
Normal file
35
jellyfin_accounts/generate_ini.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import configparser
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ini(base_file, ini_file, version):
|
||||||
|
"""
|
||||||
|
Generates .ini file from config-base file.
|
||||||
|
"""
|
||||||
|
with open(Path(base_file), "r") as f:
|
||||||
|
config_base = json.load(f)
|
||||||
|
|
||||||
|
ini = configparser.RawConfigParser(allow_no_value=True)
|
||||||
|
|
||||||
|
for section in config_base:
|
||||||
|
ini.add_section(section)
|
||||||
|
for entry in config_base[section]:
|
||||||
|
if "description" in config_base[section][entry]:
|
||||||
|
ini.set(section, "; " + config_base[section][entry]["description"])
|
||||||
|
if entry != "meta":
|
||||||
|
value = config_base[section][entry]["value"]
|
||||||
|
if isinstance(value, bool):
|
||||||
|
value = str(value).lower()
|
||||||
|
else:
|
||||||
|
value = str(value)
|
||||||
|
ini.set(section, entry, value)
|
||||||
|
|
||||||
|
ini["jellyfin"]["version"] = version
|
||||||
|
ini["jellyfin"]["device_id"] = ini["jellyfin"]["device_id"].replace(
|
||||||
|
"{version}", version
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(Path(ini_file), "w") as config_file:
|
||||||
|
ini.write(config_file)
|
||||||
|
return True
|
@ -79,7 +79,10 @@ class Jellyfin:
|
|||||||
"User-Agent": self.useragent,
|
"User-Agent": self.useragent,
|
||||||
"X-Emby-Authorization": self.auth,
|
"X-Emby-Authorization": self.auth,
|
||||||
}
|
}
|
||||||
self.info = requests.get(self.server + "/System/Info/Public").json()
|
try:
|
||||||
|
self.info = requests.get(self.server + "/System/Info/Public").json()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def getUsers(self, username: str = "all", userId: str = "all", public: bool = True):
|
def getUsers(self, username: str = "all", userId: str = "all", public: bool = True):
|
||||||
"""
|
"""
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from flask import request, jsonify, render_template
|
from flask import request, jsonify, render_template
|
||||||
from configparser import RawConfigParser
|
from configparser import RawConfigParser
|
||||||
from jellyfin_accounts.jf_api import Jellyfin
|
from jellyfin_accounts.jf_api import Jellyfin
|
||||||
from jellyfin_accounts import config, config_path, app, first_run
|
from jellyfin_accounts import config, config_path, app, first_run, resp
|
||||||
from jellyfin_accounts import web_log as log
|
from jellyfin_accounts import web_log as log
|
||||||
from jellyfin_accounts.web_api import resp
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
if first_run:
|
if first_run:
|
||||||
|
@ -4,24 +4,20 @@ import json
|
|||||||
import datetime
|
import datetime
|
||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
from jellyfin_accounts import config, config_path, app, g, data_store
|
from jellyfin_accounts import (
|
||||||
|
config,
|
||||||
|
config_path,
|
||||||
|
app,
|
||||||
|
g,
|
||||||
|
data_store,
|
||||||
|
resp,
|
||||||
|
configparser,
|
||||||
|
config_base_path,
|
||||||
|
)
|
||||||
from jellyfin_accounts import web_log as log
|
from jellyfin_accounts import web_log as log
|
||||||
from jellyfin_accounts.validate_password import PasswordValidator
|
from jellyfin_accounts.validate_password import PasswordValidator
|
||||||
|
|
||||||
|
|
||||||
def resp(success=True, code=500):
|
|
||||||
if success:
|
|
||||||
r = jsonify({"success": True})
|
|
||||||
if code == 500:
|
|
||||||
r.status_code = 200
|
|
||||||
else:
|
|
||||||
r.status_code = code
|
|
||||||
else:
|
|
||||||
r = jsonify({"success": False})
|
|
||||||
r.status_code = code
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
def checkInvite(code, delete=False):
|
def checkInvite(code, delete=False):
|
||||||
current_time = datetime.datetime.now()
|
current_time = datetime.datetime.now()
|
||||||
invites = dict(data_store.invites)
|
invites = dict(data_store.invites)
|
||||||
@ -327,4 +323,48 @@ def setDefaults():
|
|||||||
return resp()
|
return resp()
|
||||||
|
|
||||||
|
|
||||||
import jellyfin_accounts.setup
|
@app.route("/modifyConfig", methods=["POST"])
|
||||||
|
@auth.login_required
|
||||||
|
def modifyConfig():
|
||||||
|
log.info("Config modification requested")
|
||||||
|
data = request.get_json()
|
||||||
|
temp_config = configparser.RawConfigParser(
|
||||||
|
comment_prefixes="/", allow_no_value=True
|
||||||
|
)
|
||||||
|
temp_config.read(config_path)
|
||||||
|
for section in data:
|
||||||
|
if section in temp_config:
|
||||||
|
for item in data[section]:
|
||||||
|
if item in temp_config[section]:
|
||||||
|
temp_config[section][item] = data[section][item]
|
||||||
|
data[section][item] = True
|
||||||
|
log.debug(f"{section}/{item} modified")
|
||||||
|
else:
|
||||||
|
data[section][item] = False
|
||||||
|
log.debug(f"{section}/{item} does not exist in config")
|
||||||
|
with open(config_path, "w") as config_file:
|
||||||
|
temp_config.write(config_file)
|
||||||
|
log.info("Config written. Restart is needed to load settings.")
|
||||||
|
return resp()
|
||||||
|
|
||||||
|
|
||||||
|
# @app.route('/getConfig', methods=["GET"])
|
||||||
|
# @auth.login_required
|
||||||
|
# def getConfig():
|
||||||
|
# log.debug('Config requested')
|
||||||
|
# return jsonify(config._sections), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/getConfig", methods=["GET"])
|
||||||
|
@auth.login_required
|
||||||
|
def getConfig():
|
||||||
|
log.debug("Config requested")
|
||||||
|
with open(config_base_path, "r") as f:
|
||||||
|
config_base = json.load(f)
|
||||||
|
config.read(config_path)
|
||||||
|
response_config = config_base
|
||||||
|
for section in config_base:
|
||||||
|
for entry in config_base[section]:
|
||||||
|
if entry in config[section]:
|
||||||
|
response_config[section][entry]["value"] = config[section][entry]
|
||||||
|
return jsonify(response_config), 200
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "jellyfin-accounts"
|
name = "jellyfin-accounts"
|
||||||
version = "0.2.3"
|
version = "0.2.5"
|
||||||
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>"]
|
||||||
|
Loading…
Reference in New Issue
Block a user