mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +00:00
accounts: add user enable/disable & emails
This commit is contained in:
parent
dafb439a7d
commit
55e21f8be3
168
api.go
168
api.go
@ -573,6 +573,70 @@ func (app *appContext) Announce(gc *gin.Context) {
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Enable/Disable a list of users, optionally notifying them why.
|
||||
// @Produce json
|
||||
// @Param enableDisableUserDTO body enableDisableUserDTO true "User enable/disable request object"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} stringResponse
|
||||
// @Failure 500 {object} errorListDTO "List of errors"
|
||||
// @Router /users/enable [post]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) EnableDisableUsers(gc *gin.Context) {
|
||||
var req enableDisableUserDTO
|
||||
gc.BindJSON(&req)
|
||||
errors := errorListDTO{
|
||||
"GetUser": map[string]string{},
|
||||
"SetPolicy": map[string]string{},
|
||||
}
|
||||
var addresses []string
|
||||
for _, userID := range req.Users {
|
||||
user, status, err := app.jf.UserByID(userID, false)
|
||||
if status != 200 || err != nil {
|
||||
errors["GetUser"][userID] = fmt.Sprintf("%d %v", status, err)
|
||||
app.err.Printf("Failed to get user \"%s\" (%d): %v", userID, status, err)
|
||||
continue
|
||||
}
|
||||
user.Policy.IsDisabled = !req.Enabled
|
||||
status, err = app.jf.SetPolicy(userID, user.Policy)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
errors["SetPolicy"][userID] = fmt.Sprintf("%d %v", status, err)
|
||||
app.err.Printf("Failed to set policy for user \"%s\" (%d): %v", userID, status, err)
|
||||
continue
|
||||
}
|
||||
if emailEnabled && req.Notify {
|
||||
addr, ok := app.storage.emails[userID]
|
||||
if addr != nil && ok {
|
||||
addresses = append(addresses, addr.(string))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(addresses) != 0 {
|
||||
go func(reason string, addresses []string) {
|
||||
var msg *Email
|
||||
var err error
|
||||
if req.Enabled {
|
||||
msg, err = app.email.constructEnabled(reason, app, false)
|
||||
} else {
|
||||
msg, err = app.email.constructDisabled(reason, app, false)
|
||||
}
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to construct account enabled/disabled emails: %v", err)
|
||||
} else if err := app.email.send(msg, addresses...); err != nil {
|
||||
app.err.Printf("Failed to send account enabled/disabled emails: %v", err)
|
||||
} else {
|
||||
app.info.Println("Sent account enabled/disabled emails")
|
||||
}
|
||||
}(req.Reason, addresses)
|
||||
}
|
||||
app.jf.CacheExpiry = time.Now()
|
||||
if len(errors["GetUser"]) != 0 || len(errors["SetPolicy"]) != 0 {
|
||||
gc.JSON(500, errors)
|
||||
return
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Delete a list of users, optionally notifying them why.
|
||||
// @Produce json
|
||||
// @Param deleteUserDTO body deleteUserDTO true "User deletion request object"
|
||||
@ -1379,6 +1443,8 @@ func (app *appContext) GetEmails(gc *gin.Context) {
|
||||
"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},
|
||||
"UserDeleted": {Name: app.storage.lang.Email[lang].UserDeleted["name"], Enabled: app.storage.customEmails.UserDeleted.Enabled},
|
||||
"UserDisabled": {Name: app.storage.lang.Email[lang].UserDisabled["name"], Enabled: app.storage.customEmails.UserDisabled.Enabled},
|
||||
"UserEnabled": {Name: app.storage.lang.Email[lang].UserEnabled["name"], Enabled: app.storage.customEmails.UserEnabled.Enabled},
|
||||
"InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.customEmails.InviteEmail.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},
|
||||
@ -1414,6 +1480,12 @@ func (app *appContext) SetEmail(gc *gin.Context) {
|
||||
} else if id == "UserDeleted" {
|
||||
app.storage.customEmails.UserDeleted.Content = req.Content
|
||||
app.storage.customEmails.UserDeleted.Enabled = true
|
||||
} else if id == "UserDisabled" {
|
||||
app.storage.customEmails.UserDisabled.Content = req.Content
|
||||
app.storage.customEmails.UserDisabled.Enabled = true
|
||||
} else if id == "UserEnabled" {
|
||||
app.storage.customEmails.UserEnabled.Content = req.Content
|
||||
app.storage.customEmails.UserEnabled.Enabled = true
|
||||
} else if id == "InviteEmail" {
|
||||
app.storage.customEmails.InviteEmail.Content = req.Content
|
||||
app.storage.customEmails.InviteEmail.Enabled = true
|
||||
@ -1461,6 +1533,10 @@ func (app *appContext) SetEmailState(gc *gin.Context) {
|
||||
app.storage.customEmails.PasswordReset.Enabled = enabled
|
||||
} else if id == "UserDeleted" {
|
||||
app.storage.customEmails.UserDeleted.Enabled = enabled
|
||||
} else if id == "UserDisabled" {
|
||||
app.storage.customEmails.UserDisabled.Enabled = enabled
|
||||
} else if id == "UserEnabled" {
|
||||
app.storage.customEmails.UserEnabled.Enabled = enabled
|
||||
} else if id == "InviteEmail" {
|
||||
app.storage.customEmails.InviteEmail.Enabled = enabled
|
||||
} else if id == "WelcomeEmail" {
|
||||
@ -1480,39 +1556,6 @@ func (app *appContext) SetEmailState(gc *gin.Context) {
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Returns whether there's a new update, and extra info if there is.
|
||||
// @Produce json
|
||||
// @Success 200 {object} checkUpdateDTO
|
||||
// @Router /config/update [get]
|
||||
// @tags Configuration
|
||||
func (app *appContext) CheckUpdate(gc *gin.Context) {
|
||||
if !app.newUpdate {
|
||||
app.update = Update{}
|
||||
}
|
||||
gc.JSON(200, checkUpdateDTO{New: app.newUpdate, Update: app.update})
|
||||
}
|
||||
|
||||
// @Summary Apply an update.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Success 400 {object} stringResponse
|
||||
// @Success 500 {object} boolResponse
|
||||
// @Router /config/update [post]
|
||||
// @tags Configuration
|
||||
func (app *appContext) ApplyUpdate(gc *gin.Context) {
|
||||
if !app.update.CanUpdate {
|
||||
respond(400, "Update is manual", gc)
|
||||
return
|
||||
}
|
||||
err := app.update.update()
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to apply update: %v", err)
|
||||
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.
|
||||
// @Produce json
|
||||
// @Success 200 {object} customEmailDTO
|
||||
@ -1578,8 +1621,32 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
||||
variables = app.storage.customEmails.UserDeleted.Variables
|
||||
}
|
||||
writeVars = func(variables []string) { app.storage.customEmails.UserDeleted.Variables = variables }
|
||||
values = app.email.deletedValues(app.storage.lang.Email[lang].UserDeleted.get("reason"), app, false)
|
||||
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
|
||||
// app.storage.customEmails.UserDeleted = content
|
||||
} else if id == "UserDisabled" {
|
||||
content = app.storage.customEmails.UserDisabled.Content
|
||||
if content == "" {
|
||||
newEmail = true
|
||||
msg, err = app.email.constructDisabled("", app, true)
|
||||
content = msg.Text
|
||||
} else {
|
||||
variables = app.storage.customEmails.UserDisabled.Variables
|
||||
}
|
||||
writeVars = func(variables []string) { app.storage.customEmails.UserDisabled.Variables = variables }
|
||||
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
|
||||
// app.storage.customEmails.UserDeleted = content
|
||||
} else if id == "UserEnabled" {
|
||||
content = app.storage.customEmails.UserEnabled.Content
|
||||
if content == "" {
|
||||
newEmail = true
|
||||
msg, err = app.email.constructEnabled("", app, true)
|
||||
content = msg.Text
|
||||
} else {
|
||||
variables = app.storage.customEmails.UserEnabled.Variables
|
||||
}
|
||||
writeVars = func(variables []string) { app.storage.customEmails.UserEnabled.Variables = variables }
|
||||
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
|
||||
// app.storage.customEmails.UserEnabled = content
|
||||
} else if id == "InviteEmail" {
|
||||
content = app.storage.customEmails.InviteEmail.Content
|
||||
if content == "" {
|
||||
@ -1670,6 +1737,39 @@ func (app *appContext) GetEmail(gc *gin.Context) {
|
||||
gc.JSON(200, customEmailDTO{Content: content, Variables: variables, Values: values, HTML: email.HTML, Plaintext: email.Text})
|
||||
}
|
||||
|
||||
// @Summary Returns whether there's a new update, and extra info if there is.
|
||||
// @Produce json
|
||||
// @Success 200 {object} checkUpdateDTO
|
||||
// @Router /config/update [get]
|
||||
// @tags Configuration
|
||||
func (app *appContext) CheckUpdate(gc *gin.Context) {
|
||||
if !app.newUpdate {
|
||||
app.update = Update{}
|
||||
}
|
||||
gc.JSON(200, checkUpdateDTO{New: app.newUpdate, Update: app.update})
|
||||
}
|
||||
|
||||
// @Summary Apply an update.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Success 400 {object} stringResponse
|
||||
// @Success 500 {object} boolResponse
|
||||
// @Router /config/update [post]
|
||||
// @tags Configuration
|
||||
func (app *appContext) ApplyUpdate(gc *gin.Context) {
|
||||
if !app.update.CanUpdate {
|
||||
respond(400, "Update is manual", gc)
|
||||
return
|
||||
}
|
||||
err := app.update.update()
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to apply update: %v", err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Logout by deleting refresh token from cookies.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
|
@ -64,6 +64,12 @@ func (app *appContext) loadConfig() error {
|
||||
app.MustSetValue("deletion", "email_html", "jfa-go:"+"deleted.html")
|
||||
app.MustSetValue("deletion", "email_text", "jfa-go:"+"deleted.txt")
|
||||
|
||||
// Deletion template is good enough for these as well.
|
||||
app.MustSetValue("disable_enable", "disabled_html", "jfa-go:"+"deleted.html")
|
||||
app.MustSetValue("disable_enable", "disabled_text", "jfa-go:"+"deleted.txt")
|
||||
app.MustSetValue("disable_enable", "enabled_html", "jfa-go:"+"deleted.html")
|
||||
app.MustSetValue("disable_enable", "enabled_text", "jfa-go:"+"deleted.txt")
|
||||
|
||||
app.MustSetValue("welcome_email", "email_html", "jfa-go:"+"welcome.html")
|
||||
app.MustSetValue("welcome_email", "email_text", "jfa-go:"+"welcome.txt")
|
||||
|
||||
|
@ -899,6 +899,68 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"disable_enable": {
|
||||
"order": [],
|
||||
"meta": {
|
||||
"name": "Account Disabling/Enabling",
|
||||
"description": "Subject/email files for account disabling/enabling emails.",
|
||||
"depends_true": "email|method"
|
||||
},
|
||||
"settings": {
|
||||
"subject_disabled": {
|
||||
"name": "Email subject (Disabled)",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Subject of account disabling emails."
|
||||
},
|
||||
"subject_enabled": {
|
||||
"name": "Email subject (Enabled)",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Subject of account enabling emails."
|
||||
},
|
||||
"disabled_html": {
|
||||
"name": "Custom disabling email (HTML)",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"advanced": true,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Path to custom email html"
|
||||
},
|
||||
"disabled_text": {
|
||||
"name": "Custom disabling email (plaintext)",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"advanced": true,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Path to custom email in plain text"
|
||||
},
|
||||
"enabled_html": {
|
||||
"name": "Custom enabling email (HTML)",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"advanced": true,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Path to custom email html"
|
||||
},
|
||||
"enabled_text": {
|
||||
"name": "Custom enabling email (plaintext)",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"advanced": true,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Path to custom email in plain text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deletion": {
|
||||
"order": [],
|
||||
"meta": {
|
||||
|
90
email.go
90
email.go
@ -535,9 +535,9 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub
|
||||
|
||||
func (emailer *Emailer) deletedValues(reason string, app *appContext, noSub bool) map[string]interface{} {
|
||||
template := map[string]interface{}{
|
||||
"yourAccountWasDeleted": emailer.lang.UserDeleted.get("yourAccountWasDeleted"),
|
||||
"reasonString": emailer.lang.UserDeleted.get("reason"),
|
||||
"message": "",
|
||||
"yourAccountWas": emailer.lang.UserDeleted.get("yourAccountWasDeleted"),
|
||||
"reasonString": emailer.lang.Strings.get("reason"),
|
||||
"message": "",
|
||||
}
|
||||
if noSub {
|
||||
empty := []string{"reason"}
|
||||
@ -575,6 +575,90 @@ func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub b
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) disabledValues(reason string, app *appContext, noSub bool) map[string]interface{} {
|
||||
template := map[string]interface{}{
|
||||
"yourAccountWas": emailer.lang.UserDisabled.get("yourAccountWasDisabled"),
|
||||
"reasonString": emailer.lang.Strings.get("reason"),
|
||||
"message": "",
|
||||
}
|
||||
if noSub {
|
||||
empty := []string{"reason"}
|
||||
for _, v := range empty {
|
||||
template[v] = "{" + v + "}"
|
||||
}
|
||||
} else {
|
||||
template["reason"] = reason
|
||||
template["message"] = app.config.Section("email").Key("message").String()
|
||||
}
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructDisabled(reason string, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
Subject: app.config.Section("disable_enable").Key("subject_disabled").MustString(emailer.lang.UserDisabled.get("title")),
|
||||
}
|
||||
var err error
|
||||
template := emailer.disabledValues(reason, app, noSub)
|
||||
if app.storage.customEmails.UserDisabled.Enabled {
|
||||
content := app.storage.customEmails.UserDisabled.Content
|
||||
for _, v := range app.storage.customEmails.UserDisabled.Variables {
|
||||
replaceWith, ok := template[v[1:len(v)-1]]
|
||||
if ok {
|
||||
content = strings.ReplaceAll(content, v, replaceWith.(string))
|
||||
}
|
||||
}
|
||||
email, err = emailer.constructTemplate(email.Subject, content, app)
|
||||
} else {
|
||||
email.HTML, email.Text, err = emailer.construct(app, "disable_enable", "disabled_", template)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) enabledValues(reason string, app *appContext, noSub bool) map[string]interface{} {
|
||||
template := map[string]interface{}{
|
||||
"yourAccountWas": emailer.lang.UserEnabled.get("yourAccountWasEnabled"),
|
||||
"reasonString": emailer.lang.Strings.get("reason"),
|
||||
"message": "",
|
||||
}
|
||||
if noSub {
|
||||
empty := []string{"reason"}
|
||||
for _, v := range empty {
|
||||
template[v] = "{" + v + "}"
|
||||
}
|
||||
} else {
|
||||
template["reason"] = reason
|
||||
template["message"] = app.config.Section("email").Key("message").String()
|
||||
}
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructEnabled(reason string, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
Subject: app.config.Section("disable_enable").Key("subject_enabled").MustString(emailer.lang.UserEnabled.get("title")),
|
||||
}
|
||||
var err error
|
||||
template := emailer.enabledValues(reason, app, noSub)
|
||||
if app.storage.customEmails.UserEnabled.Enabled {
|
||||
content := app.storage.customEmails.UserEnabled.Content
|
||||
for _, v := range app.storage.customEmails.UserEnabled.Variables {
|
||||
replaceWith, ok := template[v[1:len(v)-1]]
|
||||
if ok {
|
||||
content = strings.ReplaceAll(content, v, replaceWith.(string))
|
||||
}
|
||||
}
|
||||
email, err = emailer.constructTemplate(email.Subject, content, app)
|
||||
} else {
|
||||
email.HTML, email.Text, err = emailer.construct(app, "disable_enable", "enabled_", template)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) welcomeValues(username string, expiry time.Time, app *appContext, noSub bool, custom bool) map[string]interface{} {
|
||||
template := map[string]interface{}{
|
||||
"welcome": emailer.lang.WelcomeEmail.get("welcome"),
|
||||
|
@ -471,6 +471,7 @@
|
||||
<span class="col sm button ~info !normal center mb-half" id="accounts-announce">{{ .strings.announce }}</span>
|
||||
<span class="col sm button ~urge !normal center mb-half" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
|
||||
<span class="col sm button ~warning !normal center mb-half" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
|
||||
<span class="col sm button ~positive !normal center mb-half" id="accounts-disable-enable">{{ .strings.disable }}</span>
|
||||
<span class="col sm button ~critical !normal center mb-half" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
2
lang.go
2
lang.go
@ -93,6 +93,8 @@ type emailLang struct {
|
||||
InviteExpiry langSection `json:"inviteExpiry"`
|
||||
PasswordReset langSection `json:"passwordReset"`
|
||||
UserDeleted langSection `json:"userDeleted"`
|
||||
UserDisabled langSection `json:"userDisabled"`
|
||||
UserEnabled langSection `json:"userEnabled"`
|
||||
InviteEmail langSection `json:"inviteEmail"`
|
||||
WelcomeEmail langSection `json:"welcomeEmail"`
|
||||
EmailConfirmation langSection `json:"emailConfirmation"`
|
||||
|
@ -24,6 +24,8 @@
|
||||
"date": "Date",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"reEnable": "Re-enable",
|
||||
"disable": "Disable",
|
||||
"admin": "Admin",
|
||||
"updates": "Updates",
|
||||
"update": "Update",
|
||||
@ -136,6 +138,14 @@
|
||||
"singular": "Delete {n} user",
|
||||
"plural": "Delete {n} users"
|
||||
},
|
||||
"disableUsers": {
|
||||
"singular": "Disable {n} user",
|
||||
"plural": "Disable {n} users"
|
||||
},
|
||||
"reEnableUsers": {
|
||||
"singular": "Re-enable {n} user",
|
||||
"plural": "Re-enable {n} users"
|
||||
},
|
||||
"addUser": {
|
||||
"singular": "Add user",
|
||||
"plural": "Add users"
|
||||
@ -148,6 +158,14 @@
|
||||
"singular": "Deleted {n} user.",
|
||||
"plural": "Deleted {n} users."
|
||||
},
|
||||
"disabledUser": {
|
||||
"singular": "Disabled {n} user.",
|
||||
"plural": "Disabled {n} users."
|
||||
},
|
||||
"enabledUser": {
|
||||
"singular": "Enabled {n} user.",
|
||||
"plural": "Enabled {n} users."
|
||||
},
|
||||
"announceTo": {
|
||||
"singular": "Announce to {n} user",
|
||||
"plural": "Announce to {n} users"
|
||||
|
@ -4,6 +4,7 @@
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Wenn du das nicht warst, ignoriere bitte diese E-Mail.",
|
||||
"reason": "Grund",
|
||||
"helloUser": "Hallo {username},"
|
||||
},
|
||||
"userCreated": {
|
||||
@ -31,7 +32,6 @@
|
||||
"userDeleted": {
|
||||
"title": "Dein Konto wurde gelöscht - Jellyfin",
|
||||
"yourAccountWasDeleted": "Dein Jellyfin-Konto wurde gelöscht.",
|
||||
"reason": "Grund",
|
||||
"name": "Benutzerlöschung"
|
||||
},
|
||||
"inviteEmail": {
|
||||
|
@ -4,6 +4,7 @@
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Αν δεν ήσασταν εσείς, παρακαλώ αγνοήστε αυτό το email.",
|
||||
"reason": "Λόγος",
|
||||
"helloUser": "Γεία σου {username},"
|
||||
},
|
||||
"userCreated": {
|
||||
@ -32,7 +33,6 @@
|
||||
"userDeleted": {
|
||||
"title": "Ο λογαριασμός σας διαγράφηκε - Jellyfin",
|
||||
"yourAccountWasDeleted": "Ο λογαριασμός σας Jellyfin διαγράφηκε.",
|
||||
"reason": "Λόγος",
|
||||
"name": "Διαγραφή χρήστη"
|
||||
},
|
||||
"inviteEmail": {
|
||||
|
@ -4,7 +4,8 @@
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "If this wasn't you, please ignore this email.",
|
||||
"helloUser": "Hi {username},"
|
||||
"helloUser": "Hi {username},",
|
||||
"reason": "Reason"
|
||||
},
|
||||
"userCreated": {
|
||||
"name": "User creation",
|
||||
@ -32,8 +33,17 @@
|
||||
"userDeleted": {
|
||||
"name": "User deletion",
|
||||
"title": "Your account was deleted - Jellyfin",
|
||||
"yourAccountWasDeleted": "Your Jellyfin account was deleted.",
|
||||
"reason": "Reason"
|
||||
"yourAccountWasDeleted": "Your Jellyfin account was deleted."
|
||||
},
|
||||
"userDisabled": {
|
||||
"name": "User disabled",
|
||||
"title": "Your account has been disabled - Jellyfin",
|
||||
"yourAccountWasDisabled": "Your account was disabled."
|
||||
},
|
||||
"userEnabled": {
|
||||
"name": "User enabled",
|
||||
"title": "Your account has been re-enabled - Jellyfin",
|
||||
"yourAccountWasEnabled": "Your account was re-enabled."
|
||||
},
|
||||
"inviteEmail": {
|
||||
"name": "Invite email",
|
||||
|
@ -5,6 +5,7 @@
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Si ce n'était pas toi, tu peux ignorer ce mail.",
|
||||
"reason": "Motif",
|
||||
"helloUser": "Salut {username},"
|
||||
},
|
||||
"userCreated": {
|
||||
@ -32,7 +33,6 @@
|
||||
"userDeleted": {
|
||||
"title": "Ton compte a été désactivé - Jellyfin",
|
||||
"yourAccountWasDeleted": "Ton compte Jellyfin a été supprimé.",
|
||||
"reason": "Motif",
|
||||
"name": "Suppression de l'utilisateur"
|
||||
},
|
||||
"inviteEmail": {
|
||||
|
@ -4,6 +4,7 @@
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Jika ini bukan kamu, silahkan mengabaikan email ini.",
|
||||
"reason": "Alasan",
|
||||
"helloUser": "Halo {username},"
|
||||
},
|
||||
"userCreated": {
|
||||
@ -31,7 +32,6 @@
|
||||
"userDeleted": {
|
||||
"title": "Akun anda telah dihapus - Jellyfin",
|
||||
"yourAccountWasDeleted": "Akun Jellyfin anda telah dihapus.",
|
||||
"reason": "Alasan",
|
||||
"name": "Penghapusan pengguna"
|
||||
},
|
||||
"inviteEmail": {
|
||||
|
@ -4,7 +4,8 @@
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Se non sei stato tu, puoi ignorare questa email.",
|
||||
"helloUser": "Ciao {username},"
|
||||
"helloUser": "Ciao {username},",
|
||||
"reason": "Motivo"
|
||||
},
|
||||
"userCreated": {
|
||||
"title": "Nota: Utente creato",
|
||||
@ -27,8 +28,7 @@
|
||||
},
|
||||
"userDeleted": {
|
||||
"title": "Il tuo account è stato eliminato - Jellyfin",
|
||||
"yourAccountWasDeleted": "Il tuo account di Jellyfin è stato eliminato.",
|
||||
"reason": "Motivo"
|
||||
"yourAccountWasDeleted": "Il tuo account di Jellyfin è stato eliminato."
|
||||
},
|
||||
"inviteEmail": {
|
||||
"title": "Invita - Jellyfin",
|
||||
|
@ -4,6 +4,7 @@
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Als jij dit niet was, negeer dan alsjeblieft deze email.",
|
||||
"reason": "Reden",
|
||||
"helloUser": "Hoi {username},"
|
||||
},
|
||||
"userCreated": {
|
||||
@ -32,7 +33,6 @@
|
||||
"userDeleted": {
|
||||
"title": "Je account is verwijderd - Jellyfin",
|
||||
"yourAccountWasDeleted": "Je Jellyfin account is verwijderd.",
|
||||
"reason": "Reden",
|
||||
"name": "Gebruiker verwijderd"
|
||||
},
|
||||
"inviteEmail": {
|
||||
|
@ -4,6 +4,7 @@
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Se não foi você, ignore este e-mail.",
|
||||
"reason": "Razão",
|
||||
"helloUser": "Ola {username},"
|
||||
},
|
||||
"userCreated": {
|
||||
@ -32,7 +33,6 @@
|
||||
"userDeleted": {
|
||||
"title": "Sua conta foi excluída - Jellyfin",
|
||||
"yourAccountWasDeleted": "Sua conta Jellyfin foi excluída.",
|
||||
"reason": "Razão",
|
||||
"name": "Exclusão do usuário"
|
||||
},
|
||||
"inviteEmail": {
|
||||
|
@ -4,7 +4,8 @@
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Om detta inte var du, ignorera det här e-postmeddelandet.",
|
||||
"helloUser": "Hej {användarnamn},"
|
||||
"helloUser": "Hej {username},",
|
||||
"reason": "Anledning"
|
||||
},
|
||||
"userCreated": {
|
||||
"name": "Användarskapande",
|
||||
@ -31,8 +32,7 @@
|
||||
"userDeleted": {
|
||||
"name": "Radering av användare",
|
||||
"title": "Ditt konto raderades - Jellyfin",
|
||||
"yourAccountWasDeleted": "Ditt Jellyfin-konto raderades.",
|
||||
"reason": "Anledning"
|
||||
"yourAccountWasDeleted": "Ditt Jellyfin-konto raderades."
|
||||
},
|
||||
"inviteEmail": {
|
||||
"name": "Inbjudnings e-post",
|
||||
|
@ -60,7 +60,7 @@
|
||||
<mj-section mj-class="bg">
|
||||
<mj-column>
|
||||
<mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif">
|
||||
<h3>{{ .yourAccountWasDeleted }}</h3>
|
||||
<h3>{{ .yourAccountWas }}</h3>
|
||||
<p>{{ .reasonString }}: <i>{{ .reason }}</i></p>
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{ .yourAccountWasDeleted }}
|
||||
{{ .yourAccountWas }}
|
||||
|
||||
{{ .reasonString }}: {{ .reason }}
|
||||
|
||||
|
@ -29,6 +29,13 @@ type deleteUserDTO struct {
|
||||
Reason string `json:"reason"` // Account deletion reason (for notification)
|
||||
}
|
||||
|
||||
type enableDisableUserDTO struct {
|
||||
Users []string `json:"users" binding:"required"` // List of usernames to delete
|
||||
Enabled bool `json:"enabled"` // True = enable users, False = disable.
|
||||
Notify bool `json:"notify"` // Whether to notify users of deletion
|
||||
Reason string `json:"reason"` // Account deletion reason (for notification)
|
||||
}
|
||||
|
||||
type generateInviteDTO struct {
|
||||
Months int `json:"months" example:"0"` // Number of months
|
||||
Days int `json:"days" example:"1"` // Number of days
|
||||
|
@ -132,6 +132,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||
api.GET(p+"/users", app.GetUsers)
|
||||
api.POST(p+"/users", app.NewUserAdmin)
|
||||
api.POST(p+"/users/extend", app.ExtendExpiry)
|
||||
api.POST(p+"/users/enable", app.EnableDisableUsers)
|
||||
api.POST(p+"/invites", app.GenerateInvite)
|
||||
api.GET(p+"/invites", app.GetInvites)
|
||||
api.DELETE(p+"/invites", app.DeleteInvite)
|
||||
|
@ -34,6 +34,8 @@ type customEmails struct {
|
||||
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"`
|
||||
@ -431,10 +433,13 @@ func (st *Storage) loadLangEmail(filesystems ...fs.FS) error {
|
||||
patchLang(&english.InviteExpiry, &lang.InviteExpiry)
|
||||
patchLang(&english.PasswordReset, &lang.PasswordReset)
|
||||
patchLang(&english.UserDeleted, &lang.UserDeleted)
|
||||
patchLang(&english.UserDisabled, &lang.UserDisabled)
|
||||
patchLang(&english.UserEnabled, &lang.UserEnabled)
|
||||
patchLang(&english.InviteEmail, &lang.InviteEmail)
|
||||
patchLang(&english.WelcomeEmail, &lang.WelcomeEmail)
|
||||
patchLang(&english.EmailConfirmation, &lang.EmailConfirmation)
|
||||
patchLang(&english.UserExpired, &lang.UserExpired)
|
||||
patchLang(&english.Strings, &lang.Strings)
|
||||
}
|
||||
st.lang.Email[index] = lang
|
||||
return nil
|
||||
|
@ -194,6 +194,7 @@ export class accountsList {
|
||||
private _addUserButton = document.getElementById("accounts-add-user") as HTMLSpanElement;
|
||||
private _announceButton = document.getElementById("accounts-announce") as HTMLSpanElement;
|
||||
private _deleteUser = document.getElementById("accounts-delete-user") as HTMLSpanElement;
|
||||
private _disableEnable = document.getElementById("accounts-disable-enable") as HTMLSpanElement;
|
||||
private _deleteNotify = document.getElementById("delete-user-notify") as HTMLInputElement;
|
||||
private _deleteReason = document.getElementById("textarea-delete-user") as HTMLTextAreaElement;
|
||||
private _extendExpiry = document.getElementById("accounts-extend-expiry") as HTMLSpanElement;
|
||||
@ -209,6 +210,8 @@ export class accountsList {
|
||||
private _sortedByName: string[] = [];
|
||||
private _checkCount: number = 0;
|
||||
private _inSearch = false;
|
||||
// Whether the enable/disable button should enable or not.
|
||||
private _shouldEnable = false;
|
||||
|
||||
private _addUserForm = document.getElementById("form-add-user") as HTMLFormElement;
|
||||
private _addUserName = this._addUserForm.querySelector("input[type=text]") as HTMLInputElement;
|
||||
@ -329,6 +332,7 @@ export class accountsList {
|
||||
this._announceButton.classList.add("unfocused");
|
||||
}
|
||||
this._extendExpiry.classList.add("unfocused");
|
||||
this._disableEnable.classList.add("unfocused");
|
||||
} else {
|
||||
let visibleCount = 0;
|
||||
for (let id in this._users) {
|
||||
@ -350,16 +354,37 @@ export class accountsList {
|
||||
this._announceButton.classList.remove("unfocused");
|
||||
}
|
||||
let anyNonExpiries = list.length == 0 ? true : false;
|
||||
// Only show enable/disable button if all selected have the same state.
|
||||
this._shouldEnable = this._users[list[0]].disabled
|
||||
let showDisableEnable = true;
|
||||
for (let id of list) {
|
||||
if (!this._users[id].expiry) {
|
||||
if (!anyNonExpiries && !this._users[id].expiry) {
|
||||
anyNonExpiries = true;
|
||||
this._extendExpiry.classList.add("unfocused");
|
||||
break;
|
||||
}
|
||||
if (showDisableEnable && this._users[id].disabled != this._shouldEnable) {
|
||||
showDisableEnable = false;
|
||||
this._disableEnable.classList.add("unfocused");
|
||||
}
|
||||
if (!showDisableEnable && anyNonExpiries) { break; }
|
||||
}
|
||||
if (!anyNonExpiries) {
|
||||
this._extendExpiry.classList.remove("unfocused");
|
||||
}
|
||||
if (showDisableEnable) {
|
||||
let message: string;
|
||||
if (this._shouldEnable) {
|
||||
message = window.lang.strings("reEnable");
|
||||
this._disableEnable.classList.add("~positive");
|
||||
this._disableEnable.classList.remove("~warning");
|
||||
} else {
|
||||
message = window.lang.strings("disable");
|
||||
this._disableEnable.classList.add("~warning");
|
||||
this._disableEnable.classList.remove("~positive");
|
||||
}
|
||||
this._disableEnable.classList.remove("unfocused");
|
||||
this._disableEnable.textContent = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,12 +466,66 @@ export class accountsList {
|
||||
window.modals.announce.show();
|
||||
}
|
||||
|
||||
deleteUsers = () => {
|
||||
enableDisableUsers = () => {
|
||||
// We can share the delete modal for this
|
||||
const modalHeader = document.getElementById("header-delete-user");
|
||||
modalHeader.textContent = window.lang.quantity("deleteNUsers", this._collectUsers().length);
|
||||
let list = this._collectUsers();
|
||||
const form = document.getElementById("form-delete-user") as HTMLFormElement;
|
||||
const button = form.querySelector("span.submit") as HTMLSpanElement;
|
||||
let list = this._collectUsers();
|
||||
if (this._shouldEnable) {
|
||||
modalHeader.textContent = window.lang.quantity("reEnableUsers", list.length);
|
||||
button.textContent = window.lang.strings("reEnable");
|
||||
button.classList.add("~urge");
|
||||
button.classList.remove("~critical");
|
||||
} else {
|
||||
modalHeader.textContent = window.lang.quantity("disableUsers", list.length);
|
||||
button.textContent = window.lang.strings("disable");
|
||||
button.classList.add("~critical");
|
||||
button.classList.remove("~urge");
|
||||
}
|
||||
this._deleteNotify.checked = false;
|
||||
this._deleteReason.value = "";
|
||||
this._deleteReason.classList.add("unfocused");
|
||||
form.onsubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
toggleLoader(button);
|
||||
let send = {
|
||||
"users": list,
|
||||
"enabled": this._shouldEnable,
|
||||
"notify": this._deleteNotify.checked,
|
||||
"reason": this._deleteNotify ? this._deleteReason.value : ""
|
||||
};
|
||||
_post("/users/enable", send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(button);
|
||||
window.modals.deleteUser.close();
|
||||
if (req.status != 200 && req.status != 204) {
|
||||
let errorMsg = window.lang.notif("errorFailureCheckLogs");
|
||||
if (!("error" in req.response)) {
|
||||
errorMsg = window.lang.notif("errorPartialFailureCheckLogs");
|
||||
}
|
||||
window.notifications.customError("deleteUserError", errorMsg);
|
||||
} else if (this._shouldEnable) {
|
||||
window.notifications.customSuccess("enableUserSuccess", window.lang.quantity("enabledUser", list.length));
|
||||
} else {
|
||||
window.notifications.customSuccess("disableUserSuccess", window.lang.quantity("disabledUser", list.length));
|
||||
}
|
||||
this.reload();
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
window.modals.deleteUser.show();
|
||||
}
|
||||
|
||||
deleteUsers = () => {
|
||||
const modalHeader = document.getElementById("header-delete-user");
|
||||
let list = this._collectUsers();
|
||||
modalHeader.textContent = window.lang.quantity("deleteNUsers", list.length);
|
||||
const form = document.getElementById("form-delete-user") as HTMLFormElement;
|
||||
const button = form.querySelector("span.submit") as HTMLSpanElement;
|
||||
button.textContent = window.lang.strings("delete");
|
||||
button.classList.add("~critical");
|
||||
button.classList.remove("~urge");
|
||||
this._deleteNotify.checked = false;
|
||||
this._deleteReason.value = "";
|
||||
this._deleteReason.classList.add("unfocused");
|
||||
@ -469,7 +548,7 @@ export class accountsList {
|
||||
}
|
||||
window.notifications.customError("deleteUserError", errorMsg);
|
||||
} else {
|
||||
window.notifications.customSuccess("deleteUserSuccess", window.lang.quantity("deletedUser", this._collectUsers().length));
|
||||
window.notifications.customSuccess("deleteUserSuccess", window.lang.quantity("deletedUser", list.length));
|
||||
}
|
||||
this.reload();
|
||||
}
|
||||
@ -630,6 +709,9 @@ export class accountsList {
|
||||
this._extendExpiry.onclick = this.extendExpiry;
|
||||
this._extendExpiry.classList.add("unfocused");
|
||||
|
||||
this._disableEnable.onclick = this.enableDisableUsers;
|
||||
this._disableEnable.classList.add("unfocused");
|
||||
|
||||
if (!window.usernameEnabled) {
|
||||
this._addUserName.classList.add("unfocused");
|
||||
this._addUserName = this._addUserEmail;
|
||||
|
@ -793,6 +793,7 @@ export class createInvite {
|
||||
this._invDurationButton.onchange = checkDuration;
|
||||
|
||||
this._days.onchange = this._checkDurationValidity;
|
||||
this._months.onchange = this._checkDurationValidity;
|
||||
this._hours.onchange = this._checkDurationValidity;
|
||||
this._minutes.onchange = this._checkDurationValidity;
|
||||
document.addEventListener("profileLoadEvent", () => { this.loadProfiles(); }, false);
|
||||
|
Loading…
Reference in New Issue
Block a user