mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-28 20:10: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]
|
||||
// @Security Bearer
|
||||
// @tags Configuration
|
||||
func (app *appContext) GetCustomEmails(gc *gin.Context) {
|
||||
func (app *appContext) GetCustomContent(gc *gin.Context) {
|
||||
lang := gc.Query("lang")
|
||||
if _, ok := app.storage.lang.Email[lang]; !ok {
|
||||
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},
|
||||
"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},
|
||||
@ -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},
|
||||
"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},
|
||||
})
|
||||
"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 {
|
||||
case "Announcement":
|
||||
return &customEmail{}
|
||||
return &customContent{}
|
||||
case "UserCreated":
|
||||
return &app.storage.customEmails.UserCreated
|
||||
case "InviteExpiry":
|
||||
@ -58,13 +74,17 @@ func (app *appContext) getCustomEmail(id string) *customEmail {
|
||||
return &app.storage.customEmails.EmailConfirmation
|
||||
case "UserExpired":
|
||||
return &app.storage.customEmails.UserExpired
|
||||
case "UserLogin":
|
||||
return &app.storage.userPage.Login
|
||||
case "UserPage":
|
||||
return &app.storage.userPage.Page
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// @Summary Sets the corresponding custom email.
|
||||
// @Produce json
|
||||
// @Param customEmail body customEmail true "Content = email (in markdown)."
|
||||
// @Param customEmails body customEmails true "Content = email (in markdown)."
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
@ -72,25 +92,29 @@ func (app *appContext) getCustomEmail(id string) *customEmail {
|
||||
// @Router /config/emails/{id} [post]
|
||||
// @Security Bearer
|
||||
// @tags Configuration
|
||||
func (app *appContext) SetCustomEmail(gc *gin.Context) {
|
||||
var req customEmail
|
||||
func (app *appContext) SetCustomMessage(gc *gin.Context) {
|
||||
var req customContent
|
||||
gc.BindJSON(&req)
|
||||
id := gc.Param("id")
|
||||
if req.Content == "" {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
email := app.getCustomEmail(id)
|
||||
if email == nil {
|
||||
message := app.getCustomMessage(id)
|
||||
if message == nil {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
email.Content = req.Content
|
||||
email.Enabled = true
|
||||
message.Content = req.Content
|
||||
message.Enabled = true
|
||||
if app.storage.storeCustomEmails() != nil {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
if app.storage.storeUserPageContent() != nil {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
@ -104,7 +128,7 @@ func (app *appContext) SetCustomEmail(gc *gin.Context) {
|
||||
// @Router /config/emails/{id}/state/{enable/disable} [post]
|
||||
// @Security Bearer
|
||||
// @tags Configuration
|
||||
func (app *appContext) SetCustomEmailState(gc *gin.Context) {
|
||||
func (app *appContext) SetCustomMessageState(gc *gin.Context) {
|
||||
id := gc.Param("id")
|
||||
s := gc.Param("state")
|
||||
enabled := false
|
||||
@ -113,20 +137,24 @@ func (app *appContext) SetCustomEmailState(gc *gin.Context) {
|
||||
} else if s != "disable" {
|
||||
respondBool(400, false, gc)
|
||||
}
|
||||
email := app.getCustomEmail(id)
|
||||
if email == nil {
|
||||
message := app.getCustomMessage(id)
|
||||
if message == nil {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
email.Enabled = enabled
|
||||
message.Enabled = enabled
|
||||
if app.storage.storeCustomEmails() != nil {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
if app.storage.storeUserPageContent() != nil {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
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
|
||||
// @Success 200 {object} customEmailDTO
|
||||
// @Failure 400 {object} boolResponse
|
||||
@ -135,7 +163,7 @@ func (app *appContext) SetCustomEmailState(gc *gin.Context) {
|
||||
// @Router /config/emails/{id} [get]
|
||||
// @Security Bearer
|
||||
// @tags Configuration
|
||||
func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
||||
func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) {
|
||||
lang := app.storage.lang.chosenEmailLang
|
||||
id := gc.Param("id")
|
||||
var content string
|
||||
@ -146,20 +174,26 @@ func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
||||
var values map[string]interface{}
|
||||
username := app.storage.lang.Email[lang].Strings.get("username")
|
||||
emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress")
|
||||
email := app.getCustomEmail(id)
|
||||
if email == nil {
|
||||
app.err.Printf("Failed to get custom email with ID \"%s\"", id)
|
||||
customMessage := app.getCustomMessage(id)
|
||||
if customMessage == nil {
|
||||
app.err.Printf("Failed to get custom message with ID \"%s\"", id)
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
if id == "WelcomeEmail" {
|
||||
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 == ""
|
||||
if !noContent {
|
||||
variables = email.Variables
|
||||
variables = customMessage.Variables
|
||||
}
|
||||
switch id {
|
||||
case "Announcement":
|
||||
@ -215,12 +249,14 @@ func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
||||
msg, err = app.email.constructUserExpired(app, true)
|
||||
}
|
||||
values = app.email.userExpiredValues(app, false)
|
||||
case "UserLogin", "UserPage":
|
||||
values = map[string]interface{}{}
|
||||
}
|
||||
if err != nil {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
if noContent && id != "Announcement" {
|
||||
if noContent && id != "Announcement" && id != "UserPage" && id != "UserLogin" {
|
||||
content = msg.Text
|
||||
variables = make([]string, strings.Count(content, "{"))
|
||||
i := 0
|
||||
@ -239,7 +275,7 @@ func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
||||
i++
|
||||
}
|
||||
}
|
||||
email.Variables = variables
|
||||
customMessage.Variables = variables
|
||||
}
|
||||
if variables == nil {
|
||||
variables = []string{}
|
||||
@ -248,10 +284,21 @@ func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
mail, err := app.email.constructTemplate("", "<div class=\"preview-content\"></div>", app)
|
||||
if err != nil {
|
||||
if app.storage.storeUserPageContent() != nil {
|
||||
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})
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ func (app *appContext) loadConfig() error {
|
||||
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"))))
|
||||
}
|
||||
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.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("")
|
||||
|
||||
if substituteStrings != "" {
|
||||
|
@ -1551,6 +1551,14 @@
|
||||
"value": "",
|
||||
"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": {
|
||||
"name": "Telegram users",
|
||||
"required": false,
|
||||
|
@ -13,6 +13,8 @@
|
||||
--border-width-2: 3px;
|
||||
--border-width-4: 5px;
|
||||
--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 {
|
||||
|
4
email.go
4
email.go
@ -24,7 +24,7 @@ import (
|
||||
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.
|
||||
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]})
|
||||
}
|
||||
email := &Message{Subject: subject}
|
||||
html := markdown.ToHTML([]byte(md), nil, renderer)
|
||||
html := markdown.ToHTML([]byte(md), nil, markdownRenderer)
|
||||
text := stripMarkdown(md)
|
||||
message := app.config.Section("messages").Key("message").String()
|
||||
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="Description" content="jfa-go, a better way to manage Jellyfin users.">
|
||||
<meta name="color-scheme" content="dark light">
|
||||
|
@ -1,11 +1,20 @@
|
||||
<div id="modal-login" class="modal">
|
||||
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3 form-login" 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 class="my-[10%] row items-stretch relative mx-auto w-[40%] lg:w-[60%]">
|
||||
{{ if index . "LoginMessageEnabled" }}
|
||||
{{ if .LoginMessageEnabled }}
|
||||
<div class="card mx-2 flex-initial w-[100%] xl:w-[35%] mb-4 xl:mb-0 ~neutral @low content">
|
||||
{{ .LoginMessageContent }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<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>
|
||||
|
@ -43,6 +43,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ template "login-modal.html" . }}
|
||||
{{ template "account-linking.html" . }}
|
||||
<div id="notification-box"></div>
|
||||
<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 ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span>
|
||||
</div>
|
||||
{{ template "login-modal.html" . }}
|
||||
<div class="page-container">
|
||||
<div class="card @low dark:~d_neutral mb-4" id="card-user">
|
||||
<span class="heading mb-2"></span>
|
||||
</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">
|
||||
<span class="heading mb-2">{{ .strings.expiry }}</span>
|
||||
<aside class="aside ~warning user-expiry my-4"></aside>
|
||||
@ -83,6 +83,13 @@
|
||||
<span class="heading mb-2">{{ .strings.contactMethods }}</span>
|
||||
<div class="content flex justify-between flex-col h-100"></div>
|
||||
</div>
|
||||
{{ if index . "PageMessageEnabled" }}
|
||||
{{ if .PageMessageEnabled }}
|
||||
<div class="card @low dark:~d_neutral content">
|
||||
{{ .PageMessageContent }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{ .urlBase }}/js/user.js" type="module"></script>
|
||||
|
@ -113,7 +113,9 @@
|
||||
"actions": "Actions",
|
||||
"searchOptions": "Search Options",
|
||||
"matchText": "Match Text",
|
||||
"jellyfinID": "Jellyfin ID"
|
||||
"jellyfinID": "Jellyfin ID",
|
||||
"userPageLogin": "User Page: Login",
|
||||
"userPagePage": "User Page: Page"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Changed email address of {n}.",
|
||||
@ -209,4 +211,4 @@
|
||||
"plural": "Extended expiry for {n} users."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,4 +61,4 @@
|
||||
"plural": "{n} Days"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@
|
||||
"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.",
|
||||
"welcomeUser": "Welcome, {user}!",
|
||||
"addContactMethod": "Add Contact Method",
|
||||
"editContactMethod": "Edit Contact Method",
|
||||
"joinTheServer": "Join the server:"
|
||||
},
|
||||
"notifications": {
|
||||
@ -61,4 +63,4 @@
|
||||
"plural": "Must have at least {n} special characters"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -249,7 +249,7 @@ func (d *MatrixDaemon) Send(message *Message, users ...MatrixUser) (err error) {
|
||||
md := ""
|
||||
if message.Markdown != "" {
|
||||
// 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{
|
||||
MsgType: "m.text",
|
||||
|
@ -196,10 +196,10 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||
|
||||
api.GET(p+"/config/update", app.CheckUpdate)
|
||||
api.POST(p+"/config/update", app.ApplyUpdate)
|
||||
api.GET(p+"/config/emails", app.GetCustomEmails)
|
||||
api.GET(p+"/config/emails/:id", app.GetCustomEmailTemplate)
|
||||
api.POST(p+"/config/emails/:id", app.SetCustomEmail)
|
||||
api.POST(p+"/config/emails/:id/state/:state", app.SetCustomEmailState)
|
||||
api.GET(p+"/config/emails", app.GetCustomContent)
|
||||
api.GET(p+"/config/emails/:id", app.GetCustomMessageTemplate)
|
||||
api.POST(p+"/config/emails/:id", app.SetCustomMessage)
|
||||
api.POST(p+"/config/emails/:id/state/:state", app.SetCustomMessageState)
|
||||
api.GET(p+"/config", app.GetConfig)
|
||||
api.POST(p+"/config", app.ModifyConfig)
|
||||
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 Storage struct {
|
||||
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
|
||||
users map[string]time.Time // Map of Jellyfin User IDs to their expiry times.
|
||||
invites Invites
|
||||
profiles map[string]Profile
|
||||
defaultProfile string
|
||||
displayprefs, ombi_template map[string]interface{}
|
||||
emails emailStore
|
||||
telegram telegramStore // Map of Jellyfin User IDs to telegram users.
|
||||
discord discordStore // Map of Jellyfin user IDs to discord users.
|
||||
matrix matrixStore // Map of Jellyfin user IDs to Matrix users.
|
||||
customEmails customEmails
|
||||
policy mediabrowser.Policy
|
||||
configuration mediabrowser.Configuration
|
||||
lang Lang
|
||||
announcements map[string]announcementTemplate
|
||||
invitesLock, usersLock, discordLock, telegramLock, matrixLock, emailsLock sync.Mutex
|
||||
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, userPage_path string
|
||||
users map[string]time.Time // Map of Jellyfin User IDs to their expiry times.
|
||||
invites Invites
|
||||
profiles map[string]Profile
|
||||
defaultProfile string
|
||||
displayprefs, ombi_template map[string]interface{}
|
||||
emails emailStore
|
||||
telegram telegramStore // Map of Jellyfin User IDs to telegram users.
|
||||
discord discordStore // Map of Jellyfin user IDs to discord users.
|
||||
matrix matrixStore // Map of Jellyfin user IDs to Matrix users.
|
||||
customEmails customEmails
|
||||
userPage userPageContent
|
||||
policy mediabrowser.Policy
|
||||
configuration mediabrowser.Configuration
|
||||
lang Lang
|
||||
announcements map[string]announcementTemplate
|
||||
invitesLock, usersLock, discordLock, telegramLock, matrixLock, emailsLock sync.Mutex
|
||||
}
|
||||
|
||||
// GetEmails returns a copy of the store.
|
||||
@ -172,25 +173,30 @@ type EmailAddress struct {
|
||||
}
|
||||
|
||||
type customEmails struct {
|
||||
UserCreated customEmail `json:"userCreated"`
|
||||
InviteExpiry customEmail `json:"inviteExpiry"`
|
||||
PasswordReset customEmail `json:"passwordReset"`
|
||||
UserDeleted customEmail `json:"userDeleted"`
|
||||
UserDisabled customEmail `json:"userDisabled"`
|
||||
UserEnabled customEmail `json:"userEnabled"`
|
||||
InviteEmail customEmail `json:"inviteEmail"`
|
||||
WelcomeEmail customEmail `json:"welcomeEmail"`
|
||||
EmailConfirmation customEmail `json:"emailConfirmation"`
|
||||
UserExpired customEmail `json:"userExpired"`
|
||||
UserCreated customContent `json:"userCreated"`
|
||||
InviteExpiry customContent `json:"inviteExpiry"`
|
||||
PasswordReset customContent `json:"passwordReset"`
|
||||
UserDeleted customContent `json:"userDeleted"`
|
||||
UserDisabled customContent `json:"userDisabled"`
|
||||
UserEnabled customContent `json:"userEnabled"`
|
||||
InviteEmail customContent `json:"inviteEmail"`
|
||||
WelcomeEmail customContent `json:"welcomeEmail"`
|
||||
EmailConfirmation customContent `json:"emailConfirmation"`
|
||||
UserExpired customContent `json:"userExpired"`
|
||||
}
|
||||
|
||||
type customEmail struct {
|
||||
type customContent struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Content string `json:"content"`
|
||||
Variables []string `json:"variables,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
|
||||
|
||||
type Profile struct {
|
||||
@ -981,6 +987,14 @@ func (st *Storage) storeCustomEmails() error {
|
||||
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 {
|
||||
return loadJSON(st.policy_path, &st.policy)
|
||||
}
|
||||
|
@ -542,7 +542,7 @@ export class settingsList {
|
||||
private _sections: { [name: string]: sectionPanel }
|
||||
private _buttons: { [name: string]: HTMLSpanElement }
|
||||
private _needsRestart: boolean = false;
|
||||
private _emailEditor = new EmailEditor();
|
||||
private _messageEditor = new MessageEditor();
|
||||
|
||||
addSection = (name: string, s: Section, subButton?: HTMLElement) => {
|
||||
const section = new sectionPanel(s, name);
|
||||
@ -713,7 +713,7 @@ export class settingsList {
|
||||
if (name in this._sections) {
|
||||
this._sections[name].update(settings.sections[name]);
|
||||
} else {
|
||||
if (name == "messages") {
|
||||
if (name == "messages" || name == "user_page") {
|
||||
const editButton = document.createElement("div");
|
||||
editButton.classList.add("tooltip", "left");
|
||||
editButton.innerHTML = `
|
||||
@ -724,7 +724,9 @@ export class settingsList {
|
||||
${window.lang.get("strings", "customizeMessages")}
|
||||
</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);
|
||||
} else if (name == "updates") {
|
||||
const icon = document.createElement("span") as HTMLSpanElement;
|
||||
@ -773,7 +775,7 @@ interface emailListEl {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
class EmailEditor {
|
||||
class MessageEditor {
|
||||
private _currentID: string;
|
||||
private _names: { [id: string]: emailListEl };
|
||||
private _content: string;
|
||||
@ -884,8 +886,8 @@ class EmailEditor {
|
||||
// }, true);
|
||||
}
|
||||
|
||||
showList = () => {
|
||||
_get("/config/emails?lang=" + window.language, null, (req: XMLHttpRequest) => {
|
||||
showList = (filter?: string) => {
|
||||
_get("/config/emails?lang=" + window.language + (filter ? "&filter=" + filter : ""), null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
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) => {
|
||||
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 = `
|
||||
<div class="inline align-middle">
|
||||
<div class="flex items-baseline flex-nowrap ellipsis">
|
||||
<span class="shield ~urge" alt="${name}">
|
||||
<span class="icon">
|
||||
${icon}
|
||||
</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 class="flex items-center">
|
||||
<div class="flex items-center ml-2">
|
||||
<button class="user-contact-enabled-disabled button ~neutral" ${details.value == "" ? "disabled" : ""}>
|
||||
<input type="checkbox" class="mr-2" ${details.value == "" ? "disabled" : ""}>
|
||||
<span>${window.lang.strings("enabled")}</span>
|
||||
|
19
views.go
19
views.go
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/hrfee/mediabrowser"
|
||||
"github.com/steambap/captcha"
|
||||
)
|
||||
@ -203,6 +204,24 @@ func (app *appContext) MyUserPage(gc *gin.Context) {
|
||||
data["discordServerName"] = app.discord.serverName
|
||||
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)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user