mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-29 12:30:11 +00:00
Compare commits
3 Commits
8e153cd92f
...
b546aeb440
Author | SHA1 | Date | |
---|---|---|---|
b546aeb440 | |||
99679a800d | |||
7b9b0d8a84 |
105
api-messages.go
105
api-messages.go
@ -15,12 +15,16 @@ import (
|
|||||||
// @Router /config/emails [get]
|
// @Router /config/emails [get]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @tags Configuration
|
// @tags Configuration
|
||||||
func (app *appContext) GetCustomEmails(gc *gin.Context) {
|
func (app *appContext) GetCustomContent(gc *gin.Context) {
|
||||||
lang := gc.Query("lang")
|
lang := gc.Query("lang")
|
||||||
if _, ok := app.storage.lang.Email[lang]; !ok {
|
if _, ok := app.storage.lang.Email[lang]; !ok {
|
||||||
lang = app.storage.lang.chosenEmailLang
|
lang = app.storage.lang.chosenEmailLang
|
||||||
}
|
}
|
||||||
gc.JSON(200, emailListDTO{
|
adminLang := lang
|
||||||
|
if _, ok := app.storage.lang.Admin[lang]; !ok {
|
||||||
|
adminLang = app.storage.lang.chosenAdminLang
|
||||||
|
}
|
||||||
|
list := emailListDTO{
|
||||||
"UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.customEmails.UserCreated.Enabled},
|
"UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.customEmails.UserCreated.Enabled},
|
||||||
"InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.customEmails.InviteExpiry.Enabled},
|
"InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.customEmails.InviteExpiry.Enabled},
|
||||||
"PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.customEmails.PasswordReset.Enabled},
|
"PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.customEmails.PasswordReset.Enabled},
|
||||||
@ -31,13 +35,25 @@ func (app *appContext) GetCustomEmails(gc *gin.Context) {
|
|||||||
"WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.customEmails.WelcomeEmail.Enabled},
|
"WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.customEmails.WelcomeEmail.Enabled},
|
||||||
"EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.customEmails.EmailConfirmation.Enabled},
|
"EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.customEmails.EmailConfirmation.Enabled},
|
||||||
"UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.customEmails.UserExpired.Enabled},
|
"UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.customEmails.UserExpired.Enabled},
|
||||||
})
|
"UserLogin": {Name: app.storage.lang.Admin[adminLang].Strings["userPageLogin"], Enabled: app.storage.userPage.Login.Enabled},
|
||||||
|
"UserPage": {Name: app.storage.lang.Admin[adminLang].Strings["userPagePage"], Enabled: app.storage.userPage.Page.Enabled},
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := gc.Query("filter")
|
||||||
|
if filter == "user" {
|
||||||
|
list = emailListDTO{"UserLogin": list["UserLogin"], "UserPage": list["UserPage"]}
|
||||||
|
} else {
|
||||||
|
delete(list, "UserLogin")
|
||||||
|
delete(list, "UserPage")
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.JSON(200, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) getCustomEmail(id string) *customEmail {
|
func (app *appContext) getCustomMessage(id string) *customContent {
|
||||||
switch id {
|
switch id {
|
||||||
case "Announcement":
|
case "Announcement":
|
||||||
return &customEmail{}
|
return &customContent{}
|
||||||
case "UserCreated":
|
case "UserCreated":
|
||||||
return &app.storage.customEmails.UserCreated
|
return &app.storage.customEmails.UserCreated
|
||||||
case "InviteExpiry":
|
case "InviteExpiry":
|
||||||
@ -58,13 +74,17 @@ func (app *appContext) getCustomEmail(id string) *customEmail {
|
|||||||
return &app.storage.customEmails.EmailConfirmation
|
return &app.storage.customEmails.EmailConfirmation
|
||||||
case "UserExpired":
|
case "UserExpired":
|
||||||
return &app.storage.customEmails.UserExpired
|
return &app.storage.customEmails.UserExpired
|
||||||
|
case "UserLogin":
|
||||||
|
return &app.storage.userPage.Login
|
||||||
|
case "UserPage":
|
||||||
|
return &app.storage.userPage.Page
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Sets the corresponding custom email.
|
// @Summary Sets the corresponding custom email.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param customEmail body customEmail true "Content = email (in markdown)."
|
// @Param customEmails body customEmails true "Content = email (in markdown)."
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
// @Failure 400 {object} boolResponse
|
// @Failure 400 {object} boolResponse
|
||||||
// @Failure 500 {object} boolResponse
|
// @Failure 500 {object} boolResponse
|
||||||
@ -72,25 +92,29 @@ func (app *appContext) getCustomEmail(id string) *customEmail {
|
|||||||
// @Router /config/emails/{id} [post]
|
// @Router /config/emails/{id} [post]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @tags Configuration
|
// @tags Configuration
|
||||||
func (app *appContext) SetCustomEmail(gc *gin.Context) {
|
func (app *appContext) SetCustomMessage(gc *gin.Context) {
|
||||||
var req customEmail
|
var req customContent
|
||||||
gc.BindJSON(&req)
|
gc.BindJSON(&req)
|
||||||
id := gc.Param("id")
|
id := gc.Param("id")
|
||||||
if req.Content == "" {
|
if req.Content == "" {
|
||||||
respondBool(400, false, gc)
|
respondBool(400, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
email := app.getCustomEmail(id)
|
message := app.getCustomMessage(id)
|
||||||
if email == nil {
|
if message == nil {
|
||||||
respondBool(400, false, gc)
|
respondBool(400, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
email.Content = req.Content
|
message.Content = req.Content
|
||||||
email.Enabled = true
|
message.Enabled = true
|
||||||
if app.storage.storeCustomEmails() != nil {
|
if app.storage.storeCustomEmails() != nil {
|
||||||
respondBool(500, false, gc)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if app.storage.storeUserPageContent() != nil {
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +128,7 @@ func (app *appContext) SetCustomEmail(gc *gin.Context) {
|
|||||||
// @Router /config/emails/{id}/state/{enable/disable} [post]
|
// @Router /config/emails/{id}/state/{enable/disable} [post]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @tags Configuration
|
// @tags Configuration
|
||||||
func (app *appContext) SetCustomEmailState(gc *gin.Context) {
|
func (app *appContext) SetCustomMessageState(gc *gin.Context) {
|
||||||
id := gc.Param("id")
|
id := gc.Param("id")
|
||||||
s := gc.Param("state")
|
s := gc.Param("state")
|
||||||
enabled := false
|
enabled := false
|
||||||
@ -113,20 +137,24 @@ func (app *appContext) SetCustomEmailState(gc *gin.Context) {
|
|||||||
} else if s != "disable" {
|
} else if s != "disable" {
|
||||||
respondBool(400, false, gc)
|
respondBool(400, false, gc)
|
||||||
}
|
}
|
||||||
email := app.getCustomEmail(id)
|
message := app.getCustomMessage(id)
|
||||||
if email == nil {
|
if message == nil {
|
||||||
respondBool(400, false, gc)
|
respondBool(400, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
email.Enabled = enabled
|
message.Enabled = enabled
|
||||||
if app.storage.storeCustomEmails() != nil {
|
if app.storage.storeCustomEmails() != nil {
|
||||||
respondBool(500, false, gc)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if app.storage.storeUserPageContent() != nil {
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Returns the custom email (generating it if not set) and list of used variables in it.
|
// @Summary Returns the custom email/message (generating it if not set) and list of used variables in it.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} customEmailDTO
|
// @Success 200 {object} customEmailDTO
|
||||||
// @Failure 400 {object} boolResponse
|
// @Failure 400 {object} boolResponse
|
||||||
@ -135,7 +163,7 @@ func (app *appContext) SetCustomEmailState(gc *gin.Context) {
|
|||||||
// @Router /config/emails/{id} [get]
|
// @Router /config/emails/{id} [get]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @tags Configuration
|
// @tags Configuration
|
||||||
func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) {
|
||||||
lang := app.storage.lang.chosenEmailLang
|
lang := app.storage.lang.chosenEmailLang
|
||||||
id := gc.Param("id")
|
id := gc.Param("id")
|
||||||
var content string
|
var content string
|
||||||
@ -146,20 +174,26 @@ func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
|||||||
var values map[string]interface{}
|
var values map[string]interface{}
|
||||||
username := app.storage.lang.Email[lang].Strings.get("username")
|
username := app.storage.lang.Email[lang].Strings.get("username")
|
||||||
emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress")
|
emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress")
|
||||||
email := app.getCustomEmail(id)
|
customMessage := app.getCustomMessage(id)
|
||||||
if email == nil {
|
if customMessage == nil {
|
||||||
app.err.Printf("Failed to get custom email with ID \"%s\"", id)
|
app.err.Printf("Failed to get custom message with ID \"%s\"", id)
|
||||||
respondBool(400, false, gc)
|
respondBool(400, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if id == "WelcomeEmail" {
|
if id == "WelcomeEmail" {
|
||||||
conditionals = []string{"{yourAccountWillExpire}"}
|
conditionals = []string{"{yourAccountWillExpire}"}
|
||||||
email.Conditionals = conditionals
|
customMessage.Conditionals = conditionals
|
||||||
|
} else if id == "UserPage" {
|
||||||
|
variables = []string{"{username}"}
|
||||||
|
customMessage.Variables = variables
|
||||||
|
} else if id == "UserLogin" {
|
||||||
|
variables = []string{}
|
||||||
|
customMessage.Variables = variables
|
||||||
}
|
}
|
||||||
content = email.Content
|
content = customMessage.Content
|
||||||
noContent := content == ""
|
noContent := content == ""
|
||||||
if !noContent {
|
if !noContent {
|
||||||
variables = email.Variables
|
variables = customMessage.Variables
|
||||||
}
|
}
|
||||||
switch id {
|
switch id {
|
||||||
case "Announcement":
|
case "Announcement":
|
||||||
@ -215,12 +249,14 @@ func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
|||||||
msg, err = app.email.constructUserExpired(app, true)
|
msg, err = app.email.constructUserExpired(app, true)
|
||||||
}
|
}
|
||||||
values = app.email.userExpiredValues(app, false)
|
values = app.email.userExpiredValues(app, false)
|
||||||
|
case "UserLogin", "UserPage":
|
||||||
|
values = map[string]interface{}{}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondBool(500, false, gc)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if noContent && id != "Announcement" {
|
if noContent && id != "Announcement" && id != "UserPage" && id != "UserLogin" {
|
||||||
content = msg.Text
|
content = msg.Text
|
||||||
variables = make([]string, strings.Count(content, "{"))
|
variables = make([]string, strings.Count(content, "{"))
|
||||||
i := 0
|
i := 0
|
||||||
@ -239,7 +275,7 @@ func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
email.Variables = variables
|
customMessage.Variables = variables
|
||||||
}
|
}
|
||||||
if variables == nil {
|
if variables == nil {
|
||||||
variables = []string{}
|
variables = []string{}
|
||||||
@ -248,10 +284,21 @@ func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
|||||||
respondBool(500, false, gc)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mail, err := app.email.constructTemplate("", "<div class=\"preview-content\"></div>", app)
|
if app.storage.storeUserPageContent() != nil {
|
||||||
if err != nil {
|
|
||||||
respondBool(500, false, gc)
|
respondBool(500, false, gc)
|
||||||
return
|
}
|
||||||
|
var mail *Message
|
||||||
|
if id != "UserLogin" && id != "UserPage" {
|
||||||
|
mail, err = app.email.constructTemplate("", "<div class=\"preview-content\"></div>", app)
|
||||||
|
if err != nil {
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mail = &Message{
|
||||||
|
HTML: "<div class=\"card ~neutral dark:~d_neutral @low preview-content\"></div>",
|
||||||
|
Markdown: "<div class=\"card ~neutral dark:~d_neutral @low preview-content\"></div>",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
gc.JSON(200, customEmailDTO{Content: content, Variables: variables, Conditionals: conditionals, Values: values, HTML: mail.HTML, Plaintext: mail.Text})
|
gc.JSON(200, customEmailDTO{Content: content, Variables: variables, Conditionals: conditionals, Values: values, HTML: mail.HTML, Plaintext: mail.Text})
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ func (app *appContext) loadConfig() error {
|
|||||||
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
|
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users", "telegram_users", "discord_users", "matrix_users", "announcements"} {
|
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users", "telegram_users", "discord_users", "matrix_users", "announcements", "custom_user_page_content"} {
|
||||||
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
|
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
|
||||||
}
|
}
|
||||||
for _, key := range []string{"matrix_sql"} {
|
for _, key := range []string{"matrix_sql"} {
|
||||||
@ -160,6 +160,12 @@ func (app *appContext) loadConfig() error {
|
|||||||
app.storage.customEmails_path = app.config.Section("files").Key("custom_emails").String()
|
app.storage.customEmails_path = app.config.Section("files").Key("custom_emails").String()
|
||||||
app.storage.loadCustomEmails()
|
app.storage.loadCustomEmails()
|
||||||
|
|
||||||
|
app.MustSetValue("user_page", "enabled", "true")
|
||||||
|
if app.config.Section("user_page").Key("enabled").MustBool(false) {
|
||||||
|
app.storage.userPage_path = app.config.Section("files").Key("custom_user_page_content").String()
|
||||||
|
app.storage.loadUserPageContent()
|
||||||
|
}
|
||||||
|
|
||||||
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
|
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
|
||||||
|
|
||||||
if substituteStrings != "" {
|
if substituteStrings != "" {
|
||||||
|
@ -1551,6 +1551,14 @@
|
|||||||
"value": "",
|
"value": "",
|
||||||
"description": "JSON file generated by program in settings, different from email_html/email_text. See wiki for more info."
|
"description": "JSON file generated by program in settings, different from email_html/email_text. See wiki for more info."
|
||||||
},
|
},
|
||||||
|
"custom_user_page_content": {
|
||||||
|
"name": "Custom user page content",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "JSON file generated by program in settings, containing user page messages. See wiki for more info."
|
||||||
|
},
|
||||||
"telegram_users": {
|
"telegram_users": {
|
||||||
"name": "Telegram users",
|
"name": "Telegram users",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
--border-width-2: 3px;
|
--border-width-2: 3px;
|
||||||
--border-width-4: 5px;
|
--border-width-4: 5px;
|
||||||
--border-width-8: 8px;
|
--border-width-8: 8px;
|
||||||
|
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
}
|
}
|
||||||
|
|
||||||
.light {
|
.light {
|
||||||
|
4
email.go
4
email.go
@ -24,7 +24,7 @@ import (
|
|||||||
sMail "github.com/xhit/go-simple-mail/v2"
|
sMail "github.com/xhit/go-simple-mail/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var renderer = html.NewRenderer(html.RendererOptions{Flags: html.Smartypants})
|
var markdownRenderer = html.NewRenderer(html.RendererOptions{Flags: html.Smartypants})
|
||||||
|
|
||||||
// EmailClient implements email sending, right now via smtp, mailgun or a dummy client.
|
// EmailClient implements email sending, right now via smtp, mailgun or a dummy client.
|
||||||
type EmailClient interface {
|
type EmailClient interface {
|
||||||
@ -353,7 +353,7 @@ func (emailer *Emailer) constructTemplate(subject, md string, app *appContext, u
|
|||||||
subject = templateEmail(subject, []string{"{username}"}, nil, map[string]interface{}{"username": username[0]})
|
subject = templateEmail(subject, []string{"{username}"}, nil, map[string]interface{}{"username": username[0]})
|
||||||
}
|
}
|
||||||
email := &Message{Subject: subject}
|
email := &Message{Subject: subject}
|
||||||
html := markdown.ToHTML([]byte(md), nil, renderer)
|
html := markdown.ToHTML([]byte(md), nil, markdownRenderer)
|
||||||
text := stripMarkdown(md)
|
text := stripMarkdown(md)
|
||||||
message := app.config.Section("messages").Key("message").String()
|
message := app.config.Section("messages").Key("message").String()
|
||||||
var err error
|
var err error
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<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">
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
<div id="modal-login" class="modal">
|
<div id="modal-login" class="modal">
|
||||||
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3 form-login" href="">
|
<div class="my-[10%] row items-stretch relative mx-auto w-[40%] lg:w-[60%]">
|
||||||
<span class="heading">{{ .strings.login }}</span>
|
{{ if index . "LoginMessageEnabled" }}
|
||||||
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="login-user">
|
{{ if .LoginMessageEnabled }}
|
||||||
<input type="password" class="field input ~neutral @high mb-4" placeholder="{{ .strings.password }}" id="login-password">
|
<div class="card mx-2 flex-initial w-[100%] xl:w-[35%] mb-4 xl:mb-0 ~neutral @low content">
|
||||||
<label>
|
{{ .LoginMessageContent }}
|
||||||
<input type="submit" class="unfocused">
|
</div>
|
||||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.login }}</span>
|
{{ end }}
|
||||||
</label>
|
{{ end }}
|
||||||
</form>
|
<form class="card mx-2 flex-auto form-login w-[100%] xl:w-[55%] mb-0" href="">
|
||||||
|
<span class="heading">{{ .strings.login }}</span>
|
||||||
|
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="login-user">
|
||||||
|
<input type="password" class="field input ~neutral @high mb-4" placeholder="{{ .strings.password }}" id="login-password">
|
||||||
|
<label>
|
||||||
|
<input type="submit" class="unfocused">
|
||||||
|
<span class="button ~urge @low full-width center supra submit">{{ .strings.login }}</span>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{ template "login-modal.html" . }}
|
||||||
{{ template "account-linking.html" . }}
|
{{ template "account-linking.html" . }}
|
||||||
<div id="notification-box"></div>
|
<div id="notification-box"></div>
|
||||||
<div class="top-4 left-4 absolute">
|
<div class="top-4 left-4 absolute">
|
||||||
@ -68,12 +69,11 @@
|
|||||||
<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>
|
||||||
<span class="button ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span>
|
<span class="button ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span>
|
||||||
</div>
|
</div>
|
||||||
{{ template "login-modal.html" . }}
|
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<div class="card @low dark:~d_neutral mb-4" id="card-user">
|
<div class="card @low dark:~d_neutral mb-4" id="card-user">
|
||||||
<span class="heading mb-2"></span>
|
<span class="heading mb-2"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
<div class="card @low dark:~d_neutral unfocused" id="card-status">
|
<div class="card @low dark:~d_neutral unfocused" id="card-status">
|
||||||
<span class="heading mb-2">{{ .strings.expiry }}</span>
|
<span class="heading mb-2">{{ .strings.expiry }}</span>
|
||||||
<aside class="aside ~warning user-expiry my-4"></aside>
|
<aside class="aside ~warning user-expiry my-4"></aside>
|
||||||
@ -83,6 +83,13 @@
|
|||||||
<span class="heading mb-2">{{ .strings.contactMethods }}</span>
|
<span class="heading mb-2">{{ .strings.contactMethods }}</span>
|
||||||
<div class="content flex justify-between flex-col h-100"></div>
|
<div class="content flex justify-between flex-col h-100"></div>
|
||||||
</div>
|
</div>
|
||||||
|
{{ if index . "PageMessageEnabled" }}
|
||||||
|
{{ if .PageMessageEnabled }}
|
||||||
|
<div class="card @low dark:~d_neutral content">
|
||||||
|
{{ .PageMessageContent }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="{{ .urlBase }}/js/user.js" type="module"></script>
|
<script src="{{ .urlBase }}/js/user.js" type="module"></script>
|
||||||
|
@ -113,7 +113,9 @@
|
|||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"searchOptions": "Search Options",
|
"searchOptions": "Search Options",
|
||||||
"matchText": "Match Text",
|
"matchText": "Match Text",
|
||||||
"jellyfinID": "Jellyfin ID"
|
"jellyfinID": "Jellyfin ID",
|
||||||
|
"userPageLogin": "User Page: Login",
|
||||||
|
"userPagePage": "User Page: Page"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"changedEmailAddress": "Changed email address of {n}.",
|
"changedEmailAddress": "Changed email address of {n}.",
|
||||||
@ -209,4 +211,4 @@
|
|||||||
"plural": "Extended expiry for {n} users."
|
"plural": "Extended expiry for {n} users."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,4 +61,4 @@
|
|||||||
"plural": "{n} Days"
|
"plural": "{n} Days"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
"sendPINDiscord": "Type {command} in {server_channel} on Discord, then send the PIN below.",
|
"sendPINDiscord": "Type {command} in {server_channel} on Discord, then send the PIN below.",
|
||||||
"matrixEnterUser": "Enter your User ID, press submit, and a PIN will be sent to you. Enter it here to continue.",
|
"matrixEnterUser": "Enter your User ID, press submit, and a PIN will be sent to you. Enter it here to continue.",
|
||||||
"welcomeUser": "Welcome, {user}!",
|
"welcomeUser": "Welcome, {user}!",
|
||||||
|
"addContactMethod": "Add Contact Method",
|
||||||
|
"editContactMethod": "Edit Contact Method",
|
||||||
"joinTheServer": "Join the server:"
|
"joinTheServer": "Join the server:"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
@ -61,4 +63,4 @@
|
|||||||
"plural": "Must have at least {n} special characters"
|
"plural": "Must have at least {n} special characters"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,7 +249,7 @@ func (d *MatrixDaemon) Send(message *Message, users ...MatrixUser) (err error) {
|
|||||||
md := ""
|
md := ""
|
||||||
if message.Markdown != "" {
|
if message.Markdown != "" {
|
||||||
// Convert images to links
|
// Convert images to links
|
||||||
md = string(markdown.ToHTML([]byte(strings.ReplaceAll(message.Markdown, "![", "[")), nil, renderer))
|
md = string(markdown.ToHTML([]byte(strings.ReplaceAll(message.Markdown, "![", "[")), nil, markdownRenderer))
|
||||||
}
|
}
|
||||||
content := &event.MessageEventContent{
|
content := &event.MessageEventContent{
|
||||||
MsgType: "m.text",
|
MsgType: "m.text",
|
||||||
|
@ -196,10 +196,10 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
|
|
||||||
api.GET(p+"/config/update", app.CheckUpdate)
|
api.GET(p+"/config/update", app.CheckUpdate)
|
||||||
api.POST(p+"/config/update", app.ApplyUpdate)
|
api.POST(p+"/config/update", app.ApplyUpdate)
|
||||||
api.GET(p+"/config/emails", app.GetCustomEmails)
|
api.GET(p+"/config/emails", app.GetCustomContent)
|
||||||
api.GET(p+"/config/emails/:id", app.GetCustomEmailTemplate)
|
api.GET(p+"/config/emails/:id", app.GetCustomMessageTemplate)
|
||||||
api.POST(p+"/config/emails/:id", app.SetCustomEmail)
|
api.POST(p+"/config/emails/:id", app.SetCustomMessage)
|
||||||
api.POST(p+"/config/emails/:id/state/:state", app.SetCustomEmailState)
|
api.POST(p+"/config/emails/:id/state/:state", app.SetCustomMessageState)
|
||||||
api.GET(p+"/config", app.GetConfig)
|
api.GET(p+"/config", app.GetConfig)
|
||||||
api.POST(p+"/config", app.ModifyConfig)
|
api.POST(p+"/config", app.ModifyConfig)
|
||||||
api.POST(p+"/restart", app.restart)
|
api.POST(p+"/restart", app.restart)
|
||||||
|
70
storage.go
70
storage.go
@ -21,23 +21,24 @@ type matrixStore map[string]MatrixUser
|
|||||||
type emailStore map[string]EmailAddress
|
type emailStore map[string]EmailAddress
|
||||||
|
|
||||||
type Storage struct {
|
type Storage struct {
|
||||||
timePattern string
|
timePattern string
|
||||||
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path, matrix_path, announcements_path, matrix_sql_path string
|
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path, matrix_path, announcements_path, matrix_sql_path, userPage_path string
|
||||||
users map[string]time.Time // Map of Jellyfin User IDs to their expiry times.
|
users map[string]time.Time // Map of Jellyfin User IDs to their expiry times.
|
||||||
invites Invites
|
invites Invites
|
||||||
profiles map[string]Profile
|
profiles map[string]Profile
|
||||||
defaultProfile string
|
defaultProfile string
|
||||||
displayprefs, ombi_template map[string]interface{}
|
displayprefs, ombi_template map[string]interface{}
|
||||||
emails emailStore
|
emails emailStore
|
||||||
telegram telegramStore // Map of Jellyfin User IDs to telegram users.
|
telegram telegramStore // Map of Jellyfin User IDs to telegram users.
|
||||||
discord discordStore // Map of Jellyfin user IDs to discord users.
|
discord discordStore // Map of Jellyfin user IDs to discord users.
|
||||||
matrix matrixStore // Map of Jellyfin user IDs to Matrix users.
|
matrix matrixStore // Map of Jellyfin user IDs to Matrix users.
|
||||||
customEmails customEmails
|
customEmails customEmails
|
||||||
policy mediabrowser.Policy
|
userPage userPageContent
|
||||||
configuration mediabrowser.Configuration
|
policy mediabrowser.Policy
|
||||||
lang Lang
|
configuration mediabrowser.Configuration
|
||||||
announcements map[string]announcementTemplate
|
lang Lang
|
||||||
invitesLock, usersLock, discordLock, telegramLock, matrixLock, emailsLock sync.Mutex
|
announcements map[string]announcementTemplate
|
||||||
|
invitesLock, usersLock, discordLock, telegramLock, matrixLock, emailsLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEmails returns a copy of the store.
|
// GetEmails returns a copy of the store.
|
||||||
@ -172,25 +173,30 @@ type EmailAddress struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type customEmails struct {
|
type customEmails struct {
|
||||||
UserCreated customEmail `json:"userCreated"`
|
UserCreated customContent `json:"userCreated"`
|
||||||
InviteExpiry customEmail `json:"inviteExpiry"`
|
InviteExpiry customContent `json:"inviteExpiry"`
|
||||||
PasswordReset customEmail `json:"passwordReset"`
|
PasswordReset customContent `json:"passwordReset"`
|
||||||
UserDeleted customEmail `json:"userDeleted"`
|
UserDeleted customContent `json:"userDeleted"`
|
||||||
UserDisabled customEmail `json:"userDisabled"`
|
UserDisabled customContent `json:"userDisabled"`
|
||||||
UserEnabled customEmail `json:"userEnabled"`
|
UserEnabled customContent `json:"userEnabled"`
|
||||||
InviteEmail customEmail `json:"inviteEmail"`
|
InviteEmail customContent `json:"inviteEmail"`
|
||||||
WelcomeEmail customEmail `json:"welcomeEmail"`
|
WelcomeEmail customContent `json:"welcomeEmail"`
|
||||||
EmailConfirmation customEmail `json:"emailConfirmation"`
|
EmailConfirmation customContent `json:"emailConfirmation"`
|
||||||
UserExpired customEmail `json:"userExpired"`
|
UserExpired customContent `json:"userExpired"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type customEmail struct {
|
type customContent struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Variables []string `json:"variables,omitempty"`
|
Variables []string `json:"variables,omitempty"`
|
||||||
Conditionals []string `json:"conditionals,omitempty"`
|
Conditionals []string `json:"conditionals,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type userPageContent struct {
|
||||||
|
Login customContent `json:"login"`
|
||||||
|
Page customContent `json:"page"`
|
||||||
|
}
|
||||||
|
|
||||||
// timePattern: %Y-%m-%dT%H:%M:%S.%f
|
// timePattern: %Y-%m-%dT%H:%M:%S.%f
|
||||||
|
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
@ -981,6 +987,14 @@ func (st *Storage) storeCustomEmails() error {
|
|||||||
return storeJSON(st.customEmails_path, st.customEmails)
|
return storeJSON(st.customEmails_path, st.customEmails)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st *Storage) loadUserPageContent() error {
|
||||||
|
return loadJSON(st.userPage_path, &st.userPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *Storage) storeUserPageContent() error {
|
||||||
|
return storeJSON(st.userPage_path, st.userPage)
|
||||||
|
}
|
||||||
|
|
||||||
func (st *Storage) loadPolicy() error {
|
func (st *Storage) loadPolicy() error {
|
||||||
return loadJSON(st.policy_path, &st.policy)
|
return loadJSON(st.policy_path, &st.policy)
|
||||||
}
|
}
|
||||||
|
@ -542,7 +542,7 @@ export class settingsList {
|
|||||||
private _sections: { [name: string]: sectionPanel }
|
private _sections: { [name: string]: sectionPanel }
|
||||||
private _buttons: { [name: string]: HTMLSpanElement }
|
private _buttons: { [name: string]: HTMLSpanElement }
|
||||||
private _needsRestart: boolean = false;
|
private _needsRestart: boolean = false;
|
||||||
private _emailEditor = new EmailEditor();
|
private _messageEditor = new MessageEditor();
|
||||||
|
|
||||||
addSection = (name: string, s: Section, subButton?: HTMLElement) => {
|
addSection = (name: string, s: Section, subButton?: HTMLElement) => {
|
||||||
const section = new sectionPanel(s, name);
|
const section = new sectionPanel(s, name);
|
||||||
@ -713,7 +713,7 @@ export class settingsList {
|
|||||||
if (name in this._sections) {
|
if (name in this._sections) {
|
||||||
this._sections[name].update(settings.sections[name]);
|
this._sections[name].update(settings.sections[name]);
|
||||||
} else {
|
} else {
|
||||||
if (name == "messages") {
|
if (name == "messages" || name == "user_page") {
|
||||||
const editButton = document.createElement("div");
|
const editButton = document.createElement("div");
|
||||||
editButton.classList.add("tooltip", "left");
|
editButton.classList.add("tooltip", "left");
|
||||||
editButton.innerHTML = `
|
editButton.innerHTML = `
|
||||||
@ -724,7 +724,9 @@ export class settingsList {
|
|||||||
${window.lang.get("strings", "customizeMessages")}
|
${window.lang.get("strings", "customizeMessages")}
|
||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
(editButton.querySelector("span.button") as HTMLSpanElement).onclick = this._emailEditor.showList;
|
(editButton.querySelector("span.button") as HTMLSpanElement).onclick = () => {
|
||||||
|
this._messageEditor.showList(name == "messages" ? "email" : "user");
|
||||||
|
};
|
||||||
this.addSection(name, settings.sections[name], editButton);
|
this.addSection(name, settings.sections[name], editButton);
|
||||||
} else if (name == "updates") {
|
} else if (name == "updates") {
|
||||||
const icon = document.createElement("span") as HTMLSpanElement;
|
const icon = document.createElement("span") as HTMLSpanElement;
|
||||||
@ -773,7 +775,7 @@ interface emailListEl {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmailEditor {
|
class MessageEditor {
|
||||||
private _currentID: string;
|
private _currentID: string;
|
||||||
private _names: { [id: string]: emailListEl };
|
private _names: { [id: string]: emailListEl };
|
||||||
private _content: string;
|
private _content: string;
|
||||||
@ -884,8 +886,8 @@ class EmailEditor {
|
|||||||
// }, true);
|
// }, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
showList = () => {
|
showList = (filter?: string) => {
|
||||||
_get("/config/emails?lang=" + window.language, null, (req: XMLHttpRequest) => {
|
_get("/config/emails?lang=" + window.language + (filter ? "&filter=" + filter : ""), null, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
if (req.status != 200) {
|
if (req.status != 200) {
|
||||||
window.notifications.customError("loadTemplateError", window.lang.notif("errorFailureCheckLogs"));
|
window.notifications.customError("loadTemplateError", window.lang.notif("errorFailureCheckLogs"));
|
||||||
|
@ -94,17 +94,17 @@ class ContactMethods {
|
|||||||
|
|
||||||
append = (name: string, details: MyDetailsContactMethod, icon: string, addEditFunc?: (add: boolean) => void, required?: boolean) => {
|
append = (name: string, details: MyDetailsContactMethod, icon: string, addEditFunc?: (add: boolean) => void, required?: boolean) => {
|
||||||
const row = document.createElement("div");
|
const row = document.createElement("div");
|
||||||
row.classList.add("row", "flex-expand", "my-2");
|
row.classList.add("flex", "flex-expand", "my-2", "flex-nowrap");
|
||||||
let innerHTML = `
|
let innerHTML = `
|
||||||
<div class="inline align-middle">
|
<div class="flex items-baseline flex-nowrap ellipsis">
|
||||||
<span class="shield ~urge" alt="${name}">
|
<span class="shield ~urge" alt="${name}">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
${icon}
|
${icon}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="ml-2 font-bold">${(details.value == "") ? window.lang.strings("notSet") : details.value}</span>
|
<span class="ml-2 font-bold text-ellipsis overflow-hidden">${(details.value == "") ? window.lang.strings("notSet") : details.value}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center ml-2">
|
||||||
<button class="user-contact-enabled-disabled button ~neutral" ${details.value == "" ? "disabled" : ""}>
|
<button class="user-contact-enabled-disabled button ~neutral" ${details.value == "" ? "disabled" : ""}>
|
||||||
<input type="checkbox" class="mr-2" ${details.value == "" ? "disabled" : ""}>
|
<input type="checkbox" class="mr-2" ${details.value == "" ? "disabled" : ""}>
|
||||||
<span>${window.lang.strings("enabled")}</span>
|
<span>${window.lang.strings("enabled")}</span>
|
||||||
|
19
views.go
19
views.go
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
|
"github.com/gomarkdown/markdown"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
"github.com/steambap/captcha"
|
"github.com/steambap/captcha"
|
||||||
)
|
)
|
||||||
@ -203,6 +204,24 @@ func (app *appContext) MyUserPage(gc *gin.Context) {
|
|||||||
data["discordServerName"] = app.discord.serverName
|
data["discordServerName"] = app.discord.serverName
|
||||||
data["discordInviteLink"] = app.discord.inviteChannelName != ""
|
data["discordInviteLink"] = app.discord.inviteChannelName != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pageMessages := map[string]*customContent{
|
||||||
|
"Login": app.getCustomMessage("UserLogin"),
|
||||||
|
"Page": app.getCustomMessage("UserPage"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, msg := range pageMessages {
|
||||||
|
if msg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data[name+"MessageEnabled"] = msg.Enabled
|
||||||
|
if !msg.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// We don't template here, since the username is only known after login.
|
||||||
|
data[name+"MessageContent"] = template.HTML(markdown.ToHTML([]byte(msg.Content), nil, markdownRenderer))
|
||||||
|
}
|
||||||
|
|
||||||
gcHTML(gc, http.StatusOK, "user.html", data)
|
gcHTML(gc, http.StatusOK, "user.html", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user