Merge pull request #405 from hrfee/paramaterized-paths
Paramaterized paths: Put pages in different places
This commit is contained in:
@@ -170,6 +170,7 @@ $(CSS_FULLTARGET): $(TYPESCRIPT_TARGET) $(VARIANTS_TARGET) $(ALL_CSS_SRC) $(wild
|
|||||||
$(info copying fonts)
|
$(info copying fonts)
|
||||||
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/
|
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/
|
||||||
$(info bundling css)
|
$(info bundling css)
|
||||||
|
rm -f $(CSS_TARGET) $(CSS_FULLTARGET)
|
||||||
$(ESBUILD) --bundle css/base.css --outfile=$(CSS_TARGET) --external:remixicon.css --external:../fonts/hanken* --minify
|
$(ESBUILD) --bundle css/base.css --outfile=$(CSS_TARGET) --external:remixicon.css --external:../fonts/hanken* --minify
|
||||||
|
|
||||||
npx tailwindcss -i $(CSS_TARGET) -o $(CSS_FULLTARGET) $(TAILWIND)
|
npx tailwindcss -i $(CSS_TARGET) -o $(CSS_FULLTARGET) $(TAILWIND)
|
||||||
@@ -192,7 +193,7 @@ STATIC_TARGET = $(STATIC_SRC:static/%=$(DATA)/web/%)
|
|||||||
COPY_SRC = images/banner.svg jfa-go.service LICENSE $(LANG_SRC) $(STATIC_SRC)
|
COPY_SRC = images/banner.svg jfa-go.service LICENSE $(LANG_SRC) $(STATIC_SRC)
|
||||||
COPY_TARGET = $(DATA)/jfa-go.service
|
COPY_TARGET = $(DATA)/jfa-go.service
|
||||||
# $(DATA)/LICENSE $(LANG_TARGET) $(STATIC_TARGET) $(DATA)/web/css/$(CSSVERSION)bundle.css
|
# $(DATA)/LICENSE $(LANG_TARGET) $(STATIC_TARGET) $(DATA)/web/css/$(CSSVERSION)bundle.css
|
||||||
$(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC)
|
$(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC) $(CONFIG_BASE)
|
||||||
$(info copying $(CONFIG_BASE))
|
$(info copying $(CONFIG_BASE))
|
||||||
cp $(CONFIG_BASE) $(DATA)/
|
cp $(CONFIG_BASE) $(DATA)/
|
||||||
$(info copying crash page)
|
$(info copying crash page)
|
||||||
@@ -206,7 +207,7 @@ $(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC)
|
|||||||
cp -r lang $(DATA)/
|
cp -r lang $(DATA)/
|
||||||
cp LICENSE $(DATA)/
|
cp LICENSE $(DATA)/
|
||||||
|
|
||||||
BUILDDEPS := $(DATA) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET)
|
BUILDDEPS := $(DATA) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(INLINE_TARGET) $(CSS_FULLTARGET) $(TYPESCRIPT_TARGET)
|
||||||
precompile: $(BUILDDEPS)
|
precompile: $(BUILDDEPS)
|
||||||
|
|
||||||
COMPDEPS =
|
COMPDEPS =
|
||||||
|
|||||||
+5
-5
@@ -414,7 +414,7 @@ func (app *appContext) TelegramVerified(gc *gin.Context) {
|
|||||||
respondBool(200, ok, gc)
|
respondBool(200, ok, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Returns true/false on whether or not a telegram PIN was verified. Requires invite code.
|
// @Summary Returns true/false on whether or not a telegram PIN was verified. Requires invite code. NOTE: "/invite" might have been changed in Settings > URL Paths.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Success 401 {object} boolResponse
|
// @Success 401 {object} boolResponse
|
||||||
@@ -438,7 +438,7 @@ func (app *appContext) TelegramVerifiedInvite(gc *gin.Context) {
|
|||||||
respondBool(200, ok, gc)
|
respondBool(200, ok, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Returns true/false on whether or not a discord PIN was verified. Requires invite code.
|
// @Summary Returns true/false on whether or not a discord PIN was verified. Requires invite code. NOTE: "/invite" might have been changed in Settings > URL Paths.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Failure 401 {object} boolResponse
|
// @Failure 401 {object} boolResponse
|
||||||
@@ -462,7 +462,7 @@ func (app *appContext) DiscordVerifiedInvite(gc *gin.Context) {
|
|||||||
respondBool(200, ok, gc)
|
respondBool(200, ok, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Returns a 10-minute, one-use Discord server invite
|
// @Summary Returns a 10-minute, one-use Discord server invite. NOTE: "/invite" might have been changed in Settings > URL Paths.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} DiscordInviteDTO
|
// @Success 200 {object} DiscordInviteDTO
|
||||||
// @Failure 400 {object} boolResponse
|
// @Failure 400 {object} boolResponse
|
||||||
@@ -489,7 +489,7 @@ func (app *appContext) DiscordServerInvite(gc *gin.Context) {
|
|||||||
gc.JSON(200, DiscordInviteDTO{invURL, iconURL})
|
gc.JSON(200, DiscordInviteDTO{invURL, iconURL})
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Generate and send a new PIN to a specified Matrix user.
|
// @Summary Generate and send a new PIN to a specified Matrix user. NOTE: "/invite" might have been changed in Settings > URL Paths.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Failure 400 {object} stringResponse
|
// @Failure 400 {object} stringResponse
|
||||||
@@ -528,7 +528,7 @@ func (app *appContext) MatrixSendPIN(gc *gin.Context) {
|
|||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Check whether a matrix PIN is valid, and mark the token as verified if so. Requires invite code.
|
// @Summary Check whether a matrix PIN is valid, and mark the token as verified if so. Requires invite code. NOTE: "/invite" might have been changed in Settings > URL Paths.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Failure 401 {object} boolResponse
|
// @Failure 401 {object} boolResponse
|
||||||
|
|||||||
+3
-5
@@ -164,9 +164,7 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
|
|||||||
var target ConfirmationTarget
|
var target ConfirmationTarget
|
||||||
var id string
|
var id string
|
||||||
fail := func() {
|
fail := func() {
|
||||||
gcHTML(gc, 404, "404.html", gin.H{
|
app.gcHTML(gc, 404, "404.html", OtherPage, gin.H{
|
||||||
"cssClass": app.cssClass,
|
|
||||||
"cssVersion": cssVersion,
|
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -201,7 +199,7 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
|
|||||||
|
|
||||||
// Perform an Action
|
// Perform an Action
|
||||||
if target == NoOp {
|
if target == NoOp {
|
||||||
gc.Redirect(http.StatusSeeOther, "/my/account")
|
gc.Redirect(http.StatusSeeOther, PAGES.MyAccount)
|
||||||
return
|
return
|
||||||
} else if target == UserEmailChange {
|
} else if target == UserEmailChange {
|
||||||
app.modifyEmail(id, claims["email"].(string))
|
app.modifyEmail(id, claims["email"].(string))
|
||||||
@@ -216,7 +214,7 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
|
|||||||
}, gc, true)
|
}, gc, true)
|
||||||
|
|
||||||
app.info.Printf(lm.UserEmailAdjusted, gc.GetString("jfId"))
|
app.info.Printf(lm.UserEmailAdjusted, gc.GetString("jfId"))
|
||||||
gc.Redirect(http.StatusSeeOther, "/my/account")
|
gc.Redirect(http.StatusSeeOther, PAGES.MyAccount)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,13 @@ func GenericErr(status int, err error) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenericErrFromResponse(resp *http.Response, err error) error {
|
||||||
|
if resp == nil {
|
||||||
|
return ErrUnknown{code: -2}
|
||||||
|
}
|
||||||
|
return GenericErr(resp.StatusCode, err)
|
||||||
|
}
|
||||||
|
|
||||||
type ConfigurableTransport interface {
|
type ConfigurableTransport interface {
|
||||||
// SetTransport sets the http.Transport to use for requests. Can be used to set a proxy.
|
// SetTransport sets the http.Transport to use for requests. Can be used to set a proxy.
|
||||||
SetTransport(t *http.Transport)
|
SetTransport(t *http.Transport)
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ var telegramEnabled = false
|
|||||||
var discordEnabled = false
|
var discordEnabled = false
|
||||||
var matrixEnabled = false
|
var matrixEnabled = false
|
||||||
|
|
||||||
|
// URL subpaths. Ignore the "Current" field.
|
||||||
|
var PAGES = PagePaths{}
|
||||||
|
|
||||||
func (app *appContext) GetPath(sect, key string) (fs.FS, string) {
|
func (app *appContext) GetPath(sect, key string) (fs.FS, string) {
|
||||||
val := app.config.Section(sect).Key(key).MustString("")
|
val := app.config.Section(sect).Key(key).MustString("")
|
||||||
if strings.HasPrefix(val, "jfa-go:") {
|
if strings.HasPrefix(val, "jfa-go:") {
|
||||||
@@ -35,6 +38,20 @@ func (app *appContext) MustSetValue(section, key, val string) {
|
|||||||
app.config.Section(section).Key(key).SetValue(app.config.Section(section).Key(key).MustString(val))
|
app.config.Section(section).Key(key).SetValue(app.config.Section(section).Key(key).MustString(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *appContext) MustSetURLPath(section, key, val string) {
|
||||||
|
if !strings.HasPrefix(val, "/") && val != "" {
|
||||||
|
val = "/" + val
|
||||||
|
}
|
||||||
|
app.MustSetValue(section, key, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatSubpath(path string) string {
|
||||||
|
if path == "/" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(path, "/")
|
||||||
|
}
|
||||||
|
|
||||||
func (app *appContext) loadConfig() error {
|
func (app *appContext) loadConfig() error {
|
||||||
var err error
|
var err error
|
||||||
app.config, err = ini.ShadowLoad(app.configPath)
|
app.config, err = ini.ShadowLoad(app.configPath)
|
||||||
@@ -42,8 +59,23 @@ func (app *appContext) loadConfig() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// URLs
|
||||||
|
app.MustSetURLPath("ui", "url_base", "")
|
||||||
|
app.MustSetURLPath("url_paths", "admin", "")
|
||||||
|
app.MustSetURLPath("url_paths", "user_page", "/my/account")
|
||||||
|
app.MustSetURLPath("url_paths", "form", "/invite")
|
||||||
|
PAGES.Base = FormatSubpath(app.config.Section("ui").Key("url_base").String())
|
||||||
|
PAGES.Admin = FormatSubpath(app.config.Section("url_paths").Key("admin").String())
|
||||||
|
PAGES.MyAccount = FormatSubpath(app.config.Section("url_paths").Key("user_page").String())
|
||||||
|
PAGES.Form = FormatSubpath(app.config.Section("url_paths").Key("form").String())
|
||||||
|
if !(app.config.Section("user_page").Key("enabled").MustBool(true)) {
|
||||||
|
PAGES.MyAccount = "disabled"
|
||||||
|
}
|
||||||
|
if PAGES.Base == PAGES.Form || PAGES.Base == "/accounts" || PAGES.Base == "/settings" || PAGES.Base == "/activity" {
|
||||||
|
app.err.Printf(lm.BadURLBase, PAGES.Base)
|
||||||
|
}
|
||||||
|
app.info.Printf(lm.SubpathBlockMessage, PAGES.Base, PAGES.Admin, PAGES.MyAccount, PAGES.Form)
|
||||||
app.MustSetValue("jellyfin", "public_server", app.config.Section("jellyfin").Key("server").String())
|
app.MustSetValue("jellyfin", "public_server", app.config.Section("jellyfin").Key("server").String())
|
||||||
|
|
||||||
app.MustSetValue("ui", "redirect_url", app.config.Section("jellyfin").Key("public_server").String())
|
app.MustSetValue("ui", "redirect_url", app.config.Section("jellyfin").Key("public_server").String())
|
||||||
|
|
||||||
for _, key := range app.config.Section("files").Keys() {
|
for _, key := range app.config.Section("files").Keys() {
|
||||||
@@ -58,12 +90,8 @@ func (app *appContext) loadConfig() error {
|
|||||||
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".db"))))
|
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".db"))))
|
||||||
}
|
}
|
||||||
|
|
||||||
app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/")
|
|
||||||
if app.URLBase == "/invite" || app.URLBase == "/accounts" || app.URLBase == "/settings" || app.URLBase == "/activity" {
|
|
||||||
app.err.Printf(lm.BadURLBase, app.URLBase)
|
|
||||||
}
|
|
||||||
app.ExternalURI = strings.TrimSuffix(strings.TrimSuffix(app.config.Section("ui").Key("jfa_url").MustString(""), "/invite"), "/")
|
app.ExternalURI = strings.TrimSuffix(strings.TrimSuffix(app.config.Section("ui").Key("jfa_url").MustString(""), "/invite"), "/")
|
||||||
if !strings.HasSuffix(app.ExternalURI, app.URLBase) {
|
if !strings.HasSuffix(app.ExternalURI, PAGES.Base) {
|
||||||
app.err.Println(lm.NoURLSuffix)
|
app.err.Println(lm.NoURLSuffix)
|
||||||
}
|
}
|
||||||
if app.ExternalURI == "" {
|
if app.ExternalURI == "" {
|
||||||
|
|||||||
+49
-9
@@ -194,21 +194,25 @@ sections:
|
|||||||
value: Your account has been created. Click below to continue to Jellyfin.
|
value: Your account has been created. Click below to continue to Jellyfin.
|
||||||
description: Displayed when a user creates an account. Use the "post-signup card"
|
description: Displayed when a user creates an account. Use the "post-signup card"
|
||||||
in the Message editor for more control.
|
in the Message editor for more control.
|
||||||
- setting: url_base
|
- setting: subfolder_note
|
||||||
name: Reverse Proxy subfolder
|
name: "Reverse proxy-ing to a subfolder?"
|
||||||
requires_restart: true
|
type: note
|
||||||
type: text
|
description: "Put the folder (e.g. /accounts) in \"Reverse Proxy subfolder\", and the full URL including it (e.g. https://jellyf.in/accounts) in \"External jfa-go URL\"."
|
||||||
description: URL base for when running jfa-go with a reverse proxy in a subfolder.
|
|
||||||
include preceding /, e.g "/accounts".
|
|
||||||
- setting: jfa_url
|
- setting: jfa_url
|
||||||
name: External jfa-go URL
|
name: External jfa-go URL
|
||||||
required: true
|
required: true
|
||||||
depends_true: enabled
|
depends_true: enabled
|
||||||
type: text
|
type: text
|
||||||
value: http://accounts.jellyf.in:8056
|
value: http://accounts.jellyf.in:8056
|
||||||
description: The URL at which the jfa-go root (admin page) is accessible, including
|
description: The URL at which the jfa-go root (usually the admin page) is accessible, including
|
||||||
the subfolder if you use one. This is necessary because using a reverse proxy
|
the subfolder if you use one. This is necessary because using a reverse proxy
|
||||||
means the program has no way of knowing the URL itself.
|
means the program has no way of knowing the URL itself.
|
||||||
|
- setting: url_base
|
||||||
|
name: Reverse Proxy subfolder
|
||||||
|
requires_restart: true
|
||||||
|
type: text
|
||||||
|
description: URL base for when running jfa-go with a reverse proxy in a subfolder.
|
||||||
|
include preceding /, e.g "/accounts".
|
||||||
- setting: redirect_url
|
- setting: redirect_url
|
||||||
name: Form success redirect URL
|
name: Form success redirect URL
|
||||||
type: text
|
type: text
|
||||||
@@ -232,6 +236,37 @@ sections:
|
|||||||
- ["opaque", "Opaque"]
|
- ["opaque", "Opaque"]
|
||||||
value: clear
|
value: clear
|
||||||
description: Appearance of the Admin login screen.
|
description: Appearance of the Admin login screen.
|
||||||
|
- setting: urlpaths_note
|
||||||
|
name: "URL Paths:"
|
||||||
|
type: note
|
||||||
|
description: "Want \"My Account\" at \"/\" or the admin page at \"/admin\"? Enable advanced settings and check the \"URL Paths\" section."
|
||||||
|
- section: url_paths
|
||||||
|
meta:
|
||||||
|
name: URL Paths
|
||||||
|
description: Settings for changing where different pages are accessed. If you change & forget these, they're printed in the logs on startup. Paths should have a slash at the beginning but not at the end.
|
||||||
|
advanced: true
|
||||||
|
settings:
|
||||||
|
- setting: admin
|
||||||
|
name: Admin page subpath
|
||||||
|
type: text
|
||||||
|
required: true
|
||||||
|
requires_restart: true
|
||||||
|
value: ""
|
||||||
|
description: URL subpath the admin page should be at.
|
||||||
|
- setting: user_page
|
||||||
|
name: "\"My Account\" subpath"
|
||||||
|
type: text
|
||||||
|
required: true
|
||||||
|
requires_restart: true
|
||||||
|
value: "/my/account"
|
||||||
|
description: URL subpath the "My Account" page should be at.
|
||||||
|
- setting: form
|
||||||
|
name: Invite subpath
|
||||||
|
type: text
|
||||||
|
required: true
|
||||||
|
requires_restart: true
|
||||||
|
value: "/invite"
|
||||||
|
description: URL subpath invites should be on.
|
||||||
- section: advanced
|
- section: advanced
|
||||||
meta:
|
meta:
|
||||||
name: Advanced
|
name: Advanced
|
||||||
@@ -720,6 +755,12 @@ sections:
|
|||||||
type: bool
|
type: bool
|
||||||
value: false
|
value: false
|
||||||
description: Disables using the same address on multiple accounts.
|
description: Disables using the same address on multiple accounts.
|
||||||
|
- setting: test_note
|
||||||
|
name: 'Test your settings:'
|
||||||
|
type: note
|
||||||
|
depends_true: enabled
|
||||||
|
required: false
|
||||||
|
description: Go over to the accounts tab, select your user (ensuring you've assigned it an email address) and send yourself an announcement.
|
||||||
- section: mailgun
|
- section: mailgun
|
||||||
meta:
|
meta:
|
||||||
name: Mailgun (Email)
|
name: Mailgun (Email)
|
||||||
@@ -1082,8 +1123,7 @@ sections:
|
|||||||
type: note
|
type: note
|
||||||
depends_true: link_reset
|
depends_true: link_reset
|
||||||
required: false
|
required: false
|
||||||
description: Set the "External jfa-go URL" in General so that links to jfa-go
|
description: Set the "External jfa-go URL" in General so that links to jfa-go can be made.
|
||||||
can be made.
|
|
||||||
- setting: language
|
- setting: language
|
||||||
name: Default reset link language
|
name: Default reset link language
|
||||||
requires_restart: true
|
requires_restart: true
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ func (emailer *Emailer) confirmationValues(code, username, key string, app *appC
|
|||||||
if code == "" { // Personal email change
|
if code == "" { // Personal email change
|
||||||
inviteLink = fmt.Sprintf("%s/my/confirm/%s", inviteLink, url.PathEscape(key))
|
inviteLink = fmt.Sprintf("%s/my/confirm/%s", inviteLink, url.PathEscape(key))
|
||||||
} else { // Invite email confirmation
|
} else { // Invite email confirmation
|
||||||
inviteLink = fmt.Sprintf("%s/invite/%s?key=%s", inviteLink, code, url.PathEscape(key))
|
inviteLink = fmt.Sprintf("%s%s/%s?key=%s", inviteLink, PAGES.Form, code, url.PathEscape(key))
|
||||||
}
|
}
|
||||||
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username})
|
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username})
|
||||||
template["confirmationURL"] = inviteLink
|
template["confirmationURL"] = inviteLink
|
||||||
@@ -393,7 +393,7 @@ func (emailer *Emailer) inviteValues(code string, invite Invite, app *appContext
|
|||||||
expiry := invite.ValidTill
|
expiry := invite.ValidTill
|
||||||
d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern)
|
d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern)
|
||||||
message := app.config.Section("messages").Key("message").String()
|
message := app.config.Section("messages").Key("message").String()
|
||||||
inviteLink := fmt.Sprintf("%s/invite/%s", app.ExternalURI, code)
|
inviteLink := fmt.Sprintf("%s%s/%s", app.ExternalURI, PAGES.Form, code)
|
||||||
template := map[string]interface{}{
|
template := map[string]interface{}{
|
||||||
"hello": emailer.lang.InviteEmail.get("hello"),
|
"hello": emailer.lang.InviteEmail.get("hello"),
|
||||||
"youHaveBeenInvited": emailer.lang.InviteEmail.get("youHaveBeenInvited"),
|
"youHaveBeenInvited": emailer.lang.InviteEmail.get("youHaveBeenInvited"),
|
||||||
|
|||||||
+1
-2
@@ -1,9 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="{{ .cssClass }}">
|
<html lang="en" class="{{ .cssClass }}">
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
|
|
||||||
{{ template "header.html" . }}
|
|
||||||
<title>404 - jfa-go</title>
|
<title>404 - jfa-go</title>
|
||||||
|
{{ template "header.html" . }}
|
||||||
</head>
|
</head>
|
||||||
<body class="section">
|
<body class="section">
|
||||||
<div class="page-container m-2 lg:my-20 lg:mx-64">
|
<div class="page-container m-2 lg:my-20 lg:mx-64">
|
||||||
|
|||||||
+3
-13
@@ -1,16 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="{{ .cssClass }}">
|
<html lang="en" class="{{ .cssClass }}">
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" type="text/css" href="{{ .urlBase }}/css/{{ .cssVersion }}bundle.css">
|
|
||||||
<script>
|
<script>
|
||||||
window.URLBase = "{{ .urlBase }}";
|
|
||||||
window.notificationsEnabled = {{ .notifications }};
|
|
||||||
window.emailEnabled = {{ .emailEnabled }};
|
|
||||||
window.telegramEnabled = {{ .telegramEnabled }};
|
|
||||||
window.discordEnabled = {{ .discordEnabled }};
|
|
||||||
window.matrixEnabled = {{ .matrixEnabled }};
|
|
||||||
window.ombiEnabled = {{ .ombiEnabled }};
|
|
||||||
window.jellyseerrEnabled = {{ .jellyseerrEnabled }};
|
|
||||||
window.usernameEnabled = {{ .username }};
|
window.usernameEnabled = {{ .username }};
|
||||||
window.langFile = JSON.parse({{ .language }});
|
window.langFile = JSON.parse({{ .language }});
|
||||||
window.linkResetEnabled = {{ .linkResetEnabled }};
|
window.linkResetEnabled = {{ .linkResetEnabled }};
|
||||||
@@ -18,7 +9,6 @@
|
|||||||
window.jellyfinLogin = {{ .jellyfinLogin }};
|
window.jellyfinLogin = {{ .jellyfinLogin }};
|
||||||
window.jfAdminOnly = {{ .jfAdminOnly }};
|
window.jfAdminOnly = {{ .jfAdminOnly }};
|
||||||
window.jfAllowAll = {{ .jfAllowAll }};
|
window.jfAllowAll = {{ .jfAllowAll }};
|
||||||
window.referralsEnabled = {{ .referralsEnabled }};
|
|
||||||
window.loginAppearance = "{{ .loginAppearance }}";
|
window.loginAppearance = "{{ .loginAppearance }}";
|
||||||
</script>
|
</script>
|
||||||
<title>Admin - jfa-go</title>
|
<title>Admin - jfa-go</title>
|
||||||
@@ -47,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="modal-about" class="modal">
|
<div id="modal-about" class="modal">
|
||||||
<div class="relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/2 content card">
|
<div class="relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/2 content card">
|
||||||
<img src="{{ .urlBase }}/banner.svg" class="banner header" alt="jfa-go banner">
|
<img src="{{ .pages.Base }}/banner.svg" class="banner header" alt="jfa-go banner">
|
||||||
<span class="heading"><span class="modal-close">×</span></span>
|
<span class="heading"><span class="modal-close">×</span></span>
|
||||||
<p>{{ .strings.version }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .version }}</span></p>
|
<p>{{ .strings.version }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .version }}</span></p>
|
||||||
<p>{{ .strings.commitNoun }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .commit }}</span></p>
|
<p>{{ .strings.commitNoun }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .commit }}</span></p>
|
||||||
@@ -551,7 +541,7 @@
|
|||||||
<span class="button ~critical @low unfocused" id="logout-button">{{ .strings.logout }}</span>
|
<span class="button ~critical @low unfocused" id="logout-button">{{ .strings.logout }}</span>
|
||||||
{{ if .userPageEnabled }}
|
{{ if .userPageEnabled }}
|
||||||
<div class="">
|
<div class="">
|
||||||
<a class="button ~info" href="{{ .urlBase }}/my/account"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
|
<a class="button ~info" href="{{ .pages.Base }}{{ .pages.MyAccount }}"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
@@ -925,6 +915,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="{{ .urlBase }}/js/admin.js" type="module"></script>
|
<script src="{{ .pages.Base }}/js/admin.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<!--- This CSS is inlined so we should keep this here! -->
|
||||||
<link inline rel="stylesheet" type="text/css" href="web/css/v3bundle.css">
|
<link inline rel="stylesheet" type="text/css" href="web/css/v3bundle.css">
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
<title>Crash report</title>
|
<title>Crash report</title>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="{{ .cssClass }}">
|
<html lang="en" class="{{ .cssClass }}">
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" type="text/css" href="{{ .urlBase }}/css/{{ .cssVersion }}bundle.css">
|
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
<title>{{ .strings.successHeader }} - jfa-go</title>
|
<title>{{ .strings.successHeader }} - jfa-go</title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
window.usernameEnabled = {{ .username }};
|
window.usernameEnabled = {{ .username }};
|
||||||
window.validationStrings = JSON.parse({{ .validationStrings }});
|
window.validationStrings = JSON.parse({{ .validationStrings }});
|
||||||
window.invalidPassword = "{{ .strings.reEnterPasswordInvalid }}";
|
window.invalidPassword = "{{ .strings.reEnterPasswordInvalid }}";
|
||||||
window.URLBase = "{{ .urlBase }}";
|
|
||||||
window.code = "{{ .code }}";
|
window.code = "{{ .code }}";
|
||||||
window.language = "{{ .langName }}";
|
window.language = "{{ .langName }}";
|
||||||
window.messages = JSON.parse({{ .notifications }});
|
window.messages = JSON.parse({{ .notifications }});
|
||||||
@@ -14,16 +13,13 @@
|
|||||||
window.userExpiryHours = {{ .userExpiryHours }};
|
window.userExpiryHours = {{ .userExpiryHours }};
|
||||||
window.userExpiryMinutes = {{ .userExpiryMinutes }};
|
window.userExpiryMinutes = {{ .userExpiryMinutes }};
|
||||||
window.userExpiryMessage = {{ .userExpiryMessage }};
|
window.userExpiryMessage = {{ .userExpiryMessage }};
|
||||||
window.telegramEnabled = {{ .telegramEnabled }};
|
|
||||||
window.telegramRequired = {{ .telegramRequired }};
|
window.telegramRequired = {{ .telegramRequired }};
|
||||||
window.telegramPIN = "{{ .telegramPIN }}";
|
window.telegramPIN = "{{ .telegramPIN }}";
|
||||||
window.emailRequired = {{ .emailRequired }};
|
window.emailRequired = {{ .emailRequired }};
|
||||||
window.discordEnabled = {{ .discordEnabled }};
|
|
||||||
window.discordRequired = {{ .discordRequired }};
|
window.discordRequired = {{ .discordRequired }};
|
||||||
window.discordPIN = "{{ .discordPIN }}";
|
window.discordPIN = "{{ .discordPIN }}";
|
||||||
window.discordInviteLink = {{ .discordInviteLink }};
|
window.discordInviteLink = {{ .discordInviteLink }};
|
||||||
window.discordServerName = "{{ .discordServerName }}";
|
window.discordServerName = "{{ .discordServerName }}";
|
||||||
window.matrixEnabled = {{ .matrixEnabled }};
|
|
||||||
window.matrixRequired = {{ .matrixRequired }};
|
window.matrixRequired = {{ .matrixRequired }};
|
||||||
window.matrixUserID = "{{ .matrixUser }}";
|
window.matrixUserID = "{{ .matrixUser }}";
|
||||||
window.captcha = {{ .captcha }};
|
window.captcha = {{ .captcha }};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="{{ .cssClass }}">
|
<html lang="en" class="{{ .cssClass }}">
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
|
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
{{ if .passwordReset }}
|
{{ if .passwordReset }}
|
||||||
<title>{{ .strings.passwordReset }}</title>
|
<title>{{ .strings.passwordReset }}</title>
|
||||||
|
|||||||
+24
-5
@@ -1,13 +1,32 @@
|
|||||||
|
<link rel="stylesheet" type="text/css" href="{{ .pages.Base }}/css/{{ .cssVersion }}bundle.css">
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<meta name="Description" content="jfa-go, a better way to manage Jellyfin users.">
|
<meta name="Description" content="jfa-go, a better way to manage Jellyfin users.">
|
||||||
<meta name="color-scheme" content="dark light">
|
<meta name="color-scheme" content="dark light">
|
||||||
<meta name="robots" content="noindex">
|
<meta name="robots" content="noindex">
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ .urlBase }}/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="{{ .pages.Base }}/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ .urlBase }}/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ .pages.Base }}/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ .urlBase }}/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ .pages.Base }}/favicon-16x16.png">
|
||||||
<link rel="manifest" href="{{ .urlBase }}/site.webmanifest">
|
<link rel="manifest" href="{{ .pages.Base }}/site.webmanifest">
|
||||||
<link rel="mask-icon" href="{{ .urlBase }}/safari-pinned-tab.svg" color="#5bbad5">
|
<link rel="mask-icon" href="{{ .pages.Base }}/safari-pinned-tab.svg" color="#5bbad5">
|
||||||
<meta name="msapplication-TileColor" content="#603cba">
|
<meta name="msapplication-TileColor" content="#603cba">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
<script>
|
||||||
|
window.pages = {
|
||||||
|
"Base": "{{ .pages.Base }}",
|
||||||
|
"Current": "{{ .pages.Current }}",
|
||||||
|
"Admin": "{{ .pages.Admin }}",
|
||||||
|
"MyAccount": "{{ .pages.MyAccount }}",
|
||||||
|
"Form": "{{ .pages.Form }}"
|
||||||
|
};
|
||||||
|
window.emailEnabled = {{ .emailEnabled }};
|
||||||
|
window.discordEnabled = {{ .discordEnabled }};
|
||||||
|
window.telegramEnabled = {{ .telegramEnabled }};
|
||||||
|
window.matrixEnabled = {{ .matrixEnabled }};
|
||||||
|
window.notificationsEnabled = {{ .notifications }};
|
||||||
|
window.ombiEnabled = {{ .ombiEnabled }};
|
||||||
|
window.jellyseerrEnabled = {{ .jellyseerrEnabled }};
|
||||||
|
window.referralsEnabled = {{ .referralsEnabled }};
|
||||||
|
window.pwrEnabled = {{ .pwrEnabled }};
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="{{ .cssClass }}">
|
<html lang="en" class="{{ .cssClass }}">
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" type="text/css" href="{{ .urlBase }}/css/{{ .cssVersion }}bundle.css">
|
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
<title>Invalid Code - jfa-go</title>
|
<title>Invalid Code - jfa-go</title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
{{ $hasTwoCards = 1 }}
|
{{ $hasTwoCards = 1 }}
|
||||||
<div class="card mx-2 flex-initial w-full lg:w-[35%] mb-4 lg:mb-0 dark:~d_neutral @low content">
|
<div class="card mx-2 flex-initial w-full lg:w-[35%] mb-4 lg:mb-0 dark:~d_neutral @low content">
|
||||||
<span class="heading row">{{ .strings.loginNotAdmin }}</span>
|
<span class="heading row">{{ .strings.loginNotAdmin }}</span>
|
||||||
<a class="button ~info h-12 w-full" href="{{ .urlBase }}/my/account"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
|
<a class="button ~info h-12 w-full" href="{{ .pages.Base }}{{ .pages.MyAccount }}"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="{{ .cssClass }}">
|
<html lang="en" class="{{ .cssClass }}">
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
|
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
<title>{{ .strings.passwordReset }} - jfa-go</title>
|
<title>{{ .strings.passwordReset }} - jfa-go</title>
|
||||||
</head>
|
</head>
|
||||||
@@ -40,6 +39,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<i class="content">{{ .contactMessage }}</i>
|
<i class="content">{{ .contactMessage }}</i>
|
||||||
</div>
|
</div>
|
||||||
<script src="{{ .urlBase }}/js/pwr-pin.js" type="module"></script>
|
<script src="{{ .pages.Base }}/js/pwr-pin.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+16
-17
@@ -1,20 +1,19 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="light">
|
<html lang="en" class="light">
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
|
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
<title>{{ .lang.Strings.pageTitle }}</title>
|
<title>{{ .lang.Strings.pageTitle }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="max-w-full overflow-x-hidden section">
|
<body class="max-w-full overflow-x-hidden section">
|
||||||
<div id="notification-box"></div>
|
<div id="notification-box"></div>
|
||||||
<div class="page-container m-2 lg:my-20 lg:mx-64 flex flex-col gap-4">
|
<div class="page-container m-2 lg:my-20 lg:mx-64 flex flex-col gap-4 items-center">
|
||||||
<div class="top-2 inset-x-2 lg:absolute flex flex-row justify-between">
|
<div class="top-2 inset-x-2 lg:absolute flex flex-row justify-between">
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
{{ template "lang-select.html" . }}
|
{{ template "lang-select.html" . }}
|
||||||
</div>
|
</div>
|
||||||
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
|
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low flex flex-col gap-4 justify-between items-center">
|
<div class="card lg:container sectioned ~neutral @low flex flex-col gap-4 justify-between items-center">
|
||||||
<img class="w-[105%] max-w-none" src="banner.svg" alt="jfa-go" />
|
<img class="w-[105%] max-w-none" src="banner.svg" alt="jfa-go" />
|
||||||
<span class="heading welcome">{{ .lang.StartPage.welcome }}</span>
|
<span class="heading welcome">{{ .lang.StartPage.welcome }}</span>
|
||||||
<p class="content text-center">{{ .lang.StartPage.pressStart }}</p>
|
<p class="content text-center">{{ .lang.StartPage.pressStart }}</p>
|
||||||
@@ -23,7 +22,7 @@
|
|||||||
<span class="button ~urge @low next">{{ .lang.StartPage.start }}</span>
|
<span class="button ~urge @low next">{{ .lang.StartPage.start }}</span>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused">
|
<div class="card lg:container sectioned ~neutral @low unfocused">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<span class="heading">{{ .lang.Language.title }}</span>
|
<span class="heading">{{ .lang.Language.title }}</span>
|
||||||
<p class="content" id="language-description"></p>
|
<p class="content" id="language-description"></p>
|
||||||
@@ -54,7 +53,7 @@
|
|||||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused">
|
<div class="card lg:container sectioned ~neutral @low unfocused">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<span class="heading">{{ .lang.General.title }}</span>
|
<span class="heading">{{ .lang.General.title }}</span>
|
||||||
<div class="flex flex-row gap-2 justify-between">
|
<div class="flex flex-row gap-2 justify-between">
|
||||||
@@ -154,7 +153,7 @@
|
|||||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused">
|
<div class="card lg:container sectioned ~neutral @low unfocused">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<span class="heading">{{ .lang.Login.title }}</span>
|
<span class="heading">{{ .lang.Login.title }}</span>
|
||||||
<p class="content">{{ .lang.Login.description }}</p>
|
<p class="content">{{ .lang.Login.description }}</p>
|
||||||
@@ -199,7 +198,7 @@
|
|||||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused">
|
<div class="card lg:container sectioned ~neutral @low unfocused">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<span class="heading">{{ .lang.JellyfinEmby.title }}</span>
|
<span class="heading">{{ .lang.JellyfinEmby.title }}</span>
|
||||||
<p class="content">{{ .lang.JellyfinEmby.description }}</p>
|
<p class="content">{{ .lang.JellyfinEmby.description }}</p>
|
||||||
@@ -250,7 +249,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused">
|
<div class="card lg:container sectioned ~neutral @low unfocused">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<span class="heading">{{ .lang.Ombi.title }}</span>
|
<span class="heading">{{ .lang.Ombi.title }}</span>
|
||||||
<p class="content">{{ .lang.Ombi.description }}</p>
|
<p class="content">{{ .lang.Ombi.description }}</p>
|
||||||
@@ -292,7 +291,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused">
|
<div class="card lg:container sectioned ~neutral @low unfocused">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<span class="heading">{{ .lang.UserPage.title }}</span>
|
<span class="heading">{{ .lang.UserPage.title }}</span>
|
||||||
<p class="content">{{ .lang.UserPage.description }}</p>
|
<p class="content">{{ .lang.UserPage.description }}</p>
|
||||||
@@ -309,7 +308,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused">
|
<div class="card lg:container sectioned ~neutral @low unfocused">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<span class="heading">{{ .lang.Messages.title }}</span>
|
<span class="heading">{{ .lang.Messages.title }}</span>
|
||||||
<p class="content" id="messages-description"></p>
|
<p class="content" id="messages-description"></p>
|
||||||
@@ -404,7 +403,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused related-to-email">
|
<div class="card lg:container sectioned ~neutral @low unfocused related-to-email">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<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>
|
||||||
@@ -428,7 +427,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused related-to-email">
|
<div class="card lg:container sectioned ~neutral @low unfocused related-to-email">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<span class="heading">{{ .lang.InviteEmails.title }}</span>
|
<span class="heading">{{ .lang.InviteEmails.title }}</span>
|
||||||
<p class="content">{{ .lang.InviteEmails.description }}</p>
|
<p class="content">{{ .lang.InviteEmails.description }}</p>
|
||||||
@@ -447,7 +446,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div id="password-resets" class="card sectioned ~neutral @low unfocused related-to-email">
|
<div id="password-resets" class="card lg:container sectioned ~neutral @low unfocused related-to-email">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<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>
|
||||||
@@ -487,7 +486,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused">
|
<div class="card lg:container sectioned ~neutral @low unfocused">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<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>
|
||||||
@@ -522,7 +521,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused">
|
<div class="card lg:container sectioned ~neutral @low unfocused">
|
||||||
<section class="section flex flex-col gap-2 justify-between">
|
<section class="section flex flex-col gap-2 justify-between">
|
||||||
<span class="heading">{{ .lang.HelpMessages.title }}</span>
|
<span class="heading">{{ .lang.HelpMessages.title }}</span>
|
||||||
<p class="content">{{ .lang.HelpMessages.description }}</p>
|
<p class="content">{{ .lang.HelpMessages.description }}</p>
|
||||||
@@ -555,10 +554,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="card sectioned ~neutral @low unfocused">
|
<div class="card lg:container sectioned ~neutral @low unfocused">
|
||||||
<section class="section flex flex-col gap-2 justify-center items-center">
|
<section class="section flex flex-col gap-2 justify-center items-center">
|
||||||
<span class="heading">{{ .lang.EndPage.finished }}</span>
|
<span class="heading">{{ .lang.EndPage.finished }}</span>
|
||||||
<p class="content">{{ .lang.EndPage.restartMessage }}</p>
|
<p class="content text-center">{{ .lang.EndPage.restartMessage }} {{ .lang.EndPage.urlChangedNotice }}</p>
|
||||||
</section>
|
</section>
|
||||||
<section class="section w-full ~neutral footer flex flex-row justify-center items-center gap-2">
|
<section class="section w-full ~neutral footer flex flex-row justify-center items-center gap-2">
|
||||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||||
|
|||||||
+1
-11
@@ -1,31 +1,21 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="light">
|
<html lang="en" class="light">
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" type="text/css" href="{{ .urlBase }}/css/{{ .cssVersion }}bundle.css">
|
|
||||||
<script>
|
<script>
|
||||||
window.URLBase = "{{ .urlBase }}";
|
|
||||||
window.notificationsEnabled = {{ .notifications }};
|
|
||||||
window.ombiEnabled = {{ .ombiEnabled }};
|
|
||||||
window.langFile = JSON.parse({{ .language }});
|
window.langFile = JSON.parse({{ .language }});
|
||||||
window.pwrEnabled = {{ .pwrEnabled }};
|
|
||||||
window.linkResetEnabled = {{ .linkResetEnabled }};
|
window.linkResetEnabled = {{ .linkResetEnabled }};
|
||||||
window.language = "{{ .langName }}";
|
window.language = "{{ .langName }}";
|
||||||
window.telegramEnabled = {{ .telegramEnabled }};
|
|
||||||
window.telegramRequired = {{ .telegramRequired }};
|
window.telegramRequired = {{ .telegramRequired }};
|
||||||
window.telegramUsername = {{ .telegramUsername }};
|
window.telegramUsername = {{ .telegramUsername }};
|
||||||
window.telegramURL = {{ .telegramURL }};
|
window.telegramURL = {{ .telegramURL }};
|
||||||
window.emailEnabled = {{ .emailEnabled }};
|
|
||||||
window.emailRequired = {{ .emailRequired }};
|
window.emailRequired = {{ .emailRequired }};
|
||||||
window.discordEnabled = {{ .discordEnabled }};
|
|
||||||
window.discordRequired = {{ .discordRequired }};
|
window.discordRequired = {{ .discordRequired }};
|
||||||
window.discordServerName = "{{ .discordServerName }}";
|
window.discordServerName = "{{ .discordServerName }}";
|
||||||
window.discordInviteLink = {{ .discordInviteLink }};
|
window.discordInviteLink = {{ .discordInviteLink }};
|
||||||
window.discordSendPINMessage = "{{ .discordSendPINMessage }}";
|
window.discordSendPINMessage = "{{ .discordSendPINMessage }}";
|
||||||
window.matrixEnabled = {{ .matrixEnabled }};
|
|
||||||
window.matrixRequired = {{ .matrixRequired }};
|
window.matrixRequired = {{ .matrixRequired }};
|
||||||
window.matrixUserID = "{{ .matrixUser }}";
|
window.matrixUserID = "{{ .matrixUser }}";
|
||||||
window.validationStrings = JSON.parse({{ .validationStrings }});
|
window.validationStrings = JSON.parse({{ .validationStrings }});
|
||||||
window.referralsEnabled = {{ .referralsEnabled }};
|
|
||||||
</script>
|
</script>
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
<title>{{ .strings.myAccount }}</title>
|
<title>{{ .strings.myAccount }}</title>
|
||||||
@@ -156,7 +146,7 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="{{ .urlBase }}/js/user.js" type="module"></script>
|
<script src="{{ .pages.Base }}/js/user.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func (js *Jellyseerr) req(mode string, uri string, data any, queryParams url.Val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp, err := js.httpClient.Do(req)
|
resp, err := js.httpClient.Do(req)
|
||||||
err = co.GenericErr(resp.StatusCode, err)
|
err = co.GenericErrFromResponse(resp, err)
|
||||||
defer js.timeoutHandler()
|
defer js.timeoutHandler()
|
||||||
var responseText string
|
var responseText string
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"endPage": {
|
"endPage": {
|
||||||
"finished": "Finished!",
|
"finished": "Finished!",
|
||||||
"restartMessage": "Features like Discord/Telegram/Matrix bots, custom Markdown messages, and a user-accessible \"My Account\" page can be found in Settings, so make sure to give it a browse. Click below to restart, then refresh the page.",
|
"restartMessage": "Features like Discord/Telegram/Matrix bots, custom Markdown messages, and a user-accessible \"My Account\" page can be found in Settings, so make sure to give it a browse. Click below to restart, then refresh the page.",
|
||||||
|
"urlChangedNotice": "If you've changed the host, port, subfolder etc. that jfa-go is hosted on, check the URL is right.",
|
||||||
"refreshPage": "Refresh"
|
"refreshPage": "Refresh"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
|
|||||||
@@ -212,9 +212,10 @@ const (
|
|||||||
InitProxy = "Initialized proxy @ \"%s\""
|
InitProxy = "Initialized proxy @ \"%s\""
|
||||||
FailedInitProxy = "Failed to initialize proxy @ \"%s\": %v\nStartup will pause for a bit to grab your attention."
|
FailedInitProxy = "Failed to initialize proxy @ \"%s\": %v\nStartup will pause for a bit to grab your attention."
|
||||||
NoURLSuffix = `Warning: Given "jfa_url"/"External jfa-go URL" value does not include "url_base" value!`
|
NoURLSuffix = `Warning: Given "jfa_url"/"External jfa-go URL" value does not include "url_base" value!`
|
||||||
BadURLBase = `Warning: Given URL Base "%s" may conflict with the applications subpaths.`
|
BadURLBase = `Warning: Given reverse proxy subfolder "%s" may conflict with the applications subpaths.`
|
||||||
NoExternalHost = `No "External jfa-go URL" provided, set one in Settings > General.`
|
NoExternalHost = `No "External jfa-go URL" provided, set one in Settings > General.`
|
||||||
LoginWontSave = ` Your login won't save until you do.`
|
LoginWontSave = ` Your login won't save until you do.`
|
||||||
|
SubpathBlockMessage = `URLs: Root subfolder = "%s", Admin = "%s", My Account = "%s", Invite forms = "%s"`
|
||||||
|
|
||||||
// discord.go
|
// discord.go
|
||||||
StartDaemon = "Started %s daemon"
|
StartDaemon = "Started %s daemon"
|
||||||
|
|||||||
@@ -103,37 +103,37 @@ type appContext struct {
|
|||||||
adminUsers []User
|
adminUsers []User
|
||||||
invalidTokens []string
|
invalidTokens []string
|
||||||
// Keeping jf name because I can't think of a better one
|
// Keeping jf name because I can't think of a better one
|
||||||
jf *mediabrowser.MediaBrowser
|
jf *mediabrowser.MediaBrowser
|
||||||
authJf *mediabrowser.MediaBrowser
|
authJf *mediabrowser.MediaBrowser
|
||||||
ombi *OmbiWrapper
|
ombi *OmbiWrapper
|
||||||
js *JellyseerrWrapper
|
js *JellyseerrWrapper
|
||||||
thirdPartyServices []ThirdPartyService
|
thirdPartyServices []ThirdPartyService
|
||||||
datePattern string
|
datePattern string
|
||||||
timePattern string
|
timePattern string
|
||||||
storage Storage
|
storage Storage
|
||||||
validator Validator
|
validator Validator
|
||||||
email *Emailer
|
email *Emailer
|
||||||
telegram *TelegramDaemon
|
telegram *TelegramDaemon
|
||||||
discord *DiscordDaemon
|
discord *DiscordDaemon
|
||||||
matrix *MatrixDaemon
|
matrix *MatrixDaemon
|
||||||
contactMethods []ContactMethodLinker
|
contactMethods []ContactMethodLinker
|
||||||
info, debug, err *logger.Logger
|
info, debug, err *logger.Logger
|
||||||
host string
|
host string
|
||||||
port int
|
port int
|
||||||
version string
|
version string
|
||||||
URLBase, ExternalURI, ExternalDomain string
|
ExternalURI, ExternalDomain string
|
||||||
updater *Updater
|
updater *Updater
|
||||||
webhooks *WebhookSender
|
webhooks *WebhookSender
|
||||||
newUpdate bool // Whether whatever's in update is new.
|
newUpdate bool // Whether whatever's in update is new.
|
||||||
tag Tag
|
tag Tag
|
||||||
update Update
|
update Update
|
||||||
proxyEnabled bool
|
proxyEnabled bool
|
||||||
proxyTransport *http.Transport
|
proxyTransport *http.Transport
|
||||||
proxyConfig easyproxy.ProxyConfig
|
proxyConfig easyproxy.ProxyConfig
|
||||||
internalPWRs map[string]InternalPWR
|
internalPWRs map[string]InternalPWR
|
||||||
pwrCaptchas map[string]Captcha
|
pwrCaptchas map[string]Captcha
|
||||||
ConfirmationKeys map[string]map[string]ConfirmationKey // Map of invite code to jwt to request
|
ConfirmationKeys map[string]map[string]ConfirmationKey // Map of invite code to jwt to request
|
||||||
confirmationKeysLock sync.Mutex
|
confirmationKeysLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateSecret(length int) (string, error) {
|
func generateSecret(length int) (string, error) {
|
||||||
@@ -147,7 +147,7 @@ func generateSecret(length int) (string, error) {
|
|||||||
|
|
||||||
func test(app *appContext) {
|
func test(app *appContext) {
|
||||||
fmt.Printf("\n\n----\n\n")
|
fmt.Printf("\n\n----\n\n")
|
||||||
settings := map[string]interface{}{
|
settings := map[string]any{
|
||||||
"server": app.jf.Server,
|
"server": app.jf.Server,
|
||||||
"server version": app.jf.ServerInfo.Version,
|
"server version": app.jf.ServerInfo.Version,
|
||||||
"server name": app.jf.ServerInfo.Name,
|
"server name": app.jf.ServerInfo.Name,
|
||||||
|
|||||||
@@ -467,3 +467,18 @@ type ContactMethodKey struct {
|
|||||||
PIN string
|
PIN string
|
||||||
User ContactMethodUser
|
User ContactMethodUser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PagePaths struct {
|
||||||
|
// The base subfolder the app is hosted on.
|
||||||
|
Base string `json:"Base"`
|
||||||
|
// Those for other pages
|
||||||
|
Admin string `json:"Admin"`
|
||||||
|
MyAccount string `json:"MyAccount"`
|
||||||
|
Form string `json:"Form"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PagePathsDTO struct {
|
||||||
|
PagePaths
|
||||||
|
// The subdirectory this bit of the app is hosted on (e.g. admin is usually on "/", myacc is usually on "/my/account")
|
||||||
|
Current string `json:"Current"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ func (app *appContext) loadRouter(address string, debug bool) *gin.Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) loadRoutes(router *gin.Engine) {
|
func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||||
routePrefixes := []string{app.URLBase}
|
routePrefixes := []string{PAGES.Base}
|
||||||
if app.URLBase != "" {
|
if PAGES.Base != "" {
|
||||||
routePrefixes = append(routePrefixes, "")
|
routePrefixes = append(routePrefixes, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
for _, p := range routePrefixes {
|
for _, p := range routePrefixes {
|
||||||
router.GET(p+"/lang/:page", app.GetLanguages)
|
router.GET(p+"/lang/:page", app.GetLanguages)
|
||||||
router.Use(static.Serve(p+"/", app.webFS))
|
router.Use(static.Serve(p+"/", app.webFS))
|
||||||
router.GET(p+"/", app.AdminPage)
|
router.GET(p+PAGES.Admin, app.AdminPage)
|
||||||
|
|
||||||
if app.config.Section("password_resets").Key("link_reset").MustBool(false) {
|
if app.config.Section("password_resets").Key("link_reset").MustBool(false) {
|
||||||
router.GET(p+"/reset", app.ResetPassword)
|
router.GET(p+"/reset", app.ResetPassword)
|
||||||
@@ -127,39 +127,39 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router.GET(p+"/accounts", app.AdminPage)
|
router.GET(p+PAGES.Admin+"/accounts", app.AdminPage)
|
||||||
router.GET(p+"/settings", app.AdminPage)
|
router.GET(p+PAGES.Admin+"/settings", app.AdminPage)
|
||||||
router.GET(p+"/activity", app.AdminPage)
|
router.GET(p+PAGES.Admin+"/activity", app.AdminPage)
|
||||||
router.GET(p+"/accounts/user/:userID", app.AdminPage)
|
router.GET(p+PAGES.Admin+"/accounts/user/:userID", app.AdminPage)
|
||||||
router.GET(p+"/invites/:code", app.AdminPage)
|
router.GET(p+PAGES.Admin+"/invites/:code", app.AdminPage)
|
||||||
router.GET(p+"/lang/:page/:file", app.ServeLang)
|
router.GET(p+"/lang/:page/:file", app.ServeLang)
|
||||||
router.GET(p+"/token/login", app.getTokenLogin)
|
router.GET(p+"/token/login", app.getTokenLogin)
|
||||||
router.GET(p+"/token/refresh", app.getTokenRefresh)
|
router.GET(p+"/token/refresh", app.getTokenRefresh)
|
||||||
router.POST(p+"/user/invite", app.NewUserFromInvite)
|
router.POST(p+"/user/invite", app.NewUserFromInvite)
|
||||||
router.Use(static.Serve(p+"/invite/", app.webFS))
|
router.Use(static.Serve(p+PAGES.Form, app.webFS))
|
||||||
router.GET(p+"/invite/:invCode", app.InviteProxy)
|
router.GET(p+PAGES.Form+"/:invCode", app.InviteProxy)
|
||||||
if app.config.Section("captcha").Key("enabled").MustBool(false) {
|
if app.config.Section("captcha").Key("enabled").MustBool(false) {
|
||||||
router.GET(p+"/captcha/gen/:invCode", app.GenCaptcha)
|
router.GET(p+"/captcha/gen/:invCode", app.GenCaptcha)
|
||||||
router.GET(p+"/captcha/img/:invCode/:captchaID", app.GetCaptcha)
|
router.GET(p+"/captcha/img/:invCode/:captchaID", app.GetCaptcha)
|
||||||
router.POST(p+"/captcha/verify/:invCode/:captchaID/:text", app.VerifyCaptcha)
|
router.POST(p+"/captcha/verify/:invCode/:captchaID/:text", app.VerifyCaptcha)
|
||||||
}
|
}
|
||||||
if telegramEnabled {
|
if telegramEnabled {
|
||||||
router.GET(p+"/invite/:invCode/telegram/verified/:pin", app.TelegramVerifiedInvite)
|
router.GET(p+PAGES.Form+"/:invCode/telegram/verified/:pin", app.TelegramVerifiedInvite)
|
||||||
}
|
}
|
||||||
if discordEnabled {
|
if discordEnabled {
|
||||||
router.GET(p+"/invite/:invCode/discord/verified/:pin", app.DiscordVerifiedInvite)
|
router.GET(p+PAGES.Form+"/:invCode/discord/verified/:pin", app.DiscordVerifiedInvite)
|
||||||
if app.config.Section("discord").Key("provide_invite").MustBool(false) {
|
if app.config.Section("discord").Key("provide_invite").MustBool(false) {
|
||||||
router.GET(p+"/invite/:invCode/discord/invite", app.DiscordServerInvite)
|
router.GET(p+PAGES.Form+"/:invCode/discord/invite", app.DiscordServerInvite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if matrixEnabled {
|
if matrixEnabled {
|
||||||
router.GET(p+"/invite/:invCode/matrix/verified/:userID/:pin", app.MatrixCheckPIN)
|
router.GET(p+PAGES.Form+"/:invCode/matrix/verified/:userID/:pin", app.MatrixCheckPIN)
|
||||||
router.POST(p+"/invite/:invCode/matrix/user", app.MatrixSendPIN)
|
router.POST(p+PAGES.Form+"/:invCode/matrix/user", app.MatrixSendPIN)
|
||||||
router.POST(p+"/users/matrix", app.MatrixConnect)
|
router.POST(p+"/users/matrix", app.MatrixConnect)
|
||||||
}
|
}
|
||||||
if userPageEnabled {
|
if userPageEnabled {
|
||||||
router.GET(p+"/my/account", app.MyUserPage)
|
router.GET(p+PAGES.MyAccount, app.MyUserPage)
|
||||||
router.GET(p+"/my/account/password/reset", app.MyUserPage)
|
router.GET(p+PAGES.MyAccount+"/password/reset", app.MyUserPage)
|
||||||
router.GET(p+"/my/token/login", app.getUserTokenLogin)
|
router.GET(p+"/my/token/login", app.getUserTokenLogin)
|
||||||
router.GET(p+"/my/token/refresh", app.getUserTokenRefresh)
|
router.GET(p+"/my/token/refresh", app.getUserTokenRefresh)
|
||||||
router.GET(p+"/my/confirm/:jwt", app.ConfirmMyAction)
|
router.GET(p+"/my/confirm/:jwt", app.ConfirmMyAction)
|
||||||
|
|||||||
@@ -39,8 +39,10 @@ func (app *appContext) ServeSetup(gc *gin.Context) {
|
|||||||
respond(500, "Failed to fetch default values", gc)
|
respond(500, "Failed to fetch default values", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
pages := PagePathsDTO{PagePaths: PAGES}
|
||||||
gc.HTML(200, "setup.html", gin.H{
|
gc.HTML(200, "setup.html", gin.H{
|
||||||
"cssVersion": cssVersion,
|
"cssVersion": cssVersion,
|
||||||
|
"pages": pages,
|
||||||
"lang": app.storage.lang.Setup[lang],
|
"lang": app.storage.lang.Setup[lang],
|
||||||
"strings": app.storage.lang.Setup[lang].Strings,
|
"strings": app.storage.lang.Setup[lang].Strings,
|
||||||
"emailLang": app.storage.lang.Email[emailLang],
|
"emailLang": app.storage.lang.Email[emailLang],
|
||||||
|
|||||||
+5
-4
@@ -11,6 +11,8 @@ import { _get, _post, notificationBox, whichAnimationEvent, bindManualDropdowns
|
|||||||
import { Updater } from "./modules/update.js";
|
import { Updater } from "./modules/update.js";
|
||||||
import { Login } from "./modules/login.js";
|
import { Login } from "./modules/login.js";
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
const theme = new ThemeManager(document.getElementById("button-theme"));
|
const theme = new ThemeManager(document.getElementById("button-theme"));
|
||||||
|
|
||||||
window.lang = new lang(window.langFile as LangFile);
|
window.lang = new lang(window.langFile as LangFile);
|
||||||
@@ -165,18 +167,17 @@ const defaultTab = tabs[0];
|
|||||||
window.tabs = new Tabs();
|
window.tabs = new Tabs();
|
||||||
|
|
||||||
for (let tab of tabs) {
|
for (let tab of tabs) {
|
||||||
window.tabs.addTab(tab.id, tab.url, null, tab.reloader);
|
window.tabs.addTab(tab.id, window.pages.Admin + "/" + tab.url, null, tab.reloader);
|
||||||
}
|
}
|
||||||
|
|
||||||
let matchedTab = false
|
let matchedTab = false
|
||||||
for (let tab of tabs) {
|
for (const tab of tabs) {
|
||||||
if (window.location.pathname.startsWith(window.URLBase + "/" + tab.url)) {
|
if (window.location.pathname.startsWith(window.pages.Base + window.pages.Current + "/" + tab.url)) {
|
||||||
window.tabs.switch(tab.url, true);
|
window.tabs.switch(tab.url, true);
|
||||||
matchedTab = true;
|
matchedTab = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Default tab
|
// Default tab
|
||||||
// if ((window.URLBase + "/").includes(window.location.pathname)) {
|
|
||||||
if (!matchedTab) {
|
if (!matchedTab) {
|
||||||
window.tabs.switch("", true);
|
window.tabs.switch("", true);
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -6,7 +6,7 @@ import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.
|
|||||||
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
|
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
|
||||||
import { Captcha, GreCAPTCHA } from "./modules/captcha.js";
|
import { Captcha, GreCAPTCHA } from "./modules/captcha.js";
|
||||||
|
|
||||||
interface formWindow extends Window {
|
interface formWindow extends GlobalWindow {
|
||||||
invalidPassword: string;
|
invalidPassword: string;
|
||||||
successModal: Modal;
|
successModal: Modal;
|
||||||
telegramModal: Modal;
|
telegramModal: Modal;
|
||||||
@@ -59,7 +59,7 @@ if (window.telegramEnabled) {
|
|||||||
modal: window.telegramModal as Modal,
|
modal: window.telegramModal as Modal,
|
||||||
pin: window.telegramPIN,
|
pin: window.telegramPIN,
|
||||||
pinURL: "",
|
pinURL: "",
|
||||||
verifiedURL: "/invite/" + window.code + "/telegram/verified/",
|
verifiedURL: window.pages.Form + "/" + window.code + "/telegram/verified/",
|
||||||
invalidCodeError: window.messages["errorInvalidPIN"],
|
invalidCodeError: window.messages["errorInvalidPIN"],
|
||||||
accountLinkedError: window.messages["errorAccountLinked"],
|
accountLinkedError: window.messages["errorAccountLinked"],
|
||||||
successError: window.messages["verified"],
|
successError: window.messages["verified"],
|
||||||
@@ -89,9 +89,9 @@ if (window.discordEnabled) {
|
|||||||
const discordConf: ServiceConfiguration = {
|
const discordConf: ServiceConfiguration = {
|
||||||
modal: window.discordModal as Modal,
|
modal: window.discordModal as Modal,
|
||||||
pin: window.discordPIN,
|
pin: window.discordPIN,
|
||||||
inviteURL: window.discordInviteLink ? ("/invite/" + window.code + "/discord/invite") : "",
|
inviteURL: window.discordInviteLink ? (window.pages.Form + "/" + window.code + "/discord/invite") : "",
|
||||||
pinURL: "",
|
pinURL: "",
|
||||||
verifiedURL: "/invite/" + window.code + "/discord/verified/",
|
verifiedURL: window.pages.Form + "/" + window.code + "/discord/verified/",
|
||||||
invalidCodeError: window.messages["errorInvalidPIN"],
|
invalidCodeError: window.messages["errorInvalidPIN"],
|
||||||
accountLinkedError: window.messages["errorAccountLinked"],
|
accountLinkedError: window.messages["errorAccountLinked"],
|
||||||
successError: window.messages["verified"],
|
successError: window.messages["verified"],
|
||||||
@@ -121,8 +121,8 @@ if (window.matrixEnabled) {
|
|||||||
|
|
||||||
const matrixConf: MatrixConfiguration = {
|
const matrixConf: MatrixConfiguration = {
|
||||||
modal: window.matrixModal as Modal,
|
modal: window.matrixModal as Modal,
|
||||||
sendMessageURL: "/invite/" + window.code + "/matrix/user",
|
sendMessageURL: window.pages.Form + "/" + window.code + "/matrix/user",
|
||||||
verifiedURL: "/invite/" + window.code + "/matrix/verified/",
|
verifiedURL: window.pages.Form + "/" + window.code + "/matrix/verified/",
|
||||||
invalidCodeError: window.messages["errorInvalidPIN"],
|
invalidCodeError: window.messages["errorInvalidPIN"],
|
||||||
accountLinkedError: window.messages["errorAccountLinked"],
|
accountLinkedError: window.messages["errorAccountLinked"],
|
||||||
unknownError: window.messages["errorUnknown"],
|
unknownError: window.messages["errorUnknown"],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Modal } from "../modules/modal.js";
|
import { Modal } from "../modules/modal.js";
|
||||||
import { _get, _post, toggleLoader, addLoader, removeLoader } from "../modules/common.js";
|
import { _get, _post, toggleLoader, addLoader, removeLoader } from "../modules/common.js";
|
||||||
|
|
||||||
interface formWindow extends Window {
|
interface formWindow extends GlobalWindow {
|
||||||
invalidPassword: string;
|
invalidPassword: string;
|
||||||
successModal: Modal;
|
successModal: Modal;
|
||||||
telegramModal: Modal;
|
telegramModal: Modal;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
|||||||
import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js";
|
import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js";
|
||||||
import { HiddenInputField } from "./ui.js";
|
import { HiddenInputField } from "./ui.js";
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
const dateParser = require("any-date-parser");
|
const dateParser = require("any-date-parser");
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modul
|
|||||||
import { accountURLEvent } from "../modules/accounts.js";
|
import { accountURLEvent } from "../modules/accounts.js";
|
||||||
import { inviteURLEvent } from "../modules/invites.js";
|
import { inviteURLEvent } from "../modules/invites.js";
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
export interface activity {
|
export interface activity {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
@@ -52,8 +54,8 @@ export class Activity implements activity, SearchableItem {
|
|||||||
link = link.split(split)[0];
|
link = link.split(split)[0];
|
||||||
}
|
}
|
||||||
if (link.slice(-1) != "/") { link += "/"; }
|
if (link.slice(-1) != "/") { link += "/"; }
|
||||||
// FIXME: I should probably just be using window.URLBase, but incase thats not right, i'll put this warning here
|
// FIXME: I should probably just be using window.pages.Base, but incase thats not right, i'll put this warning here
|
||||||
if (link != window.URLBase) console.error(`URL Bases don't match: "${link}" != "${window.URLBase}"`);
|
if (link != window.pages.Base) console.error(`URL Bases don't match: "${link}" != "${window.pages.Base}"`);
|
||||||
return link;
|
return link;
|
||||||
})(); */
|
})(); */
|
||||||
|
|
||||||
@@ -66,17 +68,17 @@ export class Activity implements activity, SearchableItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_genUserLink = (): string => {
|
_genUserLink = (): string => {
|
||||||
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${window.URLBase}/accounts?user=${this._act.user_id}">${this._genUserText()}</a>`;
|
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${window.pages.Base}${window.pages.Admin}/accounts?user=${this._act.user_id}">${this._genUserText()}</a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_genSrcUserLink = (): string => {
|
_genSrcUserLink = (): string => {
|
||||||
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${window.URLBase}/accounts?user=${this._act.source}">${this._genSrcUserText()}</a>`;
|
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${window.pages.Base}${window.pages.Admin}/accounts?user=${this._act.source}">${this._genSrcUserText()}</a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderInvText = (): string => { return `<span class="font-medium font-mono">${this.value || this.invite_code || "???"}</span>`; }
|
private _renderInvText = (): string => { return `<span class="font-medium font-mono">${this.value || this.invite_code || "???"}</span>`; }
|
||||||
|
|
||||||
private _genInvLink = (): string => {
|
private _genInvLink = (): string => {
|
||||||
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-invite" data-id="${this.invite_code}" href="${window.URLBase}/?invite=${this.invite_code}">${this._renderInvText()}</a>`;
|
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-invite" data-id="${this.invite_code}" href="${window.pages.Base}${window.pages.Admin}/?invite=${this.invite_code}">${this._renderInvText()}</a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
declare var window: Window;
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
export function toDateString(date: Date): string {
|
export function toDateString(date: Date): string {
|
||||||
const locale = window.language || (window as any).navigator.userLanguage || window.navigator.language;
|
const locale = window.language || (window as any).navigator.userLanguage || window.navigator.language;
|
||||||
@@ -23,7 +23,7 @@ export function toDateString(date: Date): string {
|
|||||||
|
|
||||||
export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
|
export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
if (window.URLBase) { url = window.URLBase + url; }
|
if (window.pages) { url = window.pages.Base + url; }
|
||||||
req.open("GET", url, true);
|
req.open("GET", url, true);
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||||
@@ -42,7 +42,7 @@ export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHtt
|
|||||||
|
|
||||||
export const _download = (url: string, fname: string): void => {
|
export const _download = (url: string, fname: string): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
if (window.URLBase) { url = window.URLBase + url; }
|
if (window.pages) { url = window.pages.Base + url; }
|
||||||
req.open("GET", url, true);
|
req.open("GET", url, true);
|
||||||
req.responseType = 'blob';
|
req.responseType = 'blob';
|
||||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||||
@@ -58,7 +58,7 @@ export const _download = (url: string, fname: string): void => {
|
|||||||
|
|
||||||
export const _upload = (url: string, formData: FormData): void => {
|
export const _upload = (url: string, formData: FormData): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
if (window.URLBase) { url = window.URLBase + url; }
|
if (window.pages) { url = window.pages.Base + url; }
|
||||||
req.open("POST", url, true);
|
req.open("POST", url, true);
|
||||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||||
// req.setRequestHeader('Content-Type', 'multipart/form-data');
|
// req.setRequestHeader('Content-Type', 'multipart/form-data');
|
||||||
@@ -67,7 +67,8 @@ export const _upload = (url: string, formData: FormData): void => {
|
|||||||
|
|
||||||
export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
|
export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("POST", window.URLBase + url, true);
|
if (window.pages) { url = window.pages.Base + url; }
|
||||||
|
req.open("POST", url, true);
|
||||||
if (response) {
|
if (response) {
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
}
|
}
|
||||||
@@ -88,7 +89,8 @@ export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHt
|
|||||||
|
|
||||||
export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void {
|
export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("DELETE", window.URLBase + url, true);
|
if (window.pages) { url = window.pages.Base + url; }
|
||||||
|
req.open("DELETE", url, true);
|
||||||
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');
|
||||||
req.onreadystatechange = () => {
|
req.onreadystatechange = () => {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import {addLoader, removeLoader, _get} from "../modules/common.js";
|
import {addLoader, removeLoader, _get} from "../modules/common.js";
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
export interface DiscordUser {
|
export interface DiscordUser {
|
||||||
name: string;
|
name: string;
|
||||||
avatar_url: string;
|
avatar_url: string;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { _get, _post, _delete, toClipboard, toggleLoader, toDateString } from ".
|
|||||||
import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
||||||
import { reloadProfileNames } from "../modules/profiles.js";
|
import { reloadProfileNames } from "../modules/profiles.js";
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
class DOMInvite implements Invite {
|
class DOMInvite implements Invite {
|
||||||
updateNotify = (checkbox: HTMLInputElement) => {
|
updateNotify = (checkbox: HTMLInputElement) => {
|
||||||
let state: { [code: string]: { [type: string]: boolean } } = {};
|
let state: { [code: string]: { [type: string]: boolean } } = {};
|
||||||
@@ -66,7 +68,7 @@ class DOMInvite implements Invite {
|
|||||||
codeLink = codeLink.split(split)[0];
|
codeLink = codeLink.split(split)[0];
|
||||||
}
|
}
|
||||||
if (codeLink.slice(-1) != "/") { codeLink += "/"; }
|
if (codeLink.slice(-1) != "/") { codeLink += "/"; }
|
||||||
this._codeLink = codeLink + "invite/" + code;
|
this._codeLink = codeLink + window.pages.Form + "/" + code;
|
||||||
const linkEl = this._codeArea.querySelector("a") as HTMLAnchorElement;
|
const linkEl = this._codeArea.querySelector("a") as HTMLAnchorElement;
|
||||||
if (this.label == "") {
|
if (this.label == "") {
|
||||||
linkEl.textContent = code.replace(/-/g, '-');
|
linkEl.textContent = code.replace(/-/g, '-');
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,8 @@
|
|||||||
import { Modal } from "../modules/modal.js";
|
import { Modal } from "../modules/modal.js";
|
||||||
import { toggleLoader, _post, unicodeB64Encode } from "../modules/common.js";
|
import { toggleLoader, _post, unicodeB64Encode } from "../modules/common.js";
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
export class Login {
|
export class Login {
|
||||||
loggedIn: boolean = false;
|
loggedIn: boolean = false;
|
||||||
private _modal: Modal;
|
private _modal: Modal;
|
||||||
@@ -14,7 +16,7 @@ export class Login {
|
|||||||
|
|
||||||
constructor(modal: Modal, endpoint: string, appearance: string) {
|
constructor(modal: Modal, endpoint: string, appearance: string) {
|
||||||
this._endpoint = endpoint;
|
this._endpoint = endpoint;
|
||||||
this._url = window.URLBase + endpoint;
|
this._url = window.pages.Base + endpoint;
|
||||||
if (this._url[this._url.length-1] != '/') this._url += "/";
|
if (this._url[this._url.length-1] != '/') this._url += "/";
|
||||||
|
|
||||||
this._modal = modal;
|
this._modal = modal;
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
declare var window: Window;
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
export class Modal implements Modal {
|
export class Modal implements Modal {
|
||||||
modal: HTMLElement;
|
modal: HTMLElement;
|
||||||
|
|||||||
+9
-1
@@ -32,7 +32,15 @@ export class PageManager {
|
|||||||
|
|
||||||
private _onpopstate = (event: PopStateEvent) => {
|
private _onpopstate = (event: PopStateEvent) => {
|
||||||
let name = event.state;
|
let name = event.state;
|
||||||
if (!this.pages.has(event.state)) {
|
if (name == null) {
|
||||||
|
// Attempt to use hash from URL, if it isn't there, try the last part of the URL.
|
||||||
|
if (window.location.hash && window.location.hash.charAt(0) == "#") {
|
||||||
|
name = window.location.hash.substring(1);
|
||||||
|
} else {
|
||||||
|
name = window.location.pathname.split("/").filter(Boolean).at(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.pages.has(name)) {
|
||||||
name = this.pageList[0]
|
name = this.pageList[0]
|
||||||
}
|
}
|
||||||
let success = this.pages.get(name).show();
|
let success = this.pages.get(name).show();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { _get, _post, _delete, toggleLoader } from "../modules/common.js";
|
import { _get, _post, _delete, toggleLoader } from "../modules/common.js";
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
export const profileLoadEvent = new CustomEvent("profileLoadEvent");
|
export const profileLoadEvent = new CustomEvent("profileLoadEvent");
|
||||||
export const reloadProfileNames = (then?: () => void) => _get("/profiles/names", null, (req: XMLHttpRequest) => {
|
export const reloadProfileNames = (then?: () => void) => _get("/profiles/names", null, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState != 4) return;
|
if (req.readyState != 4) return;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
const dateParser = require("any-date-parser");
|
const dateParser = require("any-date-parser");
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
export interface QueryType {
|
export interface QueryType {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { _get, _post, _delete, _download, _upload, toggleLoader, addLoader, remo
|
|||||||
import { Marked } from "@ts-stack/markdown";
|
import { Marked } from "@ts-stack/markdown";
|
||||||
import { stripMarkdown } from "../modules/stripmd.js";
|
import { stripMarkdown } from "../modules/stripmd.js";
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
const toBool = (s: string): boolean => {
|
const toBool = (s: string): boolean => {
|
||||||
let b = Boolean(s);
|
let b = Boolean(s);
|
||||||
if (s == "false") b = false;
|
if (s == "false") b = false;
|
||||||
|
|||||||
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
import { PageManager, Page } from "../modules/pages.js";
|
import { PageManager, Page } from "../modules/pages.js";
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
export interface Tab {
|
export interface Tab {
|
||||||
page: Page;
|
page: Page;
|
||||||
tabEl: HTMLDivElement;
|
tabEl: HTMLDivElement;
|
||||||
@@ -38,7 +40,7 @@ export class Tabs implements Tabs {
|
|||||||
tab.page = {
|
tab.page = {
|
||||||
name: tabID,
|
name: tabID,
|
||||||
title: document.title, /*FIXME: Get actual names from translations*/
|
title: document.title, /*FIXME: Get actual names from translations*/
|
||||||
url: window.URLBase + "/" + url,
|
url: url,
|
||||||
show: () => {
|
show: () => {
|
||||||
tab.buttonEl.classList.add("active", "~urge");
|
tab.buttonEl.classList.add("active", "~urge");
|
||||||
tab.tabEl.classList.remove("unfocused");
|
tab.tabEl.classList.remove("unfocused");
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { _get, _post, toggleLoader, toDateString } from "../modules/common.js";
|
import { _get, _post, toggleLoader, toDateString } from "../modules/common.js";
|
||||||
import { Marked, Renderer } from "@ts-stack/markdown";
|
import { Marked, Renderer } from "@ts-stack/markdown";
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
interface updateDTO {
|
interface updateDTO {
|
||||||
new: boolean;
|
new: boolean;
|
||||||
update: Update;
|
update: Update;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { toClipboard, notificationBox } from "./modules/common.js";
|
import { toClipboard, notificationBox } from "./modules/common.js";
|
||||||
|
|
||||||
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
const pin = document.getElementById("pin") as HTMLSpanElement;
|
const pin = document.getElementById("pin") as HTMLSpanElement;
|
||||||
|
|
||||||
if (pin) {
|
if (pin) {
|
||||||
|
|||||||
+1
-2
@@ -3,12 +3,11 @@ import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
|||||||
import { ThemeManager } from "./modules/theme.js";
|
import { ThemeManager } from "./modules/theme.js";
|
||||||
import { PageManager } from "./modules/pages.js";
|
import { PageManager } from "./modules/pages.js";
|
||||||
|
|
||||||
interface sWindow extends Window {
|
interface sWindow extends GlobalWindow {
|
||||||
messages: {};
|
messages: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
declare var window: sWindow;
|
declare var window: sWindow;
|
||||||
window.URLBase = "";
|
|
||||||
|
|
||||||
const theme = new ThemeManager(document.getElementById("button-theme"));
|
const theme = new ThemeManager(document.getElementById("button-theme"));
|
||||||
|
|
||||||
|
|||||||
+14
-2
@@ -12,8 +12,19 @@ interface ArrayConstructor {
|
|||||||
from(arrayLike: any, mapFn?, thisArg?): Array<any>;
|
from(arrayLike: any, mapFn?, thisArg?): Array<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface Window {
|
declare interface PagePaths {
|
||||||
URLBase: string;
|
// The base subfolder the app is hosted on.
|
||||||
|
Base: string;
|
||||||
|
// The subdirectory this bit of the app is hosted on (e.g. admin is usually on "/", myacc is usually on "/my/account")
|
||||||
|
Current: string;
|
||||||
|
// Those for other pages
|
||||||
|
Admin: string;
|
||||||
|
MyAccount: string;
|
||||||
|
Form: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface GlobalWindow extends Window {
|
||||||
|
pages: PagePaths;
|
||||||
modals: Modals;
|
modals: Modals;
|
||||||
cssFile: string;
|
cssFile: string;
|
||||||
availableProfiles: string[];
|
availableProfiles: string[];
|
||||||
@@ -25,6 +36,7 @@ declare interface Window {
|
|||||||
matrixEnabled: boolean;
|
matrixEnabled: boolean;
|
||||||
ombiEnabled: boolean;
|
ombiEnabled: boolean;
|
||||||
jellyseerrEnabled: boolean;
|
jellyseerrEnabled: boolean;
|
||||||
|
pwrEnabled: boolean;
|
||||||
usernameEnabled: boolean;
|
usernameEnabled: boolean;
|
||||||
linkResetEnabled: boolean;
|
linkResetEnabled: boolean;
|
||||||
token: string;
|
token: string;
|
||||||
|
|||||||
+6
-7
@@ -7,7 +7,7 @@ import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration }
|
|||||||
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
|
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
|
||||||
import { PageManager } from "./modules/pages.js";
|
import { PageManager } from "./modules/pages.js";
|
||||||
|
|
||||||
interface userWindow extends Window {
|
interface userWindow extends GlobalWindow {
|
||||||
jellyfinID: string;
|
jellyfinID: string;
|
||||||
username: string;
|
username: string;
|
||||||
emailRequired: boolean;
|
emailRequired: boolean;
|
||||||
@@ -18,14 +18,13 @@ interface userWindow extends Window {
|
|||||||
discordInviteLink: boolean;
|
discordInviteLink: boolean;
|
||||||
matrixUserID: string;
|
matrixUserID: string;
|
||||||
discordSendPINMessage: string;
|
discordSendPINMessage: string;
|
||||||
pwrEnabled: string;
|
|
||||||
referralsEnabled: boolean;
|
referralsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const basePath = window.location.pathname.replace("/password/reset", "");
|
|
||||||
|
|
||||||
declare var window: userWindow;
|
declare var window: userWindow;
|
||||||
|
|
||||||
|
const basePath = window.location.pathname.replace("/password/reset", "");
|
||||||
|
|
||||||
const theme = new ThemeManager(document.getElementById("button-theme"));
|
const theme = new ThemeManager(document.getElementById("button-theme"));
|
||||||
|
|
||||||
window.lang = new lang(window.langFile as LangFile);
|
window.lang = new lang(window.langFile as LangFile);
|
||||||
@@ -38,7 +37,7 @@ window.token = "";
|
|||||||
|
|
||||||
window.modals = {} as Modals;
|
window.modals = {} as Modals;
|
||||||
|
|
||||||
let pages = new PageManager({
|
const pages = new PageManager({
|
||||||
hideOthersOnPageShow: true,
|
hideOthersOnPageShow: true,
|
||||||
defaultName: "",
|
defaultName: "",
|
||||||
defaultTitle: document.title,
|
defaultTitle: document.title,
|
||||||
@@ -311,7 +310,7 @@ class ReferralCard {
|
|||||||
path = path.split(split)[0];
|
path = path.split(split)[0];
|
||||||
}
|
}
|
||||||
if (path.slice(-1) != "/") { path += "/"; }
|
if (path.slice(-1) != "/") { path += "/"; }
|
||||||
path = path + "invite/" + this._code;
|
path = path + window.pages.Form + "/" + this._code;
|
||||||
|
|
||||||
u.pathname = path;
|
u.pathname = path;
|
||||||
u.hash = "";
|
u.hash = "";
|
||||||
@@ -661,7 +660,7 @@ document.addEventListener("details-reload", () => {
|
|||||||
expiryCard.expiry = details.expiry;
|
expiryCard.expiry = details.expiry;
|
||||||
|
|
||||||
const adminBackButton = document.getElementById("admin-back-button") as HTMLAnchorElement;
|
const adminBackButton = document.getElementById("admin-back-button") as HTMLAnchorElement;
|
||||||
adminBackButton.href = window.location.href.replace("my/account", "");
|
adminBackButton.href = window.pages.Base + window.pages.Admin;
|
||||||
|
|
||||||
let messageCard = document.getElementById("card-message");
|
let messageCard = document.getElementById("card-message");
|
||||||
if (details.accounts_admin) {
|
if (details.accounts_admin) {
|
||||||
|
|||||||
+2
-1
@@ -67,7 +67,8 @@ func (app *appContext) getUserTokenLogin(gc *gin.Context) {
|
|||||||
// host := gc.Request.URL.Hostname()
|
// host := gc.Request.URL.Hostname()
|
||||||
host := app.ExternalDomain
|
host := app.ExternalDomain
|
||||||
uri := "/my"
|
uri := "/my"
|
||||||
if strings.HasPrefix(gc.Request.RequestURI, app.URLBase) {
|
// FIXME: This seems like a bad idea? I think it's to deal with people having Reverse proxy subfolder/URL base set to /accounts.
|
||||||
|
if strings.HasPrefix(gc.Request.RequestURI, PAGES.Base) {
|
||||||
uri = "/accounts/my"
|
uri = "/accounts/my"
|
||||||
}
|
}
|
||||||
gc.SetCookie("user-refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, uri, host, true, true)
|
gc.SetCookie("user-refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, uri, host, true, true)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (app *appContext) loadCSSHeader() string {
|
|||||||
l := len(css)
|
l := len(css)
|
||||||
h := ""
|
h := ""
|
||||||
for i, f := range css {
|
for i, f := range css {
|
||||||
h += "<" + app.URLBase + "/css/" + f + ">; rel=preload; as=style"
|
h += "<" + PAGES.Base + "/css/" + f + ">; rel=preload; as=style"
|
||||||
if l > 1 && i != (l-1) {
|
if l > 1 && i != (l-1) {
|
||||||
h += ", "
|
h += ", "
|
||||||
}
|
}
|
||||||
@@ -41,18 +41,19 @@ func (app *appContext) loadCSSHeader() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) getURLBase(gc *gin.Context) string {
|
func (app *appContext) getURLBase(gc *gin.Context) string {
|
||||||
if strings.HasPrefix(gc.Request.URL.String(), app.URLBase) {
|
if strings.HasPrefix(gc.Request.URL.String(), PAGES.Base) {
|
||||||
// Hack to fix the common URL base /accounts
|
// Hack to fix the common URL base /accounts
|
||||||
if app.URLBase == "/accounts" && strings.HasPrefix(gc.Request.URL.String(), "/accounts/user/") {
|
if PAGES.Base == "/accounts" && strings.HasPrefix(gc.Request.URL.String(), "/accounts/user/") {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return app.URLBase
|
return PAGES.Base
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func gcHTML(gc *gin.Context, code int, file string, templ gin.H) {
|
func (app *appContext) gcHTML(gc *gin.Context, code int, file string, page Page, templ gin.H) {
|
||||||
gc.Header("Cache-Control", "no-cache")
|
gc.Header("Cache-Control", "no-cache")
|
||||||
|
app.BasePageTemplateValues(gc, page, templ)
|
||||||
gc.HTML(code, file, templ)
|
gc.HTML(code, file, templ)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,16 +62,14 @@ func (app *appContext) pushResources(gc *gin.Context, page Page) {
|
|||||||
switch page {
|
switch page {
|
||||||
case AdminPage:
|
case AdminPage:
|
||||||
toPush = []string{"/js/admin.js", "/js/theme.js", "/js/lang.js", "/js/modal.js", "/js/tabs.js", "/js/invites.js", "/js/accounts.js", "/js/settings.js", "/js/profiles.js", "/js/common.js"}
|
toPush = []string{"/js/admin.js", "/js/theme.js", "/js/lang.js", "/js/modal.js", "/js/tabs.js", "/js/invites.js", "/js/accounts.js", "/js/settings.js", "/js/profiles.js", "/js/common.js"}
|
||||||
break
|
|
||||||
case UserPage:
|
case UserPage:
|
||||||
toPush = []string{"/js/user.js", "/js/theme.js", "/js/lang.js", "/js/modal.js", "/js/common.js"}
|
toPush = []string{"/js/user.js", "/js/theme.js", "/js/lang.js", "/js/modal.js", "/js/common.js"}
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
toPush = []string{}
|
toPush = []string{}
|
||||||
}
|
}
|
||||||
if pusher := gc.Writer.Pusher(); pusher != nil {
|
if pusher := gc.Writer.Pusher(); pusher != nil {
|
||||||
for _, f := range toPush {
|
for _, f := range toPush {
|
||||||
if err := pusher.Push(app.URLBase+f, nil); err != nil {
|
if err := pusher.Push(PAGES.Base+f, nil); err != nil {
|
||||||
app.debug.Printf(lm.FailedServerPush, err)
|
app.debug.Printf(lm.FailedServerPush, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,6 +77,48 @@ func (app *appContext) pushResources(gc *gin.Context, page Page) {
|
|||||||
gc.Header("Link", cssHeader)
|
gc.Header("Link", cssHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a gin.H with general values (url base, css version, etc.)
|
||||||
|
func (app *appContext) BasePageTemplateValues(gc *gin.Context, page Page, base gin.H) {
|
||||||
|
set := func(k string, v any) {
|
||||||
|
if _, ok := base[k]; !ok {
|
||||||
|
base[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pages := PagePathsDTO{
|
||||||
|
PagePaths: PAGES,
|
||||||
|
}
|
||||||
|
pages.Base = app.getURLBase(gc)
|
||||||
|
switch page {
|
||||||
|
case AdminPage:
|
||||||
|
pages.Current = PAGES.Admin
|
||||||
|
case FormPage:
|
||||||
|
pages.Current = PAGES.Form
|
||||||
|
case UserPage:
|
||||||
|
pages.Current = PAGES.MyAccount
|
||||||
|
default:
|
||||||
|
pages.Current = "/"
|
||||||
|
}
|
||||||
|
set("pages", pages)
|
||||||
|
ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false)
|
||||||
|
jellyseerrEnabled := app.config.Section("jellyseerr").Key("enabled").MustBool(false)
|
||||||
|
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
|
||||||
|
set("notifications", notificationsEnabled)
|
||||||
|
set("cssClass", app.cssClass)
|
||||||
|
set("cssVersion", cssVersion)
|
||||||
|
set("emailEnabled", emailEnabled)
|
||||||
|
set("telegramEnabled", telegramEnabled)
|
||||||
|
set("discordEnabled", discordEnabled)
|
||||||
|
set("matrixEnabled", matrixEnabled)
|
||||||
|
set("ombiEnabled", ombiEnabled)
|
||||||
|
set("jellyseerrEnabled", jellyseerrEnabled)
|
||||||
|
// QUIRK: The login modal html template uses this' existence to check if the modal is for the admin or user page.
|
||||||
|
if page != AdminPage {
|
||||||
|
set("pwrEnabled", app.config.Section("password_resets").Key("enabled").MustBool(false))
|
||||||
|
}
|
||||||
|
set("referralsEnabled", app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false))
|
||||||
|
}
|
||||||
|
|
||||||
type Page int
|
type Page int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -132,10 +173,6 @@ func (app *appContext) getLang(gc *gin.Context, page Page, chosen string) string
|
|||||||
func (app *appContext) AdminPage(gc *gin.Context) {
|
func (app *appContext) AdminPage(gc *gin.Context) {
|
||||||
app.pushResources(gc, AdminPage)
|
app.pushResources(gc, AdminPage)
|
||||||
lang := app.getLang(gc, AdminPage, app.storage.lang.chosenAdminLang)
|
lang := app.getLang(gc, AdminPage, app.storage.lang.chosenAdminLang)
|
||||||
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
|
||||||
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
|
|
||||||
ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false)
|
|
||||||
jellyseerrEnabled := app.config.Section("jellyseerr").Key("enabled").MustBool(false)
|
|
||||||
jfAdminOnly := app.config.Section("ui").Key("admin_only").MustBool(true)
|
jfAdminOnly := app.config.Section("ui").Key("admin_only").MustBool(true)
|
||||||
jfAllowAll := app.config.Section("ui").Key("allow_all").MustBool(false)
|
jfAllowAll := app.config.Section("ui").Key("allow_all").MustBool(false)
|
||||||
var license string
|
var license string
|
||||||
@@ -157,62 +194,36 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
|||||||
builtBy = "???"
|
builtBy = "???"
|
||||||
}
|
}
|
||||||
|
|
||||||
gcHTML(gc, http.StatusOK, "admin.html", gin.H{
|
app.gcHTML(gc, http.StatusOK, "admin.html", AdminPage, gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
"contactMessage": "",
|
||||||
"cssClass": app.cssClass,
|
"linkResetEnabled": app.config.Section("password_resets").Key("link_reset").MustBool(false),
|
||||||
"cssVersion": cssVersion,
|
"version": version,
|
||||||
"contactMessage": "",
|
"commit": commit,
|
||||||
"emailEnabled": emailEnabled,
|
"buildTime": buildTime,
|
||||||
"telegramEnabled": telegramEnabled,
|
"builtBy": builtBy,
|
||||||
"discordEnabled": discordEnabled,
|
"buildTags": buildTags,
|
||||||
"matrixEnabled": matrixEnabled,
|
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||||
"ombiEnabled": ombiEnabled,
|
"strings": app.storage.lang.Admin[lang].Strings,
|
||||||
"jellyseerrEnabled": jellyseerrEnabled,
|
"quantityStrings": app.storage.lang.Admin[lang].QuantityStrings,
|
||||||
"linkResetEnabled": app.config.Section("password_resets").Key("link_reset").MustBool(false),
|
"language": app.storage.lang.Admin[lang].JSON,
|
||||||
"notifications": notificationsEnabled,
|
"langName": lang,
|
||||||
"version": version,
|
"license": license,
|
||||||
"commit": commit,
|
"jellyfinLogin": app.jellyfinLogin,
|
||||||
"buildTime": buildTime,
|
"jfAdminOnly": jfAdminOnly,
|
||||||
"builtBy": builtBy,
|
"jfAllowAll": jfAllowAll,
|
||||||
"buildTags": buildTags,
|
"userPageEnabled": app.config.Section("user_page").Key("enabled").MustBool(false),
|
||||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
"showUserPageLink": app.config.Section("user_page").Key("show_link").MustBool(true),
|
||||||
"strings": app.storage.lang.Admin[lang].Strings,
|
"loginAppearance": app.config.Section("ui").Key("login_appearance").MustString("clear"),
|
||||||
"quantityStrings": app.storage.lang.Admin[lang].QuantityStrings,
|
|
||||||
"language": app.storage.lang.Admin[lang].JSON,
|
|
||||||
"langName": lang,
|
|
||||||
"license": license,
|
|
||||||
"jellyfinLogin": app.jellyfinLogin,
|
|
||||||
"jfAdminOnly": jfAdminOnly,
|
|
||||||
"jfAllowAll": jfAllowAll,
|
|
||||||
"userPageEnabled": app.config.Section("user_page").Key("enabled").MustBool(false),
|
|
||||||
"showUserPageLink": app.config.Section("user_page").Key("show_link").MustBool(true),
|
|
||||||
"referralsEnabled": app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false),
|
|
||||||
"loginAppearance": app.config.Section("ui").Key("login_appearance").MustString("clear"),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) MyUserPage(gc *gin.Context) {
|
func (app *appContext) MyUserPage(gc *gin.Context) {
|
||||||
app.pushResources(gc, UserPage)
|
app.pushResources(gc, UserPage)
|
||||||
lang := app.getLang(gc, UserPage, app.storage.lang.chosenUserLang)
|
lang := app.getLang(gc, UserPage, app.storage.lang.chosenUserLang)
|
||||||
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
|
||||||
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
|
|
||||||
ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false)
|
|
||||||
jellyseerrEnabled := app.config.Section("jellyseerr").Key("enabled").MustBool(false)
|
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
|
||||||
"cssClass": app.cssClass,
|
|
||||||
"cssVersion": cssVersion,
|
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
"emailEnabled": emailEnabled,
|
|
||||||
"emailRequired": app.config.Section("email").Key("required").MustBool(false),
|
"emailRequired": app.config.Section("email").Key("required").MustBool(false),
|
||||||
"telegramEnabled": telegramEnabled,
|
|
||||||
"discordEnabled": discordEnabled,
|
|
||||||
"matrixEnabled": matrixEnabled,
|
|
||||||
"ombiEnabled": ombiEnabled,
|
|
||||||
"jellyseerrEnabled": jellyseerrEnabled,
|
|
||||||
"pwrEnabled": app.config.Section("password_resets").Key("enabled").MustBool(false),
|
|
||||||
"linkResetEnabled": app.config.Section("password_resets").Key("link_reset").MustBool(false),
|
"linkResetEnabled": app.config.Section("password_resets").Key("link_reset").MustBool(false),
|
||||||
"notifications": notificationsEnabled,
|
|
||||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||||
"strings": app.storage.lang.User[lang].Strings,
|
"strings": app.storage.lang.User[lang].Strings,
|
||||||
"validationStrings": app.storage.lang.User[lang].validationStringsJSON,
|
"validationStrings": app.storage.lang.User[lang].validationStringsJSON,
|
||||||
@@ -220,7 +231,6 @@ func (app *appContext) MyUserPage(gc *gin.Context) {
|
|||||||
"langName": lang,
|
"langName": lang,
|
||||||
"jfLink": app.config.Section("ui").Key("redirect_url").String(),
|
"jfLink": app.config.Section("ui").Key("redirect_url").String(),
|
||||||
"requirements": app.validator.getCriteria(),
|
"requirements": app.validator.getCriteria(),
|
||||||
"referralsEnabled": app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false),
|
|
||||||
}
|
}
|
||||||
if telegramEnabled {
|
if telegramEnabled {
|
||||||
data["telegramUsername"] = app.telegram.username
|
data["telegramUsername"] = app.telegram.username
|
||||||
@@ -264,7 +274,7 @@ func (app *appContext) MyUserPage(gc *gin.Context) {
|
|||||||
data[name+"MessageContent"] = template.HTML(markdown.ToHTML([]byte(msg.Content), nil, markdownRenderer))
|
data[name+"MessageContent"] = template.HTML(markdown.ToHTML([]byte(msg.Content), nil, markdownRenderer))
|
||||||
}
|
}
|
||||||
|
|
||||||
gcHTML(gc, http.StatusOK, "user.html", data)
|
app.gcHTML(gc, http.StatusOK, "user.html", UserPage, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) ResetPassword(gc *gin.Context) {
|
func (app *appContext) ResetPassword(gc *gin.Context) {
|
||||||
@@ -278,14 +288,9 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
|
|||||||
app.pushResources(gc, PWRPage)
|
app.pushResources(gc, PWRPage)
|
||||||
lang := app.getLang(gc, PWRPage, app.storage.lang.chosenPWRLang)
|
lang := app.getLang(gc, PWRPage, app.storage.lang.chosenPWRLang)
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
|
||||||
"cssClass": app.cssClass,
|
|
||||||
"cssVersion": cssVersion,
|
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
"strings": app.storage.lang.PasswordReset[lang].Strings,
|
"strings": app.storage.lang.PasswordReset[lang].Strings,
|
||||||
"success": false,
|
"success": false,
|
||||||
"ombiEnabled": app.config.Section("ombi").Key("enabled").MustBool(false),
|
|
||||||
"jellyseerrEnabled": app.config.Section("jellyseerr").Key("enabled").MustBool(false),
|
|
||||||
"customSuccessCard": false,
|
"customSuccessCard": false,
|
||||||
}
|
}
|
||||||
pwr, isInternal := app.internalPWRs[pin]
|
pwr, isInternal := app.internalPWRs[pin]
|
||||||
@@ -299,6 +304,7 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
|
|||||||
data["requirements"] = app.validator.getCriteria()
|
data["requirements"] = app.validator.getCriteria()
|
||||||
data["strings"] = app.storage.lang.PasswordReset[lang].Strings
|
data["strings"] = app.storage.lang.PasswordReset[lang].Strings
|
||||||
data["validationStrings"] = app.storage.lang.User[lang].validationStringsJSON
|
data["validationStrings"] = app.storage.lang.User[lang].validationStringsJSON
|
||||||
|
// ewwwww, reusing an existing field, FIXME!
|
||||||
data["notifications"] = app.storage.lang.User[lang].notificationsJSON
|
data["notifications"] = app.storage.lang.User[lang].notificationsJSON
|
||||||
data["langName"] = lang
|
data["langName"] = lang
|
||||||
data["passwordReset"] = true
|
data["passwordReset"] = true
|
||||||
@@ -309,10 +315,10 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
|
|||||||
data["reCAPTCHA"] = app.config.Section("captcha").Key("recaptcha").MustBool(false)
|
data["reCAPTCHA"] = app.config.Section("captcha").Key("recaptcha").MustBool(false)
|
||||||
data["reCAPTCHASiteKey"] = app.config.Section("captcha").Key("recaptcha_site_key").MustString("")
|
data["reCAPTCHASiteKey"] = app.config.Section("captcha").Key("recaptcha_site_key").MustString("")
|
||||||
data["pwrPIN"] = pin
|
data["pwrPIN"] = pin
|
||||||
gcHTML(gc, http.StatusOK, "form-loader.html", data)
|
app.gcHTML(gc, http.StatusOK, "form-loader.html", PWRPage, data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer gcHTML(gc, http.StatusOK, "password-reset.html", data)
|
defer app.gcHTML(gc, http.StatusOK, "password-reset.html", PWRPage, data)
|
||||||
// If it's a bot, pretend to be a success so the preview is nice.
|
// If it's a bot, pretend to be a success so the preview is nice.
|
||||||
if isBot {
|
if isBot {
|
||||||
app.debug.Println(lm.IgnoreBotPWR)
|
app.debug.Println(lm.IgnoreBotPWR)
|
||||||
@@ -413,10 +419,7 @@ func (app *appContext) GetCaptcha(gc *gin.Context) {
|
|||||||
if !isPWR {
|
if !isPWR {
|
||||||
inv, ok = app.storage.GetInvitesKey(code)
|
inv, ok = app.storage.GetInvitesKey(code)
|
||||||
if !ok {
|
if !ok {
|
||||||
gcHTML(gc, 404, "invalidCode.html", gin.H{
|
app.gcHTML(gc, 404, "invalidCode.html", OtherPage, gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
|
||||||
"cssClass": app.cssClass,
|
|
||||||
"cssVersion": cssVersion,
|
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -453,10 +456,7 @@ func (app *appContext) GenCaptcha(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
gcHTML(gc, 404, "invalidCode.html", gin.H{
|
app.gcHTML(gc, 404, "invalidCode.html", OtherPage, gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
|
||||||
"cssClass": app.cssClass,
|
|
||||||
"cssVersion": cssVersion,
|
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -587,10 +587,7 @@ func (app *appContext) VerifyCaptcha(gc *gin.Context) {
|
|||||||
if !isPWR {
|
if !isPWR {
|
||||||
inv, ok = app.storage.GetInvitesKey(code)
|
inv, ok = app.storage.GetInvitesKey(code)
|
||||||
if !ok {
|
if !ok {
|
||||||
gcHTML(gc, 404, "invalidCode.html", gin.H{
|
app.gcHTML(gc, 404, "invalidCode.html", OtherPage, gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
|
||||||
"cssClass": app.cssClass,
|
|
||||||
"cssVersion": cssVersion,
|
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -617,10 +614,7 @@ func (app *appContext) VerifyCaptcha(gc *gin.Context) {
|
|||||||
|
|
||||||
func (app *appContext) NewUserFromConfirmationKey(invite Invite, key string, lang string, gc *gin.Context) {
|
func (app *appContext) NewUserFromConfirmationKey(invite Invite, key string, lang string, gc *gin.Context) {
|
||||||
fail := func() {
|
fail := func() {
|
||||||
gcHTML(gc, 404, "404.html", gin.H{
|
app.gcHTML(gc, 404, "404.html", OtherPage, gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
|
||||||
"cssClass": app.cssClass,
|
|
||||||
"cssVersion": cssVersion,
|
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -691,10 +685,7 @@ func (app *appContext) NewUserFromConfirmationKey(invite Invite, key string, lan
|
|||||||
if app.config.Section("ui").Key("auto_redirect").MustBool(false) {
|
if app.config.Section("ui").Key("auto_redirect").MustBool(false) {
|
||||||
gc.Redirect(301, jfLink)
|
gc.Redirect(301, jfLink)
|
||||||
} else {
|
} else {
|
||||||
gcHTML(gc, http.StatusOK, "create-success.html", gin.H{
|
app.gcHTML(gc, http.StatusOK, "create-success.html", OtherPage, gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
|
||||||
"cssClass": app.cssClass,
|
|
||||||
"cssVersion": cssVersion,
|
|
||||||
"strings": app.storage.lang.User[lang].Strings,
|
"strings": app.storage.lang.User[lang].Strings,
|
||||||
"successMessage": app.config.Section("ui").Key("success_message").String(),
|
"successMessage": app.config.Section("ui").Key("success_message").String(),
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
@@ -725,10 +716,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
// if app.checkInvite(code, false, "") {
|
// if app.checkInvite(code, false, "") {
|
||||||
invite, ok := app.storage.GetInvitesKey(gc.Param("invCode"))
|
invite, ok := app.storage.GetInvitesKey(gc.Param("invCode"))
|
||||||
if !ok {
|
if !ok {
|
||||||
gcHTML(gc, 404, "invalidCode.html", gin.H{
|
app.gcHTML(gc, 404, "invalidCode.html", FormPage, gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
|
||||||
"cssClass": app.cssClass,
|
|
||||||
"cssVersion": cssVersion,
|
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -747,7 +735,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
discord := discordEnabled && app.config.Section("discord").Key("show_on_reg").MustBool(true)
|
discord := discordEnabled && app.config.Section("discord").Key("show_on_reg").MustBool(true)
|
||||||
matrix := matrixEnabled && app.config.Section("matrix").Key("show_on_reg").MustBool(true)
|
matrix := matrixEnabled && app.config.Section("matrix").Key("show_on_reg").MustBool(true)
|
||||||
|
|
||||||
userPageAddress := fmt.Sprintf("%s/my/account", app.ExternalURI)
|
userPageAddress := app.ExternalURI + PAGES.MyAccount
|
||||||
|
|
||||||
fromUser := ""
|
fromUser := ""
|
||||||
if invite.ReferrerJellyfinID != "" {
|
if invite.ReferrerJellyfinID != "" {
|
||||||
@@ -758,9 +746,6 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
|
||||||
"cssClass": app.cssClass,
|
|
||||||
"cssVersion": cssVersion,
|
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
"helpMessage": app.config.Section("ui").Key("help_message").String(),
|
"helpMessage": app.config.Section("ui").Key("help_message").String(),
|
||||||
"successMessage": app.config.Section("ui").Key("success_message").String(),
|
"successMessage": app.config.Section("ui").Key("success_message").String(),
|
||||||
@@ -772,28 +757,29 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||||
"strings": app.storage.lang.User[lang].Strings,
|
"strings": app.storage.lang.User[lang].Strings,
|
||||||
"validationStrings": app.storage.lang.User[lang].validationStringsJSON,
|
"validationStrings": app.storage.lang.User[lang].validationStringsJSON,
|
||||||
"notifications": app.storage.lang.User[lang].notificationsJSON,
|
// ewwwww, reusing an existing field, FIXME!
|
||||||
"code": invite.Code,
|
"notifications": app.storage.lang.User[lang].notificationsJSON,
|
||||||
"confirmation": app.config.Section("email_confirmation").Key("enabled").MustBool(false),
|
"code": invite.Code,
|
||||||
"userExpiry": invite.UserExpiry,
|
"confirmation": app.config.Section("email_confirmation").Key("enabled").MustBool(false),
|
||||||
"userExpiryMonths": invite.UserMonths,
|
"userExpiry": invite.UserExpiry,
|
||||||
"userExpiryDays": invite.UserDays,
|
"userExpiryMonths": invite.UserMonths,
|
||||||
"userExpiryHours": invite.UserHours,
|
"userExpiryDays": invite.UserDays,
|
||||||
"userExpiryMinutes": invite.UserMinutes,
|
"userExpiryHours": invite.UserHours,
|
||||||
"userExpiryMessage": app.storage.lang.User[lang].Strings.get("yourAccountIsValidUntil"),
|
"userExpiryMinutes": invite.UserMinutes,
|
||||||
"langName": lang,
|
"userExpiryMessage": app.storage.lang.User[lang].Strings.get("yourAccountIsValidUntil"),
|
||||||
"passwordReset": false,
|
"langName": lang,
|
||||||
"customSuccessCard": false,
|
"passwordReset": false,
|
||||||
"telegramEnabled": telegram,
|
"customSuccessCard": false,
|
||||||
"discordEnabled": discord,
|
"telegramEnabled": telegram,
|
||||||
"matrixEnabled": matrix,
|
"discordEnabled": discord,
|
||||||
"emailRequired": app.config.Section("email").Key("required").MustBool(false),
|
"matrixEnabled": matrix,
|
||||||
"captcha": app.config.Section("captcha").Key("enabled").MustBool(false),
|
"emailRequired": app.config.Section("email").Key("required").MustBool(false),
|
||||||
"reCAPTCHA": app.config.Section("captcha").Key("recaptcha").MustBool(false),
|
"captcha": app.config.Section("captcha").Key("enabled").MustBool(false),
|
||||||
"reCAPTCHASiteKey": app.config.Section("captcha").Key("recaptcha_site_key").MustString(""),
|
"reCAPTCHA": app.config.Section("captcha").Key("recaptcha").MustBool(false),
|
||||||
"userPageEnabled": app.config.Section("user_page").Key("enabled").MustBool(false),
|
"reCAPTCHASiteKey": app.config.Section("captcha").Key("recaptcha_site_key").MustString(""),
|
||||||
"userPageAddress": userPageAddress,
|
"userPageEnabled": app.config.Section("user_page").Key("enabled").MustBool(false),
|
||||||
"fromUser": fromUser,
|
"userPageAddress": userPageAddress,
|
||||||
|
"fromUser": fromUser,
|
||||||
}
|
}
|
||||||
if telegram {
|
if telegram {
|
||||||
data["telegramPIN"] = app.telegram.NewAuthToken()
|
data["telegramPIN"] = app.telegram.NewAuthToken()
|
||||||
@@ -837,15 +823,12 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
// pin := ""
|
// pin := ""
|
||||||
// for _, token := range app.discord.tokens {
|
// for _, token := range app.discord.tokens {
|
||||||
// if
|
// if
|
||||||
gcHTML(gc, http.StatusOK, "form-loader.html", data)
|
app.gcHTML(gc, http.StatusOK, "form-loader.html", OtherPage, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) NoRouteHandler(gc *gin.Context) {
|
func (app *appContext) NoRouteHandler(gc *gin.Context) {
|
||||||
app.pushResources(gc, OtherPage)
|
app.pushResources(gc, OtherPage)
|
||||||
gcHTML(gc, 404, "404.html", gin.H{
|
app.gcHTML(gc, 404, "404.html", OtherPage, gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
|
||||||
"cssClass": app.cssClass,
|
|
||||||
"cssVersion": cssVersion,
|
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user