mirror of https://github.com/hrfee/jfa-go
Merge new setup wizard This is much more up-to-date than the previous setup page, with a new design and previously missing/new settings. Currently only available in english (hopefully that changes soon). also fixes conflict in _post.pull/61/head
commit
3dd83bffbf
@ -1,374 +1,460 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="light-theme">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-alpha3/dist/js/bootstrap.min.js" integrity="sha384-t6I8D5dJmMXjCsRLhSzCltuhNZg6P10kE0m0nAncLUjH6GeYLhRU1zfLoW3QNQDF" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
.card-body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10%;
|
||||
}
|
||||
.slider {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: none;
|
||||
scroll-snap-type: x mandatory;
|
||||
}
|
||||
.slider > div {
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
.slide {
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
margin: 10%;
|
||||
}
|
||||
</style>
|
||||
<title>Setup - jfa-go</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/bundle.css">
|
||||
{{ template "header.html" . }}
|
||||
<title>{{ .lang.Strings.pageTitle }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="pageContainer">
|
||||
<div class="container">
|
||||
<body class="max-w-full overflow-x-hidden section">
|
||||
<div id="notification-box"></div>
|
||||
<span class="dropdown" tabindex="0" id="lang-dropdown">
|
||||
<span class="button ~urge dropdown-button">
|
||||
<i class="ri-global-line"></i>
|
||||
<span class="ml-1 chev"></span>
|
||||
</span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral !low" id="lang-list">
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<div class="page-container" id="page-container">
|
||||
<div class="card ~neutral !low mb-1">
|
||||
<div class="row">
|
||||
<div class="col-sm"></div>
|
||||
<div id="setupCarousel" class="col-md-auto slider">
|
||||
<div class="slide card text-center" id="page-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Welcome!</h5>
|
||||
<p class="card-text">
|
||||
You'll need to do a few things to start using jfa-go. Click below to get started, or quit and edit the config file manually.
|
||||
</p>
|
||||
<a class="btn btn-primary nextButton" href="#page-2">Get Started</a>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<small>Note: Make sure you are accessing this page through HTTPS, or on a private network.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide card" id="page-2">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Login</h5>
|
||||
<p class="card-text">
|
||||
To access the admin page, you'll need to login. Choose how below.
|
||||
<ul>
|
||||
<li><b>Authorize through Jellyfin: </b>Checks credentials with Jellyfin, allowing you to share login details and grant multiple users access.</li>
|
||||
<li><b>Username & Password: </b>Set your own username and password manually.</li>
|
||||
</ul>
|
||||
<div class="form-check" id="jfAuthFormGroup">
|
||||
<input class="form-check-input" type="radio" name="auth" id="jfAuthRadio" value="jfAuth" checked>
|
||||
<label class="form-check-label" for="jfAuthRadio">
|
||||
Authorize through Jellyfin
|
||||
</label>
|
||||
</div>
|
||||
<div id="adminOnlyArea">
|
||||
<div class="form-check" style="margin-left: 1rem;">
|
||||
<input type="checkbox" class="form-check-input" id="jfAuthAdminOnly" checked>
|
||||
<label for="jfAuthAdminOnly" class="form-check-label">Allow admin users only</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="auth" id="manualAuthRadio" value="manualAuth">
|
||||
<label class="form-check-label" for="manualAuthRadio">
|
||||
Manual username & password
|
||||
</label>
|
||||
</div>
|
||||
<div id="manualAuthArea">
|
||||
<div class="form-group">
|
||||
<label for="manualAuthUsername">Username</label>
|
||||
<input type="text" class="form-control" id="manualAuthUsername" placeholder="Username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="manualAuthPassword">Password</label>
|
||||
<input type="password" class="form-control" id="manualAuthPassword" placeholder="Password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="manualAuthEmail">Email (Optional)</label>
|
||||
<input type="email" class="form-control" id="manualAuthEmail" placeholder="example@example.com">
|
||||
<small class="form-text text-muted">Your email address is only required if you want to recieve activity notifications.</small>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
|
||||
<a class="btn btn-secondary backButton" href="#page-1">Back</a>
|
||||
<a class="btn btn-primary nextButton" href="#page-3">Next</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide card" id="page-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Jellyfin</h5>
|
||||
<p class="card-text">
|
||||
jfa-go needs admin access so that it can create users, as this is currently not permitted via API tokens.
|
||||
You should create a separate account for it, checking 'Allow this user to manage the server'. You can disable everything else. Once done, enter the credentials here.
|
||||
<div class="form-group">
|
||||
<label for="jfHost">Host (For internal use)</label>
|
||||
<input type="url" class="form-control" id="jfHost" placeholder="http://jellyf.in:443" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="jfPublicHost">Public Host (For access by users)</label>
|
||||
<input type="url" class="form-control" id="jfPublicHost" placeholder="Leave blank to use the above address.">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="jfUser">Username</label>
|
||||
<input type="text" class="form-control" id="jfUser" placeholder="Username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="jfPassword">Password</label>
|
||||
<input type="password" class="form-control" id="jfPassword" placeholder="Password" required>
|
||||
</div>
|
||||
<div style="margin-top: 1rem;">
|
||||
<button class="btn btn-secondary" id="jfTestButton">Test</button>
|
||||
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
|
||||
<a class="btn btn-secondary backButton" href="#page-2">Back</a>
|
||||
<a class="btn btn-primary nextButton disabled" id="jfNextButton" aria-disabled="true" href="#page-4">Next</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide card" id="page-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Email</h5>
|
||||
<p class="card-text">jfa-go is capable of sending a PIN code when a user tries to reset their password on Jellyfin. One can also choose to send an invite code directly to an email address. This can be done through SMTP or through <a href="https://www.mailgun.com/">Mailgun's</a> API.
|
||||
<div class="form-group">
|
||||
<div class="form-check" id="emailDisabled">
|
||||
<input class="form-check-input" type="radio" name="email" id="emailDisabledRadio" value="emailDisabled">
|
||||
<label class="form-check-label" for="emailDisabledRadio">
|
||||
Disabled
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check" id="emailSMTP">
|
||||
<input class="form-check-input" type="radio" name="email" id="emailSMTPRadio" value="emailSMTP" checked>
|
||||
<label class="form-check-label" for="emailSMTPRadio">
|
||||
SMTP
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="email" id="emailMailgunRadio" value="emailMailgun">
|
||||
<label class="form-check-label" for="emailMailgunRadio">
|
||||
Mailgun API
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="emailSMTPArea">
|
||||
<div class="form-group form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="emailSSL_TLS" checked>
|
||||
<label for="emailSSL_TLS" class="form-check-label" id="emailSSL_TLSLabel">Use SSL/TLS</label>
|
||||
<small class="form-text text-muted">Note: SSL/TLS usually uses port 465, whereas STARTTLS usually uses 587.</small>
|
||||
</div>
|
||||
<div class="form-group form-row">
|
||||
<div class="col">
|
||||
<input type="text" class="form-control" id="emailSMTPServer" placeholder="SMTP Server Address">
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="number" class="form-control" id="emailSMTPPort" placeholder="Port">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group form-row">
|
||||
<div class="col">
|
||||
<input type="email" class="form-control" id="emailSMTPAddress" placeholder="jellyfin@jellyf.in">
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type="password" class="form-control" id="emailSMTPPassword" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="emailMailgunArea">
|
||||
<div class="form-group">
|
||||
<input type="url" class="form-control" id="emailMailgunURL" placeholder="API URL">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control" id="emailMailgunKey" placeholder="API Key">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="email" class="form-control" id="emailMailgunAddress" placeholder="jellyfin@jellyf.in">
|
||||
</div>
|
||||
</div>
|
||||
<div id="emailCommonArea">
|
||||
<h5 class="card-title">Notifications</h5>
|
||||
<p class="card-text">Enabling notifications will allow you to choose (per-invite) to recieve emails when an invite expires, or when a new user is created. If you chose to use Manual auth instead of Jellyfin auth previously, make sure you provided an email address.</p>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="notificationsEnabled">
|
||||
<label for="notificationsEnabled" class="form-check-label">Enabled</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
|
||||
<a class="btn btn-secondary backButton" href="#page-3">Back</a>
|
||||
<a class="btn btn-primary nextButton" id="emailNextButton" href="#page-5">Next</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide card" id="page-5">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Email</h5>
|
||||
<p class="card-text">Just a few more things to get your emails looking great.
|
||||
<div class="form-group">
|
||||
<label for="emailSender">Sender: The name shown when a user receives an email.</label>
|
||||
<input type="text" class="form-control" id="emailSender" value="Jellyfin">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="emailDateFormat">Date Format: Follows <a target="_blank" href="https://strftime.org/">strftime</a> format.</label>
|
||||
<input type="text" class="form-control" id="emailDateFormat" value="%d/%m/%y">
|
||||
</div>
|
||||
<div class="form-group form-check">
|
||||
<input class="form-check-input" type="radio" name="time" id="email24hTimeRadio" value="email24hTime" checked>
|
||||
<label class="form-check-label" for="email24hTimeRadio">24h time</label>
|
||||
</div>
|
||||
<div class="form-group form-check">
|
||||
<input class="form-check-input" type="radio" name="time" id="email12hTimeRadio" value="email12hTime">
|
||||
<label class="form-check-label" for="email12hTimeRadio">12h time</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="emailMessage">Message: Short message displayed at the bottom of emails.</label>
|
||||
<input type="text" class="form-control" id="emailMessage" value="Need help? Contact me.">
|
||||
</div>
|
||||
</p>
|
||||
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
|
||||
<a class="btn btn-secondary backButton" href="#page-4">Back</a>
|
||||
<a class="btn btn-primary nextButton" href="#page-6">Next</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide card" id="page-6">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Password Resets</h5>
|
||||
<p class="card-text">
|
||||
When a user tries to reset their password in jellyfin, it informs them that a file has been created, named "passwordreset*.json" where * is a number. jfa-go will then read this file, and send the PIN to the user's email. Try it now, and put the folder that it informs you it put the file in below. Also, if enter a custom email subject if you don't like the default one.
|
||||
</p>
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox" class="form-check-input" id="pwrEnabled" value="enabled">
|
||||
<label class="form-check-label" for="pwrEnabled">Enabled</label>
|
||||
</div>
|
||||
<div id="pwrArea">
|
||||
<div class="form-group">
|
||||
<label for="pwrJfPath">Path to Jellyfin</label>
|
||||
<input type="text" class="form-control" id="pwrJfPath" placeholder="Folder">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pwrSubject">Email Subject</label>
|
||||
<input type="text" class="form-control" id="pwrSubject" value="Password Reset - Jellyfin">
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
|
||||
<a class="btn btn-secondary backButton" href="#page-5">Back</a>
|
||||
<a class="btn btn-primary nextButton" href="#page-7">Next</a>
|
||||
</div>
|
||||
<img class="banner header" src="banner.svg" alt="jfa-go" />
|
||||
</div>
|
||||
<div class="row col flex center">
|
||||
<span class="heading welcome">{{ .lang.StartPage.welcome }}</span>
|
||||
</div>
|
||||
<div class="row col flex center">
|
||||
<p class="content">{{ .lang.StartPage.pressStart }}</p>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="support">{{ .lang.StartPage.httpsNotice }}</span>
|
||||
<span class="button ~urge !normal next">{{ .lang.StartPage.start }}</span>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<span class="heading">{{ .lang.Language.title }}</span>
|
||||
<p class="content" id="language-description"></p>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Language.defaultAdminLang }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<select id="ui-language-admin">
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Language.defaultFormLang }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<select id="ui-language-form">
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Language.defaultEmailLang }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<select id="email-language">
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<span class="heading">{{ .lang.General.title }}</span>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.General.listenAddress }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="ui-host" value="0.0.0.0">
|
||||
</label>
|
||||
<label class="row switch">
|
||||
<input type="checkbox" id="advanced-tls"><span>{{ .lang.General.useHTTPS }}</span>
|
||||
</label>
|
||||
<p class="support mb-1">{{ .lang.General.useHTTPSNotice }}</p>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.General.pathToCertificate }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="advanced-tls_cert">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.General.pathToKeyFile }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="advanced-tls_key">
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.port }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="ui-port" value="8056">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.General.httpsPort }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="advanced-tls_port" value="8057">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.General.urlBase }} ({{ .lang.Strings.optional }})</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half" id="ui-url_base">
|
||||
<p class="support mb-1">{{ .lang.General.urlBaseNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Strings.theme }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<select id="ui-theme">
|
||||
<option value="Jellyfin (Dark)">{{ .lang.General.darkTheme }}</option>
|
||||
<option value="Default (Light)">{{ .lang.General.lightTheme }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide card" id="page-7">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Invite Emails</h5>
|
||||
<p class="card-text">
|
||||
Allows you to send an invite code directly to a specified email address.
|
||||
Since you'll most likely being running this behind a reverse proxy, the program has no way of knowing the address it will be accessed from. This is needed for sending emails with links. Write your URL Base with the protocol and append '/invite', e.g:
|
||||
<ul>
|
||||
<li>On the local network, you might use <a href="#">http://localhost:8056/invite</a></li>
|
||||
<li>Exposed to the internet, you might use <a href="#">https://accounts.jellyf.in/invite</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox" class="form-check-input" id="invEnabled" value="enabled" checked>
|
||||
<label class="form-check-label" for="invEnabled">Enabled</label>
|
||||
</div>
|
||||
<div id="invArea">
|
||||
<div class="form-group">
|
||||
<label for="invURLBase">URL Base</label>
|
||||
<input type="url" class="form-control" id="invURLBase" placeholder="https://accounts.jellyf.in/invite">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="invSubject">Subject</label>
|
||||
<input type="text" class="form-control" id="invSubject" value="Invite - Jellyfin">
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
|
||||
<a class="btn btn-secondary backButton" href="#page-6">Back</a>
|
||||
<a class="btn btn-primary nextButton" href="#page-8">Next</a>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<span class="heading">{{ .lang.Login.title }}</span>
|
||||
<p class="content">{{ .lang.Login.description }}</p>
|
||||
<div class="pl-1">
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="ui-jellyfin_login" value="true" checked><span>{{ .lang.Login.authorizeWithJellyfin }}</span>
|
||||
</label>
|
||||
<label class="row switch pl-1 pb-1">
|
||||
<input type="checkbox" id="ui-admin_only"><span>{{ .lang.Login.adminOnly }}</span>
|
||||
</label>
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="ui-jellyfin_login" value="false"><span>{{ .lang.Login.authorizeManual }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="login-manual">
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" id="ui-username" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.username }}">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Strings.password }}</span>
|
||||
<input type="password" id="ui-password" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.password }}">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Strings.emailAddress }} ({{ .lang.Strings.optional }})</span>
|
||||
<input type="email" id="ui-email" class="input ~neutral !normal mt-half" placeholder="email@address">
|
||||
<span class="support mb-1">{{ .lang.Login.emailNotice }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<span class="heading">{{ .lang.JellyfinEmby.title }}</span>
|
||||
<p class="content">{{ .lang.JellyfinEmby.description }}</p>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span>{{ .lang.Strings.serverType }}</span>
|
||||
<div class="select ~neutral !normal mt-half">
|
||||
<select id="jellyfin-type">
|
||||
<option value="jellyfin">Jellyfin</option>
|
||||
<option value="emby">Emby</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slide card" id="page-8">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Password Validation</h5>
|
||||
<p class="card-text">
|
||||
Enabling this will display a set of password requirements on the create account page, such as minimum length, uppercase characters, special characters, etc.
|
||||
</p>
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox" class="form-check-input" id="valEnabled" value="enabled">
|
||||
<label class="form-check-label" for="valEnabled">Enabled</label>
|
||||
</div>
|
||||
<div id="valArea">
|
||||
<div class="form-group">
|
||||
<label for="valLength">Minimum Length</label>
|
||||
<input type="number" class="form-control" id="valLength" value="8">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="valUpper">Minimum number of uppercase characters</label>
|
||||
<input type="number" class="form-control" id="valUpper" value="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="valLower">Minimum number of lowercase characters</label>
|
||||
<input type="number" class="form-control" id="valLower" value="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="valNumber">Minimum number of numbers</label>
|
||||
<input type="number" class="form-control" id="valNumber" value="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="valSpecial">Minimum number of special characters</label>
|
||||
<input type="number" class="form-control" id="valSpecial" value="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
|
||||
<a class="btn btn-secondary backButton" id="valBackButton" href="#page-7">Back</a>
|
||||
<a class="btn btn-primary nextButton" href="#page-9">Next</a>
|
||||
</div>
|
||||
<p class="support mb-1">{{ .lang.JellyfinEmby.embyNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.JellyfinEmby.replaceJellyfin }} ({{ .lang.Strings.optional }})</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="jellyfin-substitute_jellyfin_strings">
|
||||
<p class="support mb-1">{{ .lang.JellyfinEmby.replaceJellyfinNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" id="jellyfin-username" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.username }}">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Strings.password }}</span>
|
||||
<input type="password" id="jellyfin-password" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.password }}">
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.internal }})</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="jellyfin-server" placeholder="http://jellyf.in:80">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.external }})</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half" id="jellyfin-public_server" placeholder="https://jellyf.in">
|
||||
<p class="support mb-1">{{ .lang.JellyfinEmby.addressExternalNotice }}</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal" id="jellyfin-test-connection">{{ .lang.JellyfinEmby.testConnection }}</span>
|
||||
<span class="button ~urge !normal next" disabled>{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<span class="heading">{{ .lang.Ombi.title }}</span>
|
||||
<p class="content">{{ .lang.Ombi.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="ombi-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.serverAddress }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="ombi-server" placeholder="ombi.jellyf.in">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.apiKey }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="ombi-api_key">
|
||||
<p class="support mb-1">{{ .lang.Ombi.apiKeyNotice }}</p>
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<span class="heading">{{ .lang.Email.title }}</span>
|
||||
<p class="content" id="email-description"></p>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span>{{ .lang.Email.method }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<select id="email-method">
|
||||
<option value="">{{ .lang.Strings.disabled }}</option>
|
||||
<option value="smtp">SMTP</option>
|
||||
<option value="mailgun">Mailgun</option>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="row switch">
|
||||
<input type="checkbox" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span>
|
||||
</label>
|
||||
<p class="support mb-1">{{ .lang.Email.useEmailAsUsernameNotice }}</p>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.fromAddress }}</span>
|
||||
<input type="email" class="input ~neutral !normal mt-half mb-1" id="email-address" placeholder="mail@jellyf.in">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.senderName }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="email-from" value="Jellyfin">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.dateFormat }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="email-date_format" value="%d/%m/%y">
|
||||
<p class="support mb-1" id="email-dateformat-notice"></p>
|
||||
</label>
|
||||
<div>
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="email-24h" value="true" checked><span>{{ .lang.Email.time24h }}</span>
|
||||
</label>
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="email-24h" value="false"><span>{{ .lang.Email.time12h }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="slide card" id="page-9">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Help Messages</h5>
|
||||
<p class="card-text">
|
||||
Just a few little messages that will display in various places. Leave these alone if you want.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="msgContact">Contact message: Displays at bottom of all pages (except admin).</label>
|
||||
<input id="msgContact" type="text" class="form-control" value="Need help? Contact me.">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="msgHelp">Help message: Displays when a user is creating an account.</label>
|
||||
<input id="msgHelp" type="text" class="form-control" value="Enter your details to create an account.">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="msgSuccess">Success message: Displays when a user successfully creates an account, just above a button taking the user to Jellyfin.</label>
|
||||
<input id="msgSuccess" type="text" class="form-control" value="Your account has been created. Click below to continue to Jellyfin.">
|
||||
</div>
|
||||
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
|
||||
<a class="btn btn-secondary backButton" href="#page-8">Back</a>
|
||||
<a class="btn btn-primary nextButton" href="#page-10">Next</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div id="email-smtp">
|
||||
<p class="subheading">SMTP</p>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Email.encryption }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<select id="smtp-encryption">
|
||||
<option value="starttls">STARTTLS ({{ .lang.Strings.port }} 587)</option>
|
||||
<option value="ssl_tls">SSL/TLS ({{ .lang.Strings.port }} 465)</option>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.serverAddress }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="smtp-server" placeholder="smtp.jellyf.in">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.port }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="smtp-port" placeholder="587">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="smtp-username">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.password }}</span>
|
||||
<input type="password" class="input ~neutral !normal mt-half mb-1" id="smtp-password">
|
||||
</label>
|
||||
</div>
|
||||
<div class="slide card" id="page-10">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">Finished!</h5>
|
||||
<p class="card-text">
|
||||
Press the button below to submit your settings. The program will restart. Once it's done, refresh this page.
|
||||
</p>
|
||||
<button id="submitButton" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
<div id="email-mailgun">
|
||||
<p class="subheading">Mailgun</p>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.mailgunApiURL }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.apiKey }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_key">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm"></div>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused related-to-email">
|
||||
<span class="heading">{{ .lang.Notifications.title }}</span>
|
||||
<p class="content">{{ .lang.Notifications.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="notifications-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<span class="heading">{{ .lang.WelcomeEmails.title }}</span>
|
||||
<p class="content">{{ .lang.WelcomeEmails.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="welcome_email-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.emailSubject }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="welcome_email-subject" placeholder="{{ .emailLang.WelcomeEmail.title }}">
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused related-to-email">
|
||||
<span class="heading">{{ .lang.InviteEmails.title }}</span>
|
||||
<p class="content">{{ .lang.InviteEmails.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="invite_emails-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.URL }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="invite_emails-url_base" placeholder="https://accounts.jellyf.in/invite">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.emailSubject }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="invite_emails-subject" placeholder="{{ .emailLang.InviteEmail.title }}">
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused related-to-email">
|
||||
<span class="heading">{{ .lang.PasswordResets.title }}</span>
|
||||
<p class="content">{{ .lang.PasswordResets.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="password_resets-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordResets.pathToJellyfin }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="password_resets-watch_directory" placeholder="/config/jellyfin">
|
||||
<p class="support mb-1">{{ .lang.PasswordResets.pathToJellyfinNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.emailSubject }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="password_resets-subject" placeholder="{{ .emailLang.PasswordReset.title }}">
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<span class="heading">{{ .lang.PasswordValidation.title }}</span>
|
||||
<p class="content">{{ .lang.PasswordValidation.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="password_validation-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordValidation.length }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-min_length" value="8">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordValidation.uppercase }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-upper" value="1">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordValidation.lowercase }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-lower" value="0">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordValidation.numbers }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-number" value="0">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordValidation.special }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-special" value="0">
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<span class="heading">{{ .lang.HelpMessages.title }}</span>
|
||||
<p class="content">{{ .lang.HelpMessages.description }}</p>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.HelpMessages.contactMessage }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="ui-contact_message">
|
||||
<p class="support mb-1">{{ .lang.HelpMessages.contactMessageNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.HelpMessages.helpMessage }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="ui-help_message">
|
||||
<p class="support mb-1">{{ .lang.HelpMessages.helpMessageNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.HelpMessages.successMessage }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="ui-success_message">
|
||||
<p class="support mb-1">{{ .lang.HelpMessages.successMessageNotice }}</p>
|
||||
</label>
|
||||
<label class="label related-to-email">
|
||||
<span class="mt-half">{{ .lang.HelpMessages.emailMessage }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="email-message">
|
||||
<p class="support mb-1">{{ .lang.HelpMessages.emailMessageNotice }}</p>
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<div class="row col flex center">
|
||||
<span class="heading">{{ .lang.EndPage.finished }}</span>
|
||||
</div>
|
||||
<div class="row col flex center">
|
||||
<p class="content">{{ .lang.EndPage.restartMessage }}</p>
|
||||
</div>
|
||||
<div class="row col flex center">
|
||||
<span class="button ~neutral !normal back mr-1">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge !normal" id="restart">{{ .lang.Strings.submit }}</span>
|
||||
<span class="button ~urge !normal unfocused" id="refresh">{{ .lang.EndPage.refreshPage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/setup.js"></script>
|
||||
<script>
|
||||
window.langFile = JSON.parse({{ .language }});
|
||||
window.messages = JSON.parse({{ .messages }});
|
||||
</script>
|
||||
<script src="js/setup.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Deutsch (DE)"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Benutzername",
|
||||
"password": "Passwort",
|
||||
"emailAddress": "E-Mail-Adresse",
|
||||
"submit": "Absenden",
|
||||
"success": "Erfolg",
|
||||
"error": "Fehler",
|
||||
"theme": "Thema"
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "English (US)"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"emailAddress": "Email Address",
|
||||
"submit": "Submit",
|
||||
"success": "Success",
|
||||
"error": "Error",
|
||||
"theme": "Theme"
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Francais (FR)",
|
||||
"author": "https://github.com/Killianbe"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Nom d'utilisateur",
|
||||
"password": "Mot de passe",
|
||||
"emailAddress": "Addresse Email",
|
||||
"submit": "Soumettre",
|
||||
"success": "Succès",
|
||||
"error": "Erreur",
|
||||
"theme": "Thème"
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Nederlands (NL)"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Gebruikersnaam",
|
||||
"password": "Wachtwoord",
|
||||
"emailAddress": "E-mailadres",
|
||||
"submit": "Verstuur",
|
||||
"success": "Success",
|
||||
"error": "Fout",
|
||||
"theme": "Thema"
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "English (US)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Setup - jfa-go",
|
||||
"next": "Next",
|
||||
"back": "Back",
|
||||
"optional": "Optional",
|
||||
"serverType": "Server Type",
|
||||
"disabled": "Disabled",
|
||||
"enabled": "Enabled",
|
||||
"port": "Port",
|
||||
"message": "Message",
|
||||
"serverAddress": "Server Address",
|
||||
"emailSubject": "Email Subject",
|
||||
"URL": "URL",
|
||||
"apiKey": "API Key"
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "Welcome!",
|
||||
"pressStart": "You'll need to do a few things to set up jfa-go. Press start to get continue.",
|
||||
"httpsNotice": "Make sure you're accessing this page via HTTPS or on a private network.",
|
||||
"start": "Start"
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "Finished!",
|
||||
"restartMessage": "There are more settings you can configure on the admin page. Click below to restart, then refresh the page.",
|
||||
"refreshPage": "Refresh"
|
||||
},
|
||||
"language": {
|
||||
"title": "Language",
|
||||
"description": "Community translations are available for most parts of jfa-go. You can choose the default languages below, but users can still change it if they wish. If you want to help translate, sign up to {n} to start contributing!",
|
||||
"defaultAdminLang": "Default admin language",
|
||||
"defaultFormLang": "Default account creation language",
|
||||
"defaultEmailLang": "Default email language"
|
||||
},
|
||||
"general": {
|
||||
"title": "General",
|
||||
"listenAddress": "Listen Address",
|
||||
"urlBase": "URL Base",
|
||||
"urlBaseNotice": "Only needed if using a reverse proxy on a subdomain (e.g 'jellyf.in/accounts').",
|
||||
"lightTheme": "Light",
|
||||
"darkTheme": "Dark",
|
||||
"useHTTPS": "Use HTTPS",
|
||||
"httpsPort": "HTTPS Port",
|
||||
"useHTTPSNotice": "Only recommended if you aren't using a reverse proxy.",
|
||||
"pathToCertificate": "Path to certificate",
|
||||
"pathToKeyFile": "Path to key file"
|
||||
},
|
||||
"login": {
|
||||
"title": "Login",
|
||||
"description": "To access the admin page, you need to login with a method below:",
|
||||
"authorizeWithJellyfin": "Authorize with Jellyfin/Emby: Login details are shared with Jellyfin, which allows for multiple users.",
|
||||
"authorizeManual": "Username and Password: Manually set the username and password.",
|
||||
"adminOnly": "Admin users only (recommended)",
|
||||
"emailNotice": "Your email address can be used to receive notifications."
|
||||
},
|
||||
"jellyfinEmby": {
|
||||
"title": "Jellyfin/Emby",
|
||||
"description": "An admin account is needed because the API does not allow user creation using an API key. You should create a separate account and check 'Allow this user to manage the server'. You can disable everything else. Once done, enter the login details here.",
|
||||
"embyNotice": "Emby support is limited and does not support password resets.",
|
||||
"internal": "Internal",
|
||||
"external": "External",
|
||||
"replaceJellyfin": "Server name",
|
||||
"replaceJellyfinNotice": "If given, this will replace any occurrence of 'Jellyfin' in the app.",
|
||||
"addressExternalNotice": "Leave blank to use the same address.",
|
||||
"testConnection": "Test Connection"
|
||||
},
|
||||
"ombi": {
|
||||
"title": "Ombi",
|
||||
"description": "By connecting to Ombi, both a Jellyfin and Ombi account will be created when a user joins through jfa-go. After setup if finished, go to Settings to set a default profile for new ombi users.",
|
||||
"apiKeyNotice": "Find this in the first tab of Ombi settings."
|
||||
},
|
||||
"email": {
|
||||
"title": "Email",
|
||||
"description": "jfa-go can send password reset PINs and various notifications through email. You can connect to an SMTP server, or use the {n} API.",
|
||||
"method": "Sending method",
|
||||
"useEmailAsUsername": "Use email addresses as username",
|
||||
"useEmailAsUsernameNotice": "If enabled, new users will login to Jellyfin/Emby with their email address instead of a username.",
|
||||
"fromAddress": "From Address",
|
||||
"senderName": "Sender Name",
|
||||
"dateFormat": "Date Format",
|
||||
"dateFormatNotice": "Date follows the strftime format. For more info, visit {n}.",
|
||||
"time24h": "24h Time",
|
||||
"time12h": "12h Time",
|
||||
"encryption": "Encryption",
|
||||
"mailgunApiURL": "API URL"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"description": "If enabled, you can choose (per invite) to receive an email when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address."
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "Welcome emails",
|
||||
"description": "If enabled, an email will be sent to new users with the Jellyfin/Emby URL and their username."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "Invite Emails",
|
||||
"description": "If enabled, you can send invites directly to a user's email address. Because you might be using a reverse proxy, you need to provide the URL invites are accessed from. Write your URL Base, and append '/invite'."
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "Password Resets",
|
||||
"description": "When a user tries to reset their password, Jellyfin creates a file named 'passwordreset-*.json' which contains a PIN. jfa-go reads the file and sends the PIN to the user.",
|
||||
"pathToJellyfin": "Path to Jellyfin configuration directory",
|
||||
"pathToJellyfinNotice": "If you don't know where this is, try resetting your password in Jellyfin. A popup with '<path to jellyfin>/passwordreset-*.json' will appear."
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "Password Validation",
|
||||
"description": "If enabled, a set of password requirements will show on the account creation page, such as minimum length, uppercase/lowercase characters, etc.",
|
||||
"length": "Length",
|
||||
"uppercase": "Uppercase characters",
|
||||
"lowercase": "Lowercase characters",
|
||||
"numbers": "Numbers",
|
||||
"special": "Special characters (%, *, etc.)"
|
||||
},
|
||||
"helpMessages": {
|
||||
"title": "Help Messages",
|
||||
"description": "These messages will display in the account creation page and in some emails.",
|
||||
"contactMessage": "Contact Message",
|
||||
"contactMessageNotice": "Displays at the bottom of all pages except admin.",
|
||||
"helpMessage": "Help Message",
|
||||
"helpMessageNotice": "Displays on the account creation page.",
|
||||
"successMessage": "Success Message",
|
||||
"successMessageNotice": "Displays when a user creates their account.",
|
||||
"emailMessage": "Email Message",
|
||||
"emailMessageNotice": "Displays at the bottom of emails."
|
||||
}
|
||||
}
|
@ -1,260 +1,479 @@
|
||||
// Lord forgive me for this mess, i'll fix it one day i swear
|
||||
import { _get, _post, toggleLoader } from "./modules/common.js";
|
||||
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
||||
|
||||
document.getElementById("page-1").scrollIntoView({
|
||||
behavior: "auto",
|
||||
block: "center",
|
||||
inline: "center"
|
||||
});
|
||||
interface sWindow extends Window {
|
||||
messages: {};
|
||||
}
|
||||
|
||||
const checkAuthRadio = () => {
|
||||
if ((document.getElementById('manualAuthRadio') as HTMLInputElement).checked) {
|
||||
document.getElementById('adminOnlyArea').style.display = 'none';
|
||||
document.getElementById('manualAuthArea').style.display = '';
|
||||
} else {
|
||||
document.getElementById('manualAuthArea').style.display = 'none';
|
||||
document.getElementById('adminOnlyArea').style.display = '';
|
||||
}
|
||||
};
|
||||
declare var window: sWindow;
|
||||
window.URLBase = "";
|
||||
|
||||
for (let radio of ['manualAuthRadio', 'jfAuthRadio']) {
|
||||
document.getElementById(radio).addEventListener('change', checkAuthRadio);
|
||||
};
|
||||
const get = (id: string): HTMLElement => document.getElementById(id);
|
||||
const text = (id: string, val: string) => { document.getElementById(id).textContent = val; };
|
||||
const html = (id: string, val: string) => { document.getElementById(id).innerHTML = val; };
|
||||
|
||||
interface boolEvent extends Event {
|
||||
detail: boolean;
|
||||
}
|
||||
|
||||
const checkEmailRadio = () => {
|
||||
(document.getElementById('emailNextButton') as HTMLAnchorElement).href = '#page-5';
|
||||
(document.getElementById('valBackButton') as HTMLAnchorElement).href = '#page-7';
|
||||
if ((document.getElementById('emailSMTPRadio') as HTMLInputElement).checked) {
|
||||
document.getElementById('emailCommonArea').style.display = '';
|
||||
document.getElementById('emailSMTPArea').style.display = '';
|
||||
document.getElementById('emailMailgunArea').style.display = 'none';
|
||||
(document.getElementById('notificationsEnabled') as HTMLInputElement).checked = true;
|
||||
} else if ((document.getElementById('emailMailgunRadio') as HTMLInputElement).checked) {
|
||||
document.getElementById('emailCommonArea').style.display = '';
|
||||
document.getElementById('emailSMTPArea').style.display = 'none';
|
||||
document.getElementById('emailMailgunArea').style.display = '';
|
||||
(document.getElementById('notificationsEnabled') as HTMLInputElement).checked = true;
|
||||
} else if ((document.getElementById('emailDisabledRadio') as HTMLInputElement).checked) {
|
||||
document.getElementById('emailCommonArea').style.display = 'none';
|
||||
document.getElementById('emailSMTPArea').style.display = 'none';
|
||||
document.getElementById('emailMailgunArea').style.display = 'none';
|
||||
(document.getElementById('emailNextButton') as HTMLAnchorElement).href = '#page-8';
|
||||
(document.getElementById('valBackButton') as HTMLAnchorElement).href = '#page-4';
|
||||
(document.getElementById('notificationsEnabled') as HTMLInputElement).checked = false;
|
||||
class Input {
|
||||
private _el: HTMLInputElement;
|
||||
get value(): string { return ""+this._el.value; }
|
||||
set value(v: string) { this._el.value = v; }
|
||||
// Nothing depends on input, but we add an empty broadcast function so we can just loop over all settings to fix dependents on start.
|
||||
broadcast = () => {}
|
||||
constructor(el: HTMLElement, placeholder?: any, value?: any, depends?: string, dependsTrue?: boolean, section?: string) {
|
||||
this._el = el as HTMLInputElement;
|
||||
if (placeholder) { this._el.placeholder = placeholder; }
|
||||
if (value) { this.value = value; }
|
||||
if (depends) {
|
||||
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
||||
let el = this._el as HTMLElement;
|
||||
if (el.parentElement.tagName == "LABEL") { el = el.parentElement; }
|
||||
if (event.detail !== dependsTrue) {
|
||||
el.classList.add("unfocused");
|
||||
} else {
|
||||
el.classList.remove("unfocused");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (let radio of ['emailDisabledRadio', 'emailSMTPRadio', 'emailMailgunRadio']) {
|
||||
document.getElementById(radio).addEventListener('change', checkEmailRadio);
|
||||
class Checkbox {
|
||||
private _el: HTMLInputElement;
|
||||
get value(): string { return this._el.checked ? "true" : "false"; }
|
||||
set value(v: string) { this._el.checked = (v == "true") ? true : false; }
|
||||
|
||||
private _section: string;
|
||||
private _setting: string;
|
||||
broadcast = () => {
|
||||
if (this._section && this._setting) {
|
||||
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": this._el.checked })
|
||||
document.dispatchEvent(ev);
|
||||
}
|
||||
}
|
||||
constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
||||
this._el = el as HTMLInputElement;
|
||||
if (section && setting) {
|
||||
this._section = section;
|
||||
this._setting = setting;
|
||||
this._el.onchange = this.broadcast;
|
||||
}
|
||||
if (depends) {
|
||||
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
||||
let el = this._el as HTMLElement;
|
||||
if (el.parentElement.tagName == "LABEL") { el = el.parentElement; }
|
||||
if (event.detail !== dependsTrue) {
|
||||
el.classList.add("unfocused");
|
||||
} else {
|
||||
el.classList.remove("unfocused");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const checkSSL = () => {
|
||||
var label = document.getElementById('emailSSL_TLSLabel');
|
||||
if ((document.getElementById('emailSSL_TLS') as HTMLInputElement).checked) {
|
||||
label.textContent = 'Use SSL/TLS';
|
||||
} else {
|
||||
label.textContent = 'Use STARTTLS';
|
||||
class BoolRadios {
|
||||
private _els: NodeListOf<HTMLInputElement>;
|
||||
get value(): string { return this._els[0].checked ? "true" : "false" }
|
||||
set value(v: string) {
|
||||
const bool = (v == "true") ? true : false;
|
||||
this._els[0].checked = bool;
|
||||
this._els[1].checked = !bool;
|
||||
}
|
||||
};
|
||||
document.getElementById('emailSSL_TLS').addEventListener('change', checkSSL);
|
||||
|
||||
private _section: string;
|
||||
private _setting: string;
|
||||
broadcast = () => {
|
||||
if (this._section && this._setting) {
|
||||
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": this._els[0].checked })
|
||||
document.dispatchEvent(ev);
|
||||
}
|
||||
}
|
||||
constructor(name: string, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
||||
this._els = document.getElementsByName(name) as NodeListOf<HTMLInputElement>;
|
||||
if (section && setting) {
|
||||
this._section = section;
|
||||
this._setting = setting;
|
||||
this._els[0].onchange = this.broadcast;
|
||||
this._els[1].onchange = this.broadcast;
|
||||
}
|
||||
if (depends) {
|
||||
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
||||
if (event.detail !== dependsTrue) {
|
||||
if (this._els[0].parentElement.tagName == "LABEL") {
|
||||
this._els[0].parentElement.classList.add("unfocused");
|
||||
}
|
||||
if (this._els[1].parentElement.tagName == "LABEL") {
|
||||
this._els[1].parentElement.classList.add("unfocused");
|
||||
}
|
||||
} else {
|
||||
if (this._els[0].parentElement.tagName == "LABEL") {
|
||||
this._els[0].parentElement.classList.remove("unfocused");
|
||||
}
|
||||
if (this._els[1].parentElement.tagName == "LABEL") {
|
||||
this._els[1].parentElement.classList.remove("unfocused");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pwrEnabled = document.getElementById('pwrEnabled') as HTMLInputElement;
|
||||
const checkPwrEnabled = () => {
|
||||
if (pwrEnabled.checked) {
|
||||
document.getElementById('pwrArea').style.display = '';
|
||||
} else {
|
||||
document.getElementById('pwrArea').style.display = 'none';
|
||||
// class Radios {
|
||||
// private _el: HTMLInputElement;
|
||||
// get value(): string { return this._el.value; }
|
||||
// set value(v: string) { this._el.value = v; }
|
||||
// constructor(name: string, depends?: string, dependsTrue?: boolean, section?: string) {
|
||||
// this._el = document.getElementsByName(name)[0] as HTMLInputElement;
|
||||
// if (depends) {
|
||||
// document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
||||
// let el = this._el as HTMLElement;
|
||||
// if (el.parentElement.tagName == "LABEL") { el = el.parentElement; }
|
||||
// if (event.detail !== dependsTrue) {
|
||||
// el.classList.add("unfocused");
|
||||
// } else {
|
||||
// el.classList.remove("unfocused");
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
class Select {
|
||||
private _el: HTMLSelectElement;
|
||||
get value(): string { return this._el.value; }
|
||||
set value(v: string) { this._el.value = v; }
|
||||
add = (val: string, label: string) => {
|
||||
const item = document.createElement("option") as HTMLOptionElement;
|
||||
item.value = val;
|
||||
item.textContent = label;
|
||||
this._el.appendChild(item);
|
||||
}
|
||||
};
|
||||
pwrEnabled.addEventListener('change', checkPwrEnabled);
|
||||
set onchange(f: () => void) {
|
||||
this._el.addEventListener("change", f);
|
||||
}
|
||||
|
||||
private _section: string;
|
||||
private _setting: string;
|
||||
broadcast = () => {
|
||||
if (this._section && this._setting) {
|
||||
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": this.value ? true : false })
|
||||
document.dispatchEvent(ev);
|
||||
}
|
||||
}
|
||||
constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
||||
this._el = el as HTMLSelectElement;
|
||||
if (section && setting) {
|
||||
this._section = section;
|
||||
this._setting = setting;
|
||||
this._el.addEventListener("change", this.broadcast);
|
||||
}
|
||||
if (depends) {
|
||||
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
||||
let el = this._el as HTMLElement;
|
||||
if (el.parentElement.tagName == "LABEL") { el = el.parentElement; }
|
||||
if (event.detail !== dependsTrue) {
|
||||
el.classList.add("unfocused");
|
||||
} else {
|
||||
el.classList.remove("unfocused");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var invEnabled = document.getElementById("invEnabled") as HTMLInputElement;
|
||||
const checkInvEnabled = () => {
|
||||
if (invEnabled.checked) {
|
||||
document.getElementById('invArea').style.display = '';
|
||||
} else {
|
||||
document.getElementById('invArea').style.display = 'none';
|
||||
class LangSelect extends Select {
|
||||
constructor(page: string, el: HTMLElement) {
|
||||
super(el);
|
||||
_get("/lang/" + page, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4 && req.status == 200) {
|
||||
for (let code in req.response) {
|
||||
this.add(code, req.response[code]);
|
||||
}
|
||||
this.value = "en-us";
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
invEnabled.addEventListener('change', checkInvEnabled);
|
||||
}
|
||||
|
||||
var valEnabled = document.getElementById("valEnabled") as HTMLInputElement;
|
||||
const checkValEnabled = () => {
|
||||
const valArea = document.getElementById("valArea");
|
||||
if (valEnabled.checked) {
|
||||
valArea.style.display = '';
|
||||
} else {
|
||||
valArea.style.display = 'none';
|
||||
window.lang = new lang(window.langFile as LangFile);
|
||||
html("language-description", window.lang.var("language", "description", `<a href="https://weblate.hrfee.pw">Weblate</a>`));
|
||||
html("email-description", window.lang.var("email", "description", `<a href="https://mailgun.com">Mailgun</a>`));
|
||||
|
||||
const settings = {
|
||||
"jellyfin": {
|
||||
"type": new Select(get("jellyfin-type")),
|
||||
"server": new Input(get("jellyfin-server")),
|
||||
"public_server": new Input(get("jellyfin-public_server")),
|
||||
"username": new Input(get("jellyfin-username")),
|
||||
"password": new Input(get("jellyfin-password")),
|
||||
"substitute_jellyfin_strings": new Input(get("jellyfin-substitute_jellyfin_strings"))
|
||||
},
|
||||
"ui": {
|
||||
"host": new Input(get("ui-host")),
|
||||
"port": new Input(get("ui-port")),
|
||||
"url_base": new Input(get("ui-url_base")),
|
||||
"theme": new Select(get("ui-theme")),
|
||||
"language-form": new LangSelect("form", get("ui-language-form")),
|
||||
"language-admin": new LangSelect("admin", get("ui-language-admin")),
|
||||
"jellyfin_login": new BoolRadios("ui-jellyfin_login", "", false, "ui", "jellyfin_login"),
|
||||
"admin_only": new Checkbox(get("ui-admin_only"), "jellyfin_login", true, "ui"),
|
||||
"username": new Input(get("ui-username"), "", "", "jellyfin_login", false, "ui"),
|
||||
"password": new Input(get("ui-password"), "", "", "jellyfin_login", false, "ui"),
|
||||
"email": new Input(get("ui-email"), "", "", "jellyfin_login", false, "ui"),
|
||||
"contact_message": new Input(get("ui-contact_message"), window.messages["ui"]["contact_message"]),
|
||||
"help_message": new Input(get("ui-help_message"), window.messages["ui"]["help_message"]),
|
||||
"success_message": new Input(get("ui-success_message"), window.messages["ui"]["success_message"])
|
||||
},
|
||||
"password_validation": {
|
||||
"enabled": new Checkbox(get("password_validation-enabled"), "", false, "password_validation", "enabled"),
|
||||
"min_length": new Input(get("password_validation-min_length"), "", 8, "enabled", true, "password_validation"),
|
||||
"upper": new Input(get("password_validation-upper"), "", 1, "enabled", true, "password_validation"),
|
||||
"lower": new Input(get("password_validation-lower"), "", 0, "enabled", true, "password_validation"),
|
||||
"number": new Input(get("password_validation-number"), "", 1, "enabled", true, "password_validation"),
|
||||
"special": new Input(get("password_validation-special"), "", 0, "enabled", true, "password_validation")
|
||||
},
|
||||
"email": {
|
||||
"language": new LangSelect("email", get("email-language")),
|
||||
"no_username": new Checkbox(get("email-no_username"), "method", true, "email"),
|
||||
"use_24h": new BoolRadios("email-24h", "method", true, "email"),
|
||||
"date_format": new Input(get("email-date_format"), "", "%d/%m/%y", "method", true, "email"),
|
||||
"message": new Input(get("email-message"), window.messages["email"]["message"], "", "method", true, "email"),
|
||||
"method": new Select(get("email-method"), "", false, "email", "method"),
|
||||
"address": new Input(get("email-address"), "jellyfin@jellyf.in", "", "method", true, "email"),
|
||||
"from": new Input(get("email-from"), "", "Jellyfin", "method", true, "email")
|
||||
},
|
||||
"password_resets": {
|
||||
"enabled": new Checkbox(get("password_resets-enabled"), "", false, "password_resets", "enabled"),
|
||||
"watch_directory": new Input(get("password_resets-watch_directory"), "", "", "enabled", true, "password_resets"),
|
||||
"subject": new Input(get("password_resets-subject"), "", "", "enabled", true, "password_resets")
|
||||
},
|
||||
"notifications": {
|
||||
"enabled": new Checkbox(get("notifications-enabled"))
|
||||
},
|
||||
"welcome_email": {
|
||||
"enabled": new Checkbox(get("welcome_email-enabled"), "", false, "welcome_email", "enabled"),
|
||||
"subject": new Input(get("welcome_email-subject"), "", "", "enabled", true, "welcome_email")
|
||||
},
|
||||
"invite_emails": {
|
||||
"enabled": new Checkbox(get("invite_emails-enabled"), "", false, "invite_emails", "enabled"),
|
||||
"subject": new Input(get("invite_emails-subject"), "", "", "enabled", true, "invite_emails"),
|
||||
"url_base": new Input(get("invite_emails-url_base"), "", "", "enabled", true, "invite_emails")
|
||||
},
|
||||
"mailgun": {
|
||||
"api_url": new Input(get("mailgun-api_url")),
|
||||
"api_key": new Input(get("mailgun-api_key"))
|
||||
},
|
||||
"smtp": {
|
||||
"username": new Input(get("smtp-username")),
|
||||
"encryption": new Select(get("smtp-encryption")),
|
||||
"server": new Input(get("smtp-server")),
|
||||
"port": new Input(get("smtp-port")),
|
||||
"password": new Input(get("smtp-password"))
|
||||
},
|
||||
"ombi": {
|
||||
"enabled": new Checkbox(get("ombi-enabled"), "", false, "ombi", "enabled"),
|
||||
"server": new Input(get("ombi-server"), "", "", "enabled", true, "ombi"),
|
||||
"api_key": new Input(get("ombi-api_key"), "", "", "enabled", true, "ombi")
|
||||
},
|
||||
"advanced": {
|
||||
"tls": new Checkbox(get("advanced-tls"), "", false, "advanced", "tls"),
|
||||
"tls_port": new Input(get("advanced-tls_port"), "", "", "tls", true, "advanced"),
|
||||
"tls_cert": new Input(get("advanced-tls_cert"), "", "", "tls", true, "advanced"),
|
||||
"tls_key": new Input(get("advanced-tls_key"), "", "", "tls", true, "advanced")
|
||||
}
|
||||
};
|
||||
valEnabled.addEventListener('change', checkValEnabled);
|
||||
|
||||
checkValEnabled();
|
||||
checkInvEnabled();
|
||||
checkSSL();
|
||||
checkAuthRadio();
|
||||
checkEmailRadio();
|
||||
checkPwrEnabled();
|
||||
(() => {
|
||||
const checkTheme = () => {
|
||||
if (settings["ui"]["theme"].value.includes("Dark")) {
|
||||
document.documentElement.classList.add("dark-theme");
|
||||
document.documentElement.classList.remove("light-theme");
|
||||
} else {
|
||||
document.documentElement.classList.add("light-theme");
|
||||
document.documentElement.classList.remove("dark-theme");
|
||||
}
|
||||
};
|
||||
settings["ui"]["theme"].onchange = checkTheme;
|
||||
checkTheme();
|
||||
})();
|
||||
|
||||
var jfValid = false
|
||||
document.getElementById('jfTestButton').onclick = () => {
|
||||
let testButton = document.getElementById('jfTestButton') as HTMLInputElement;
|
||||
let nextButton = document.getElementById('jfNextButton') as HTMLAnchorElement;
|
||||
let jfData = {};
|
||||
jfData['jfHost'] = (document.getElementById('jfHost') as HTMLInputElement).value;
|
||||
jfData['jfUser'] = (document.getElementById('jfUser') as HTMLInputElement).value;
|
||||
jfData['jfPassword'] = (document.getElementById('jfPassword') as HTMLInputElement).value;
|
||||
let valid = true;
|
||||
for (let val in jfData) {
|
||||
if (jfData[val] == "") {
|
||||
valid = false;
|
||||
|
||||
const restartButton = document.getElementById("restart") as HTMLSpanElement;
|
||||
const serialize = () => {
|
||||
toggleLoader(restartButton);
|
||||
let config = {};
|
||||
for (let section in settings) {
|
||||
config[section] = {};
|
||||
for (let setting in settings[section]) {
|
||||
if (settings[section][setting].value) {
|
||||
config[section][setting] = settings[section][setting].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!valid) {
|
||||
if (!testButton.classList.contains('btn-danger')) {
|
||||
testButton.classList.add('btn-danger');
|
||||
testButton.textContent = 'Fill out fields above.';
|
||||
setTimeout(function() {
|
||||
if (testButton.classList.contains('btn-danger')) {
|
||||
testButton.classList.remove('btn-danger');
|
||||
testButton.textContent = 'Test';
|
||||
}
|
||||
}, 2000);
|
||||
config["restart-program"] = true;
|
||||
_post("/config", config, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(restartButton);
|
||||
restartButton.parentElement.querySelector("span.back").classList.add("unfocused");
|
||||
restartButton.classList.add("unfocused");
|
||||
const refresh = document.getElementById("refresh") as HTMLSpanElement;
|
||||
refresh.classList.remove("unfocused");
|
||||
refresh.onclick = () => {
|
||||
let host = window.location.href.split("#")[0].split("?")[0] + settings["ui"]["url_base"].value;
|
||||
window.location.href = host;
|
||||
};
|
||||
}
|
||||
}, true, () => {});
|
||||
}
|
||||
restartButton.onclick = serialize;
|
||||
|
||||
const relatedToEmail = Array.from(document.getElementsByClassName("related-to-email"));
|
||||
const emailMethodChange = () => {
|
||||
const val = settings["email"]["method"].value;
|
||||
const smtp = document.getElementById("email-smtp");
|
||||
const mailgun = document.getElementById("email-mailgun");
|
||||
if (val == "smtp") {
|
||||
smtp.classList.remove("unfocused");
|
||||
mailgun.classList.add("unfocused");
|
||||
for (let el of relatedToEmail) {
|
||||
el.classList.remove("hidden");
|
||||
}
|
||||
} else if (val == "mailgun") {
|
||||
mailgun.classList.remove("unfocused");
|
||||
smtp.classList.add("unfocused");
|
||||
for (let el of relatedToEmail) {
|
||||
el.classList.remove("hidden");
|
||||
}
|
||||
} else {
|
||||
testButton.disabled = true;
|
||||
testButton.innerHTML =
|
||||
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="margin-right: 0.5rem;"></span>' +
|
||||
'Testing...';
|
||||
nextButton.classList.add('disabled');
|
||||
nextButton.setAttribute('aria-disabled', 'true');
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("POST", "/jellyfin/test", true);
|
||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
req.responseType = 'json';
|
||||
req.onreadystatechange = function() {
|
||||
if (this.readyState == 4) {
|
||||
testButton.disabled = false;
|
||||
testButton.className = '';
|
||||
if (this.response['success'] == true) {
|
||||
testButton.classList.add('btn', 'btn-success');
|
||||
testButton.textContent = 'Success';
|
||||
nextButton.classList.remove('disabled');
|
||||
nextButton.setAttribute('aria-disabled', 'false');
|
||||
} else {
|
||||
testButton.classList.add('btn', 'btn-danger');
|
||||
testButton.textContent = 'Failed';
|
||||
};
|
||||
};
|
||||
};
|
||||
req.send(JSON.stringify(jfData));
|
||||
mailgun.classList.add("unfocused");
|
||||
smtp.classList.add("unfocused");
|
||||
for (let el of relatedToEmail) {
|
||||
el.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
};
|
||||
settings["email"]["method"].onchange = emailMethodChange;
|
||||
emailMethodChange();
|
||||
|
||||
document.getElementById('submitButton').onclick = () => {
|
||||
const submitButton = document.getElementById('submitButton') as HTMLInputElement;
|
||||
submitButton.disabled = true;
|
||||
submitButton.innerHTML =`
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="margin-right: 0.5rem;"></span>
|
||||
Submitting...
|
||||
`;
|
||||
let config = {};
|
||||
config['jellyfin'] = {};
|
||||
config['ui'] = {};
|
||||
config['password_validation'] = {};
|
||||
config['email'] = {};
|
||||
config['password_resets'] = {};
|
||||
config['invite_emails'] = {};
|
||||
config['mailgun'] = {};
|
||||
config['smtp'] = {};
|
||||
config['notifications'] = {};
|
||||
// Page 2: Auth
|
||||
if ((document.getElementById('jfAuthRadio') as HTMLInputElement).checked) {
|
||||
config['ui']['jellyfin_login'] = 'true';
|
||||
config['ui']['admin_only'] = ""+(document.getElementById("jfAuthAdminOnly") as HTMLInputElement).checked;
|
||||
} else {
|
||||
config['ui']['username'] = (document.getElementById('manualAuthUsername') as HTMLInputElement).value;
|
||||
config['ui']['password'] = (document.getElementById('manualAuthPassword') as HTMLInputElement).value;
|
||||
config['ui']['email'] = (document.getElementById('manualAuthEmail') as HTMLInputElement).value;
|
||||
};
|
||||
// Page 3: Connect to jellyfin
|
||||
config['jellyfin']['server'] = (document.getElementById('jfHost') as HTMLInputElement).value;
|
||||
let publicAddress = (document.getElementById('jfPublicHost') as HTMLInputElement).value;
|
||||
if (publicAddress != "") {
|
||||
config['jellyfin']['public_server'] = publicAddress;
|
||||
(window as any).settings = settings;
|
||||
|
||||
for (let section in settings) {
|
||||
for (let setting in settings[section]) {
|
||||
settings[section][setting].broadcast();
|
||||
}
|
||||
config['jellyfin']['username'] = (document.getElementById('jfUser') as HTMLInputElement).value;
|
||||
config['jellyfin']['password'] = (document.getElementById('jfPassword') as HTMLInputElement).value;
|
||||
// Page 4: Email (Page 5, 6, 7 are only used if this is enabled)
|
||||
if ((document.getElementById('emailDisabledRadio') as HTMLInputElement).checked) {
|
||||
config['password_resets']['enabled'] = 'false';
|
||||
config['invite_emails']['enabled'] = 'false';
|
||||
config['notifications']['enabled'] = 'false';
|
||||
} else {
|
||||
if ((document.getElementById('emailSMTPRadio') as HTMLInputElement).checked) {
|
||||
config['smtp']['encryption'] = (document.getElementById('emailSSL_TLS') as HTMLInputElement).checked ? "ssl_tls" : "starttls";
|
||||
config['email']['method'] = 'smtp';
|
||||
config['smtp']['server'] = (document.getElementById('emailSMTPServer') as HTMLInputElement).value;
|
||||
config['smtp']['port'] = (document.getElementById('emailSMTPPort') as HTMLInputElement).value;
|
||||
config['smtp']['password'] = (document.getElementById('emailSMTPPassword') as HTMLInputElement).value;
|
||||
config['email']['address'] = (document.getElementById('emailSMTPAddress') as HTMLInputElement).value;
|
||||
} else {
|
||||
config['email']['method'] = 'mailgun';
|
||||
config['mailgun']['api_url'] = (document.getElementById('emailMailgunURL') as HTMLInputElement).value;
|
||||
config['mailgun']['api_key'] = (document.getElementById('emailMailgunKey') as HTMLInputElement).value;
|
||||
config['email']['address'] = (document.getElementById('emailMailgunAddress') as HTMLInputElement).value;
|
||||
};
|
||||
config['notifications']['enabled'] = ""+(document.getElementById('notificationsEnabled') as HTMLInputElement).checked;
|
||||
// Page 5: Email formatting
|
||||
config['email']['from'] = (document.getElementById('emailSender') as HTMLInputElement).value;
|
||||
config['email']['date_format'] = (document.getElementById('emailDateFormat') as HTMLInputElement).value;
|
||||
config['email']['use_24h'] = ""+(document.getElementById('email24hTimeRadio') as HTMLInputElement).checked;
|
||||
config['email']['message'] = (document.getElementById('emailMessage') as HTMLInputElement).value;
|
||||
// Page 6: Password Resets
|
||||
if (pwrEnabled.checked) {
|
||||
config['password_resets']['enabled'] = 'true';
|
||||
config['password_resets']['watch_directory'] = (document.getElementById('pwrJfPath') as HTMLInputElement).value;
|
||||
config['password_resets']['subject'] = (document.getElementById('pwrSubject') as HTMLInputElement).value;
|
||||
} else {
|
||||
config['password_resets']['enabled'] = 'false';
|
||||
};
|
||||
// Page 7: Invite Emails
|
||||
if ((document.getElementById('invEnabled') as HTMLInputElement).checked) {
|
||||
config['invite_emails']['enabled'] = 'true';
|
||||
config['invite_emails']['url_base'] = (document.getElementById('invURLBase') as HTMLInputElement).value;
|
||||
config['invite_emails']['subject'] = (document.getElementById('invSubject') as HTMLInputElement).value;
|
||||
}
|
||||
|
||||
const pageNames: string[][] = [];
|
||||
|
||||
window.history.replaceState("welcome", "Setup - jfa-go");
|
||||
|
||||
const changePage = (title: string, pageTitle: string) => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const lang = urlParams.get("lang");
|
||||
let page = "/#" + title;
|
||||
if (lang) { page += "?lang=" + lang; }
|
||||
window.history.pushState(title || "welcome", pageTitle, page);
|
||||
};
|
||||
const cards = Array.from(document.getElementById("page-container").getElementsByClassName("card")) as Array<HTMLDivElement>;
|
||||
(window as any).cards = cards;
|
||||
window.onpopstate = (event: PopStateEvent) => {
|
||||
if (event.state === "welcome") {
|
||||
cards[0].classList.remove("unfocused");
|
||||
for (let i = 1; i < cards.length; i++) { cards[i].classList.add("unfocused"); }
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
if (event.state === pageNames[i][0]) {
|
||||
cards[i].classList.remove("unfocused");
|
||||
} else {
|
||||
config['invite_emails']['enabled'] = 'false';
|
||||
};
|
||||
};
|
||||
// Page 8: Password Validation
|
||||
if ((document.getElementById('valEnabled') as HTMLInputElement).checked) {
|
||||
config['password_validation']['enabled'] = 'true';
|
||||
config['password_validation']['min_length'] = (document.getElementById('valLength') as HTMLInputElement).value;
|
||||
config['password_validation']['upper'] = (document.getElementById('valUpper') as HTMLInputElement).value;
|
||||
config['password_validation']['lower'] = (document.getElementById('valLower') as HTMLInputElement).value;
|
||||
config['password_validation']['number'] = (document.getElementById('valNumber') as HTMLInputElement).value;
|
||||
config['password_validation']['special'] = (document.getElementById('valSpecial') as HTMLInputElement).value;
|
||||
} else {
|
||||
config['password_validation']['enabled'] = 'false';
|
||||
};
|
||||
// Page 9: Messages
|
||||
config['ui']['contact_message'] = (document.getElementById('msgContact') as HTMLInputElement).value;
|
||||
config['ui']['help_message'] = (document.getElementById('msgHelp') as HTMLInputElement).value;
|
||||
config['ui']['success_message'] = (document.getElementById('msgSuccess') as HTMLInputElement).value;
|
||||
// Send it
|
||||
config["restart-program"] = true;
|
||||
let req = new XMLHttpRequest();
|
||||
req.open("POST", "/config", true);
|
||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
req.responseType = 'json';
|
||||
req.onreadystatechange = function() {
|
||||
if (this.readyState == 4) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.className = '';
|
||||
submitButton.classList.add('btn', 'btn-success');
|
||||
submitButton.textContent = 'Success';
|
||||
cards[i].classList.add("unfocused");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(() => {
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
const card = cards[i];
|
||||
const back = card.getElementsByClassName("back")[0] as HTMLSpanElement;
|
||||
const next = card.getElementsByClassName("next")[0] as HTMLSpanElement;
|
||||
console.log(cards[i]);
|
||||
const titleEl = cards[i].querySelector("span.heading") as HTMLElement;
|
||||
let title = titleEl.textContent.replace("/", "_").replace(" ", "-");
|
||||
if (titleEl.classList.contains("welcome")) {
|
||||
title = "";
|
||||
}
|
||||
let pageTitle = titleEl.textContent + " - jfa-go";
|
||||
pageNames.push([title, pageTitle]);
|
||||
if (back) { back.addEventListener("click", () => {
|
||||
let found = false;
|
||||
for (let ind = cards.length - 1; ind >= 0; ind--) {
|
||||
cards[ind].classList.add("unfocused");
|
||||
if (ind < i && !(cards[ind].classList.contains("hidden")) && !found) {
|
||||
cards[ind].classList.remove("unfocused");
|
||||
changePage(pageNames[ind][0], pageNames[ind][1]);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
window.scrollTo(0, 0);
|
||||
}); }
|
||||
if (next) { next.addEventListener("click", () => {
|
||||
let found = false;
|
||||
for (let ind = 0; ind < cards.length; ind++) {
|
||||
cards[ind].classList.add("unfocused");
|
||||
if (ind > i && !(cards[ind].classList.contains("hidden")) && !found) {
|
||||
cards[ind].classList.remove("unfocused");
|
||||
changePage(pageNames[ind][0], pageNames[ind][1]);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
window.scrollTo(0, 0);
|
||||
}); }
|
||||
}
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const button = document.getElementById("jellyfin-test-connection") as HTMLSpanElement;
|
||||
const ogText = button.textContent;
|
||||
const nextButton = button.parentElement.querySelector("span.next") as HTMLSpanElement;
|
||||
button.onclick = () => {
|
||||
toggleLoader(button);
|
||||
let send = {
|
||||
"type": settings["jellyfin"]["type"].value,
|
||||
"server": settings["jellyfin"]["server"].value,
|
||||
"username": settings["jellyfin"]["username"].value,
|
||||
"password": settings["jellyfin"]["password"].value
|
||||
};
|
||||
_post("/jellyfin/test", send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(button);
|
||||
const success = req.response["success"] as boolean;
|
||||
if (success) {
|
||||
nextButton.removeAttribute("disabled");
|
||||
button.textContent = window.lang.strings("success");
|
||||
button.classList.add("~positive");
|
||||
button.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
button.textContent = ogText;
|
||||
button.classList.add("~urge");
|
||||
button.classList.remove("~positive");
|
||||
}, 5000);
|
||||
} else {
|
||||
nextButton.setAttribute("disabled", "");
|
||||
button.textContent = window.lang.strings("error");
|
||||
button.classList.add("~critical");
|
||||
button.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
button.textContent = ogText;
|
||||
button.classList.add("~urge");
|
||||
button.classList.remove("~critical");
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
}, true, () => {});
|
||||
};
|
||||
req.send(JSON.stringify(config));
|
||||
};
|
||||
})();
|
||||
|
||||
loadLangSelector("setup");
|
||||
|
Loading…
Reference in new issue