mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 17:10:10 +00:00
Add ts to link setting dependance
Also make store each setting as classes in a settings object, to make it easier to serialize on submitting. Also, added "substitute_jellyfin_strings", "no_username" and welcome_email.
This commit is contained in:
parent
bf1e6230dc
commit
8c871bc5fa
2
Makefile
2
Makefile
@ -18,12 +18,14 @@ typescript:
|
|||||||
-mkdir -p build/data/web/js
|
-mkdir -p build/data/web/js
|
||||||
-npx esbuild --bundle ts/admin.ts --outfile=./build/data/web/js/admin.js --minify
|
-npx esbuild --bundle ts/admin.ts --outfile=./build/data/web/js/admin.js --minify
|
||||||
-npx esbuild --bundle ts/form.ts --outfile=./build/data/web/js/form.js --minify
|
-npx esbuild --bundle ts/form.ts --outfile=./build/data/web/js/form.js --minify
|
||||||
|
-npx esbuild --bundle ts/setup.ts --outfile=./build/data/web/js/setup.js --minify
|
||||||
|
|
||||||
ts-debug:
|
ts-debug:
|
||||||
$(info compiling typescript w/ sourcemaps)
|
$(info compiling typescript w/ sourcemaps)
|
||||||
-mkdir -p build/data/web/js
|
-mkdir -p build/data/web/js
|
||||||
-npx esbuild --bundle ts/admin.ts --sourcemap --outfile=./build/data/web/js/admin.js
|
-npx esbuild --bundle ts/admin.ts --sourcemap --outfile=./build/data/web/js/admin.js
|
||||||
-npx esbuild --bundle ts/form.ts --sourcemap --outfile=./build/data/web/js/form.js
|
-npx esbuild --bundle ts/form.ts --sourcemap --outfile=./build/data/web/js/form.js
|
||||||
|
-npx esbuild --bundle ts/setup.ts --sourcemap --outfile=./build/data/web/js/setup.js
|
||||||
-rm -r build/data/web/js/ts
|
-rm -r build/data/web/js/ts
|
||||||
$(info copying typescript)
|
$(info copying typescript)
|
||||||
cp -r ts build/data/web/js
|
cp -r ts build/data/web/js
|
||||||
|
8
api.go
8
api.go
@ -1288,6 +1288,14 @@ func (app *appContext) GetLanguages(gc *gin.Context) {
|
|||||||
for key, lang := range app.storage.lang.Admin {
|
for key, lang := range app.storage.lang.Admin {
|
||||||
resp[key] = lang.Meta.Name
|
resp[key] = lang.Meta.Name
|
||||||
}
|
}
|
||||||
|
} else if page == "setup" {
|
||||||
|
for key, lang := range app.storage.lang.Setup {
|
||||||
|
resp[key] = lang.Meta.Name
|
||||||
|
}
|
||||||
|
} else if page == "email" {
|
||||||
|
for key, lang := range app.storage.lang.Email {
|
||||||
|
resp[key] = lang.Meta.Name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(resp) == 0 {
|
if len(resp) == 0 {
|
||||||
respond(500, "Couldn't get languages", gc)
|
respond(500, "Couldn't get languages", gc)
|
||||||
|
@ -66,27 +66,27 @@
|
|||||||
<p class="content">{{ .lang.Login.description }}</p>
|
<p class="content">{{ .lang.Login.description }}</p>
|
||||||
<div class="pl-1">
|
<div class="pl-1">
|
||||||
<label class="row switch pb-1">
|
<label class="row switch pb-1">
|
||||||
<input type="radio" name="login-method" checked><span>{{ .lang.Login.authorizeWithJellyfin }}</span>
|
<input type="radio" name="ui-jellyfin_login" value="true" checked><span>{{ .lang.Login.authorizeWithJellyfin }}</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="row switch pl-1 pb-1">
|
<label class="row switch pl-1 pb-1">
|
||||||
<input type="checkbox" id="login-jellyfin"><span>{{ .lang.Login.adminOnly }}</span>
|
<input type="checkbox" id="ui-admin_only"><span>{{ .lang.Login.adminOnly }}</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="row switch pb-1">
|
<label class="row switch pb-1">
|
||||||
<input type="radio" name="login-method"><span>{{ .lang.Login.authorizeManual }}</span>
|
<input type="radio" name="ui-jellyfin_login" value="false"><span>{{ .lang.Login.authorizeManual }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="login-manual">
|
<div id="login-manual">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="mt-half">{{ .lang.Strings.username }}</span>
|
<span class="mt-half">{{ .lang.Strings.username }}</span>
|
||||||
<input type="text" id="login-password" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.username }}">
|
<input type="text" id="ui-username" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.username }}">
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span>{{ .lang.Strings.password }}</span>
|
<span>{{ .lang.Strings.password }}</span>
|
||||||
<input type="password" id="login-username" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.password }}">
|
<input type="password" id="ui-password" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.password }}">
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span>{{ .lang.Strings.emailAddress }} ({{ .lang.Strings.optional }})</span>
|
<span>{{ .lang.Strings.emailAddress }} ({{ .lang.Strings.optional }})</span>
|
||||||
<input type="email" class="input ~neutral !normal mt-half" placeholder="email@address">
|
<input type="email" id="ui-email" class="input ~neutral !normal mt-half" placeholder="email@address">
|
||||||
<span class="support mb-1">{{ .lang.Login.emailNotice }}</span>
|
<span class="support mb-1">{{ .lang.Login.emailNotice }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -108,13 +108,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="support mb-1">{{ .lang.JellyfinEmby.embyNotice }}</p>
|
<p class="support mb-1">{{ .lang.JellyfinEmby.embyNotice }}</p>
|
||||||
</label>
|
</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">
|
<label class="label">
|
||||||
<span class="mt-half">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.internal }})</span>
|
<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">
|
<input type="url" class="input ~neutral !normal mt-half mb-1" id="jellyfin-server" placeholder="http://jellyf.in:80">
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="mt-half">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.external }})</span>
|
<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">
|
<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>
|
<p class="support mb-1">{{ .lang.JellyfinEmby.addressExternalNotice }}</p>
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
@ -139,7 +144,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span>{{ .lang.Strings.serverType }}</span>
|
<span>{{ .lang.Email.method }}</span>
|
||||||
<div class="select ~neutral !normal mt-half mb-1">
|
<div class="select ~neutral !normal mt-half mb-1">
|
||||||
<select id="email-method">
|
<select id="email-method">
|
||||||
<option value="">{{ .lang.Strings.disabled }}</option>
|
<option value="">{{ .lang.Strings.disabled }}</option>
|
||||||
@ -148,6 +153,10 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="row switch">
|
||||||
|
<input type="checkbox" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span>
|
||||||
|
<p class="support mb-1">{{ .lang.Email.useEmailAsUsernameNotice }}</p>
|
||||||
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="mt-half">{{ .lang.Email.fromAddress }}</span>
|
<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">
|
<input type="email" class="input ~neutral !normal mt-half mb-1" id="email-address" placeholder="mail@jellyf.in">
|
||||||
@ -217,12 +226,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card ~neutral !low mb-1">
|
<div class="card ~neutral !low mb-1 related-to-email">
|
||||||
<span class="heading">{{ .lang.Notifications.title }}</span>
|
<span class="heading">{{ .lang.Notifications.title }}</span>
|
||||||
<p class="content">{{ .lang.Notifications.description }}</p>
|
<p class="content">{{ .lang.Notifications.description }}</p>
|
||||||
<label class="row switch pb-1">
|
<label class="row switch pb-1">
|
||||||
<input type="checkbox" id="notifications-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
<input type="checkbox" id="notifications-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||||
</label>
|
</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>
|
||||||
|
</div>
|
||||||
|
<div class="card ~neutral !low mb-1 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 related-to-email">
|
||||||
<span class="heading">{{ .lang.PasswordResets.title }}</span>
|
<span class="heading">{{ .lang.PasswordResets.title }}</span>
|
||||||
<p class="content">{{ .lang.PasswordResets.description }}</p>
|
<p class="content">{{ .lang.PasswordResets.description }}</p>
|
||||||
<label class="row switch pb-1">
|
<label class="row switch pb-1">
|
||||||
@ -244,27 +285,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card ~neutral !low mb-1">
|
|
||||||
<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">
|
<div class="card ~neutral !low mb-1">
|
||||||
<span class="heading">{{ .lang.PasswordValidation.title }}</span>
|
<span class="heading">{{ .lang.PasswordValidation.title }}</span>
|
||||||
<p class="content">{{ .lang.PasswordValidation.description }}</p>
|
<p class="content">{{ .lang.PasswordValidation.description }}</p>
|
||||||
@ -316,7 +336,7 @@
|
|||||||
<input type="text" class="input ~neutral !normal mt-half" id="ui-success_message">
|
<input type="text" class="input ~neutral !normal mt-half" id="ui-success_message">
|
||||||
<p class="support mb-1">{{ .lang.HelpMessages.successMessageNotice }}</p>
|
<p class="support mb-1">{{ .lang.HelpMessages.successMessageNotice }}</p>
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label related-to-email">
|
||||||
<span class="mt-half">{{ .lang.HelpMessages.emailMessage }}</span>
|
<span class="mt-half">{{ .lang.HelpMessages.emailMessage }}</span>
|
||||||
<input type="text" class="input ~neutral !normal mt-half" id="email-message">
|
<input type="text" class="input ~neutral !normal mt-half" id="email-message">
|
||||||
<p class="support mb-1">{{ .lang.HelpMessages.emailMessageNotice }}</p>
|
<p class="support mb-1">{{ .lang.HelpMessages.emailMessageNotice }}</p>
|
||||||
@ -340,6 +360,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
window.langFile = JSON.parse({{ .language }});
|
||||||
|
window.messages = JSON.parse({{ .messages }});
|
||||||
|
</script>
|
||||||
|
<script src="js/setup.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
2
lang.go
2
lang.go
@ -96,10 +96,12 @@ type setupLang struct {
|
|||||||
JellyfinEmby langSection `json:"jellyfinEmby"`
|
JellyfinEmby langSection `json:"jellyfinEmby"`
|
||||||
Email langSection `json:"email"`
|
Email langSection `json:"email"`
|
||||||
Notifications langSection `json:"notifications"`
|
Notifications langSection `json:"notifications"`
|
||||||
|
WelcomeEmails langSection `json:"welcomeEmails"`
|
||||||
PasswordResets langSection `json:"passwordResets"`
|
PasswordResets langSection `json:"passwordResets"`
|
||||||
InviteEmails langSection `json:"inviteEmails"`
|
InviteEmails langSection `json:"inviteEmails"`
|
||||||
PasswordValidation langSection `json:"passwordValidation"`
|
PasswordValidation langSection `json:"passwordValidation"`
|
||||||
HelpMessages langSection `json:"helpMessages"`
|
HelpMessages langSection `json:"helpMessages"`
|
||||||
|
JSON string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ls *setupLangs) getOptions(chosen string) (string, []string) {
|
func (ls *setupLangs) getOptions(chosen string) (string, []string) {
|
||||||
|
@ -47,12 +47,17 @@
|
|||||||
"embyNotice": "Emby support is limited and does not support password resets.",
|
"embyNotice": "Emby support is limited and does not support password resets.",
|
||||||
"internal": "Internal",
|
"internal": "Internal",
|
||||||
"external": "External",
|
"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.",
|
"addressExternalNotice": "Leave blank to use the same address.",
|
||||||
"testConnection": "Test Connection"
|
"testConnection": "Test Connection"
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"title": "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.",
|
"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",
|
"fromAddress": "From Address",
|
||||||
"senderName": "Sender Name",
|
"senderName": "Sender Name",
|
||||||
"dateFormat": "Date Format",
|
"dateFormat": "Date Format",
|
||||||
@ -67,16 +72,20 @@
|
|||||||
"title": "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."
|
"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": {
|
"passwordResets": {
|
||||||
"title": "Password Resets",
|
"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.",
|
"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",
|
"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."
|
"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."
|
||||||
},
|
},
|
||||||
"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'."
|
|
||||||
},
|
|
||||||
"passwordValidation": {
|
"passwordValidation": {
|
||||||
"title": "Password Validation",
|
"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.",
|
"description": "If enabled, a set of password requirements will show on the account creation page, such as minimum length, uppercase/lowercase characters, etc.",
|
||||||
|
32
main.go
32
main.go
@ -329,6 +329,15 @@ func start(asDaemon, firstCall bool) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.storage.lang.CommonPath = filepath.Join(app.localPath, "lang", "common")
|
||||||
|
app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form")
|
||||||
|
app.storage.lang.AdminPath = filepath.Join(app.localPath, "lang", "admin")
|
||||||
|
app.storage.lang.EmailPath = filepath.Join(app.localPath, "lang", "email")
|
||||||
|
err := app.storage.loadLang()
|
||||||
|
if err != nil {
|
||||||
|
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
if !firstRun {
|
if !firstRun {
|
||||||
app.host = app.config.Section("ui").Key("host").String()
|
app.host = app.config.Section("ui").Key("host").String()
|
||||||
if app.config.Section("advanced").Key("tls").MustBool(false) {
|
if app.config.Section("advanced").Key("tls").MustBool(false) {
|
||||||
@ -516,14 +525,6 @@ func start(asDaemon, firstCall bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.storage.lang.CommonPath = filepath.Join(app.localPath, "lang", "common")
|
|
||||||
app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form")
|
|
||||||
app.storage.lang.AdminPath = filepath.Join(app.localPath, "lang", "admin")
|
|
||||||
app.storage.lang.EmailPath = filepath.Join(app.localPath, "lang", "email")
|
|
||||||
err = app.storage.loadLang()
|
|
||||||
if err != nil {
|
|
||||||
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since email depends on language, the email reload in loadConfig won't work first time.
|
// Since email depends on language, the email reload in loadConfig won't work first time.
|
||||||
app.email = NewEmailer(app)
|
app.email = NewEmailer(app)
|
||||||
@ -560,19 +561,8 @@ func start(asDaemon, firstCall bool) {
|
|||||||
} else {
|
} else {
|
||||||
debugMode = false
|
debugMode = false
|
||||||
address = "0.0.0.0:8056"
|
address = "0.0.0.0:8056"
|
||||||
|
|
||||||
app.storage.lang.CommonPath = filepath.Join(app.localPath, "lang", "common")
|
|
||||||
app.storage.lang.EmailPath = filepath.Join(app.localPath, "lang", "email")
|
|
||||||
app.storage.lang.SetupPath = filepath.Join(app.localPath, "lang", "setup")
|
app.storage.lang.SetupPath = filepath.Join(app.localPath, "lang", "setup")
|
||||||
err := app.storage.loadLangCommon()
|
err := app.storage.loadLangSetup()
|
||||||
if err != nil {
|
|
||||||
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
|
||||||
}
|
|
||||||
err = app.storage.loadLangEmail()
|
|
||||||
if err != nil {
|
|
||||||
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
|
||||||
}
|
|
||||||
err = app.storage.loadLangSetup()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
||||||
}
|
}
|
||||||
@ -595,12 +585,12 @@ func start(asDaemon, firstCall bool) {
|
|||||||
app.debug.Println("Loading pprof")
|
app.debug.Println("Loading pprof")
|
||||||
pprof.Register(router)
|
pprof.Register(router)
|
||||||
}
|
}
|
||||||
|
router.GET("/lang/:page", app.GetLanguages)
|
||||||
if !firstRun {
|
if !firstRun {
|
||||||
router.GET("/", app.AdminPage)
|
router.GET("/", app.AdminPage)
|
||||||
router.GET("/accounts", app.AdminPage)
|
router.GET("/accounts", app.AdminPage)
|
||||||
router.GET("/settings", app.AdminPage)
|
router.GET("/settings", app.AdminPage)
|
||||||
router.GET("/lang/:page/:file", app.ServeLang)
|
router.GET("/lang/:page/:file", app.ServeLang)
|
||||||
router.GET("/lang/:page", app.GetLanguages)
|
|
||||||
router.GET("/token/login", app.getTokenLogin)
|
router.GET("/token/login", app.getTokenLogin)
|
||||||
router.GET("/token/refresh", app.getTokenRefresh)
|
router.GET("/token/refresh", app.getTokenRefresh)
|
||||||
router.POST("/newUser", app.NewUser)
|
router.POST("/newUser", app.NewUser)
|
||||||
|
23
setup.go
23
setup.go
@ -22,9 +22,27 @@ func (app *appContext) ServeSetup(gc *gin.Context) {
|
|||||||
if _, ok := app.storage.lang.Email[lang]; !ok {
|
if _, ok := app.storage.lang.Email[lang]; !ok {
|
||||||
emailLang = "en-us"
|
emailLang = "en-us"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
messages := map[string]map[string]string{
|
||||||
|
"ui": {
|
||||||
|
"contact_message": app.config.Section("ui").Key("contact_message").String(),
|
||||||
|
"help_message": app.config.Section("ui").Key("help_message").String(),
|
||||||
|
"success_message": app.config.Section("ui").Key("success_message").String(),
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"message": app.config.Section("email").Key("message").String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
msg, err := json.Marshal(messages)
|
||||||
|
if err != nil {
|
||||||
|
respond(500, "Failed to fetch default values", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
gc.HTML(200, "setup2.html", gin.H{
|
gc.HTML(200, "setup2.html", gin.H{
|
||||||
"lang": app.storage.lang.Setup[lang],
|
"lang": app.storage.lang.Setup[lang],
|
||||||
"emailLang": app.storage.lang.Email[emailLang],
|
"emailLang": app.storage.lang.Email[emailLang],
|
||||||
|
"language": app.storage.lang.Setup[lang].JSON,
|
||||||
|
"messages": string(msg),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +94,11 @@ func (st *Storage) loadLangSetup() error {
|
|||||||
patchLang(&english.PasswordValidation, &lang.PasswordValidation)
|
patchLang(&english.PasswordValidation, &lang.PasswordValidation)
|
||||||
patchLang(&english.HelpMessages, &lang.HelpMessages)
|
patchLang(&english.HelpMessages, &lang.HelpMessages)
|
||||||
}
|
}
|
||||||
|
stringSettings, err := json.Marshal(lang)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lang.JSON = string(stringSettings)
|
||||||
st.lang.Setup[index] = lang
|
st.lang.Setup[index] = lang
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,8 @@ export const rmAttr = (el: HTMLElement, attr: string): void => {
|
|||||||
export const addAttr = (el: HTMLElement, attr: string): void => el.classList.add(attr);
|
export const addAttr = (el: HTMLElement, attr: string): void => el.classList.add(attr);
|
||||||
export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void): void => {
|
export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("GET", window.URLBase + url, true);
|
if (window.URLBase) { url = window.URLBase + url; }
|
||||||
|
req.open("GET", url, true);
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
|
512
ts/setup.ts
512
ts/setup.ts
@ -1,260 +1,274 @@
|
|||||||
// Lord forgive me for this mess, i'll fix it one day i swear
|
import { _get, _post } from "./modules/common.js";
|
||||||
|
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
||||||
|
|
||||||
document.getElementById("page-1").scrollIntoView({
|
interface sWindow extends Window {
|
||||||
behavior: "auto",
|
messages: {};
|
||||||
block: "center",
|
|
||||||
inline: "center"
|
|
||||||
});
|
|
||||||
|
|
||||||
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 = '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let radio of ['manualAuthRadio', 'jfAuthRadio']) {
|
|
||||||
document.getElementById(radio).addEventListener('change', checkAuthRadio);
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let radio of ['emailDisabledRadio', 'emailSMTPRadio', 'emailMailgunRadio']) {
|
|
||||||
document.getElementById(radio).addEventListener('change', checkEmailRadio);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkSSL = () => {
|
declare var window: sWindow;
|
||||||
var label = document.getElementById('emailSSL_TLSLabel');
|
|
||||||
if ((document.getElementById('emailSSL_TLS') as HTMLInputElement).checked) {
|
|
||||||
label.textContent = 'Use SSL/TLS';
|
|
||||||
} else {
|
|
||||||
label.textContent = 'Use STARTTLS';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.getElementById('emailSSL_TLS').addEventListener('change', checkSSL);
|
|
||||||
|
|
||||||
var pwrEnabled = document.getElementById('pwrEnabled') as HTMLInputElement;
|
const get = (id: string): HTMLElement => document.getElementById(id);
|
||||||
const checkPwrEnabled = () => {
|
const text = (id: string, val: string) => { document.getElementById(id).textContent = val; };
|
||||||
if (pwrEnabled.checked) {
|
const html = (id: string, val: string) => { document.getElementById(id).innerHTML = val; };
|
||||||
document.getElementById('pwrArea').style.display = '';
|
|
||||||
} else {
|
|
||||||
document.getElementById('pwrArea').style.display = 'none';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
pwrEnabled.addEventListener('change', checkPwrEnabled);
|
|
||||||
|
|
||||||
var invEnabled = document.getElementById("invEnabled") as HTMLInputElement;
|
interface boolEvent extends Event {
|
||||||
const checkInvEnabled = () => {
|
detail: boolean;
|
||||||
if (invEnabled.checked) {
|
}
|
||||||
document.getElementById('invArea').style.display = '';
|
|
||||||
} else {
|
|
||||||
document.getElementById('invArea').style.display = 'none';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
invEnabled.addEventListener('change', checkInvEnabled);
|
|
||||||
|
|
||||||
var valEnabled = document.getElementById("valEnabled") as HTMLInputElement;
|
class Input {
|
||||||
const checkValEnabled = () => {
|
private _el: HTMLInputElement;
|
||||||
const valArea = document.getElementById("valArea");
|
get value(): string { return ""+this._el.value; }
|
||||||
if (valEnabled.checked) {
|
set value(v: string) { this._el.value = v; }
|
||||||
valArea.style.display = '';
|
constructor(el: HTMLElement, placeholder?: any, value?: any, depends?: string, dependsTrue?: boolean, section?: string) {
|
||||||
} else {
|
this._el = el as HTMLInputElement;
|
||||||
valArea.style.display = 'none';
|
if (placeholder) { this._el.placeholder = placeholder; }
|
||||||
}
|
if (value) { this.value = value; }
|
||||||
};
|
if (depends) {
|
||||||
valEnabled.addEventListener('change', checkValEnabled);
|
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
||||||
|
let el = this._el as HTMLElement;
|
||||||
checkValEnabled();
|
if (el.parentElement.tagName == "LABEL") { el = el.parentElement; }
|
||||||
checkInvEnabled();
|
if (event.detail !== dependsTrue) {
|
||||||
checkSSL();
|
el.classList.add("unfocused");
|
||||||
checkAuthRadio();
|
|
||||||
checkEmailRadio();
|
|
||||||
checkPwrEnabled();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
} 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 {
|
} else {
|
||||||
testButton.classList.add('btn', 'btn-danger');
|
el.classList.remove("unfocused");
|
||||||
testButton.textContent = 'Failed';
|
}
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
||||||
|
this._el = el as HTMLInputElement;
|
||||||
|
if (section && setting) {
|
||||||
|
this._el.onchange = () => {
|
||||||
|
const ev = new CustomEvent(`settings-${section}-${setting}`, { "detail": this._el.checked })
|
||||||
|
document.dispatchEvent(ev);
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
req.send(JSON.stringify(jfData));
|
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 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;
|
||||||
|
}
|
||||||
|
constructor(name: string, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
||||||
|
this._els = document.getElementsByName(name) as NodeListOf<HTMLInputElement>;
|
||||||
|
if (section && setting) {
|
||||||
|
const onchange = () => {
|
||||||
|
const ev = new CustomEvent(`settings-${section}-${setting}`, { "detail": this._els[0].checked })
|
||||||
|
document.dispatchEvent(ev);
|
||||||
|
};
|
||||||
|
this._els[0].onchange = onchange;
|
||||||
|
this._els[1].onchange = onchange;
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
set onchange(f: () => void) {
|
||||||
|
this._el.addEventListener("change", f);
|
||||||
|
}
|
||||||
|
constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
||||||
|
this._el = el as HTMLSelectElement;
|
||||||
|
if (section && setting) {
|
||||||
|
this._el.addEventListener("change", () => {
|
||||||
|
const ev = new CustomEvent(`settings-${section}-${setting}`, { "detail": this.value ? true : false })
|
||||||
|
document.dispatchEvent(ev);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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 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";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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": {
|
||||||
|
"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"))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const relatedToEmail = Array.from(document.getElementsByClassName("related-to-email"));
|
||||||
|
settings["email"]["method"].onchange = () => {
|
||||||
|
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("unfocused");
|
||||||
|
}
|
||||||
|
} else if (val == "mailgun") {
|
||||||
|
mailgun.classList.remove("unfocused");
|
||||||
|
smtp.classList.add("unfocused");
|
||||||
|
for (let el of relatedToEmail) {
|
||||||
|
el.classList.remove("unfocused");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mailgun.classList.add("unfocused");
|
||||||
|
smtp.classList.add("unfocused");
|
||||||
|
for (let el of relatedToEmail) {
|
||||||
|
el.classList.add("unfocused");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('submitButton').onclick = () => {
|
loadLangSelector("setup");
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
} 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';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
req.send(JSON.stringify(config));
|
|
||||||
};
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user