1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-26 19:10:10 +00:00

Compare commits

...

3 Commits

Author SHA1 Message Date
2a6937228c
accounts: make ombi/jellyseerr appliction optional on "modify settings"
Checkboxes added when applying from a profile.
2024-07-30 21:36:06 +01:00
785395dd20
disable request logging 2024-07-30 20:56:48 +01:00
385953b0cb
jellyseerr: fix email setting, cover all contact adjustment areas
hopefully all places where contact methods can be adjusted should sync
with jellyseerr.
2024-07-30 20:55:45 +01:00
12 changed files with 219 additions and 70 deletions

View File

@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/jellyseerr"
"github.com/lithammer/shortuuid/v3" "github.com/lithammer/shortuuid/v3"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
@ -322,6 +323,14 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
tgUser.Lang = lang tgUser.Lang = lang
} }
app.storage.SetTelegramKey(req.ID, tgUser) app.storage.SetTelegramKey(req.ID, tgUser)
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
jellyseerr.FieldTelegram: tgUser.ChatID,
jellyseerr.FieldTelegramEnabled: tgUser.Contact,
}); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err)
}
linkExistingOmbiDiscordTelegram(app) linkExistingOmbiDiscordTelegram(app)
respondBool(200, true, gc) respondBool(200, true, gc)
} }
@ -346,6 +355,7 @@ func (app *appContext) SetContactMethods(gc *gin.Context) {
} }
func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Context) { func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Context) {
jsPrefs := map[jellyseerr.NotificationsField]any{}
if tgUser, ok := app.storage.GetTelegramKey(req.ID); ok { if tgUser, ok := app.storage.GetTelegramKey(req.ID); ok {
change := tgUser.Contact != req.Telegram change := tgUser.Contact != req.Telegram
tgUser.Contact = req.Telegram tgUser.Contact = req.Telegram
@ -356,6 +366,7 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
msg = " not" msg = " not"
} }
app.debug.Printf("Telegram: User \"%s\" will%s be notified through Telegram.", tgUser.Username, msg) app.debug.Printf("Telegram: User \"%s\" will%s be notified through Telegram.", tgUser.Username, msg)
jsPrefs[jellyseerr.FieldTelegramEnabled] = req.Telegram
} }
} }
if dcUser, ok := app.storage.GetDiscordKey(req.ID); ok { if dcUser, ok := app.storage.GetDiscordKey(req.ID); ok {
@ -368,6 +379,7 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
msg = " not" msg = " not"
} }
app.debug.Printf("Discord: User \"%s\" will%s be notified through Discord.", dcUser.Username, msg) app.debug.Printf("Discord: User \"%s\" will%s be notified through Discord.", dcUser.Username, msg)
jsPrefs[jellyseerr.FieldDiscordEnabled] = req.Discord
} }
} }
if mxUser, ok := app.storage.GetMatrixKey(req.ID); ok { if mxUser, ok := app.storage.GetMatrixKey(req.ID); ok {
@ -392,6 +404,13 @@ func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Conte
msg = " not" msg = " not"
} }
app.debug.Printf("\"%s\" will%s be notified via Email.", email.Addr, msg) app.debug.Printf("\"%s\" will%s be notified via Email.", email.Addr, msg)
jsPrefs[jellyseerr.FieldEmailEnabled] = req.Email
}
}
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
err := app.js.ModifyNotifications(req.ID, jsPrefs)
if err != nil {
app.err.Printf("Failed to sync contact prefs with Jellyseerr: %v", err)
} }
} }
respondBool(200, true, gc) respondBool(200, true, gc)
@ -678,6 +697,13 @@ func (app *appContext) DiscordConnect(gc *gin.Context) {
app.storage.SetDiscordKey(req.JellyfinID, user) app.storage.SetDiscordKey(req.JellyfinID, user)
if err := app.js.ModifyNotifications(req.JellyfinID, map[jellyseerr.NotificationsField]any{
jellyseerr.FieldDiscord: req.DiscordID,
jellyseerr.FieldDiscordEnabled: true,
}); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err)
}
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityContactLinked, Type: ActivityContactLinked,
UserID: req.JellyfinID, UserID: req.JellyfinID,
@ -708,6 +734,14 @@ func (app *appContext) UnlinkDiscord(gc *gin.Context) {
} */ } */
app.storage.DeleteDiscordKey(req.ID) app.storage.DeleteDiscordKey(req.ID)
// May not actually remove Discord ID, but should disable interaction.
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
jellyseerr.FieldDiscord: jellyseerr.BogusIdentifier,
jellyseerr.FieldDiscordEnabled: false,
}); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err)
}
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityContactUnlinked, Type: ActivityContactUnlinked,
UserID: req.ID, UserID: req.ID,
@ -737,6 +771,13 @@ func (app *appContext) UnlinkTelegram(gc *gin.Context) {
} */ } */
app.storage.DeleteTelegramKey(req.ID) app.storage.DeleteTelegramKey(req.ID)
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
jellyseerr.FieldTelegram: jellyseerr.BogusIdentifier,
jellyseerr.FieldTelegramEnabled: false,
}); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err)
}
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityContactUnlinked, Type: ActivityContactUnlinked,
UserID: req.ID, UserID: req.ID,

View File

@ -8,6 +8,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/hrfee/jfa-go/jellyseerr"
"github.com/lithammer/shortuuid/v3" "github.com/lithammer/shortuuid/v3"
"github.com/timshannon/badgerhold/v4" "github.com/timshannon/badgerhold/v4"
) )
@ -200,14 +201,7 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
gc.Redirect(http.StatusSeeOther, "/my/account") gc.Redirect(http.StatusSeeOther, "/my/account")
return return
} else if target == UserEmailChange { } else if target == UserEmailChange {
emailStore, ok := app.storage.GetEmailsKey(id) app.modifyEmail(id, claims["email"].(string))
if !ok {
emailStore = EmailAddress{
Contact: true,
}
}
emailStore.Addr = claims["email"].(string)
app.storage.SetEmailsKey(id, emailStore)
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityContactLinked, Type: ActivityContactLinked,
@ -218,17 +212,6 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
Time: time.Now(), Time: time.Now(),
}, gc, true) }, gc, true)
if app.config.Section("ombi").Key("enabled").MustBool(false) {
ombiUser, code, err := app.getOmbiUser(id)
if code == 200 && err == nil {
ombiUser["emailAddress"] = claims["email"].(string)
code, err = app.ombi.ModifyUser(ombiUser)
if code != 200 || err != nil {
app.err.Printf("%s: Failed to change ombi email address (%d): %v", ombiUser["userName"].(string), code, err)
}
}
}
app.info.Println("Email list modified") app.info.Println("Email list modified")
gc.Redirect(http.StatusSeeOther, "/my/account") gc.Redirect(http.StatusSeeOther, "/my/account")
return return
@ -371,6 +354,13 @@ func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) {
} }
app.storage.SetDiscordKey(gc.GetString("jfId"), dcUser) app.storage.SetDiscordKey(gc.GetString("jfId"), dcUser)
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
jellyseerr.FieldDiscord: dcUser.ID,
jellyseerr.FieldDiscordEnabled: dcUser.Contact,
}); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err)
}
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityContactLinked, Type: ActivityContactLinked,
UserID: gc.GetString("jfId"), UserID: gc.GetString("jfId"),
@ -419,6 +409,13 @@ func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) {
} }
app.storage.SetTelegramKey(gc.GetString("jfId"), tgUser) app.storage.SetTelegramKey(gc.GetString("jfId"), tgUser)
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
jellyseerr.FieldTelegram: tgUser.ChatID,
jellyseerr.FieldTelegramEnabled: tgUser.Contact,
}); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err)
}
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityContactLinked, Type: ActivityContactLinked,
UserID: gc.GetString("jfId"), UserID: gc.GetString("jfId"),
@ -522,6 +519,13 @@ func (app *appContext) MatrixCheckMyPIN(gc *gin.Context) {
func (app *appContext) UnlinkMyDiscord(gc *gin.Context) { func (app *appContext) UnlinkMyDiscord(gc *gin.Context) {
app.storage.DeleteDiscordKey(gc.GetString("jfId")) app.storage.DeleteDiscordKey(gc.GetString("jfId"))
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
jellyseerr.FieldDiscord: jellyseerr.BogusIdentifier,
jellyseerr.FieldDiscordEnabled: false,
}); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err)
}
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityContactUnlinked, Type: ActivityContactUnlinked,
UserID: gc.GetString("jfId"), UserID: gc.GetString("jfId"),
@ -543,6 +547,13 @@ func (app *appContext) UnlinkMyDiscord(gc *gin.Context) {
func (app *appContext) UnlinkMyTelegram(gc *gin.Context) { func (app *appContext) UnlinkMyTelegram(gc *gin.Context) {
app.storage.DeleteTelegramKey(gc.GetString("jfId")) app.storage.DeleteTelegramKey(gc.GetString("jfId"))
if err := app.js.ModifyNotifications(gc.GetString("jfId"), map[jellyseerr.NotificationsField]any{
jellyseerr.FieldTelegram: jellyseerr.BogusIdentifier,
jellyseerr.FieldTelegramEnabled: false,
}); err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err)
}
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityContactUnlinked, Type: ActivityContactUnlinked,
UserID: gc.GetString("jfId"), UserID: gc.GetString("jfId"),

View File

@ -516,7 +516,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
} }
contactMethods := map[jellyseerr.NotificationsField]any{} contactMethods := map[jellyseerr.NotificationsField]any{}
if emailEnabled { if emailEnabled {
err = app.js.ModifyUser(id, map[jellyseerr.UserField]any{jellyseerr.FieldEmail: req.Email}) err = app.js.ModifyMainUserSettings(id, jellyseerr.MainUserSettings{Email: req.Email})
if err != nil { if err != nil {
app.err.Printf("Failed to set Jellyseerr email address: %v\n", err) app.err.Printf("Failed to set Jellyseerr email address: %v\n", err)
} else { } else {
@ -1258,6 +1258,44 @@ func (app *appContext) ModifyLabels(gc *gin.Context) {
respondBool(204, true, gc) respondBool(204, true, gc)
} }
func (app *appContext) modifyEmail(jfID string, addr string) {
contactPrefChanged := false
emailStore, ok := app.storage.GetEmailsKey(jfID)
// Auto enable contact by email for newly added addresses
if !ok || emailStore.Addr == "" {
emailStore = EmailAddress{
Contact: true,
}
contactPrefChanged = true
}
emailStore.Addr = addr
app.storage.SetEmailsKey(jfID, emailStore)
if app.config.Section("ombi").Key("enabled").MustBool(false) {
ombiUser, code, err := app.getOmbiUser(jfID)
if code == 200 && err == nil {
ombiUser["emailAddress"] = addr
code, err = app.ombi.ModifyUser(ombiUser)
if code != 200 || err != nil {
app.err.Printf("%s: Failed to change ombi email address (%d): %v", ombiUser["userName"].(string), code, err)
}
}
}
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
err := app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: addr})
if err != nil {
app.err.Printf("Failed to set Jellyseerr email address: %v\n", err)
} else if contactPrefChanged {
contactMethods := map[jellyseerr.NotificationsField]any{
jellyseerr.FieldEmailEnabled: true,
}
err := app.js.ModifyNotifications(jfID, contactMethods)
if err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err)
}
}
}
}
// @Summary Modify user's email addresses. // @Summary Modify user's email addresses.
// @Produce json // @Produce json
// @Param modifyEmailsDTO body modifyEmailsDTO true "Map of userIDs to email addresses" // @Param modifyEmailsDTO body modifyEmailsDTO true "Map of userIDs to email addresses"
@ -1276,22 +1314,10 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
} }
ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false)
for _, jfUser := range users { for _, jfUser := range users {
id := jfUser.ID id := jfUser.ID
if address, ok := req[id]; ok { if address, ok := req[id]; ok {
var emailStore = EmailAddress{} app.modifyEmail(id, address)
oldEmail, ok := app.storage.GetEmailsKey(id)
if ok {
emailStore = oldEmail
}
// Auto enable contact by email for newly added addresses
if !ok || oldEmail.Addr == "" {
emailStore.Contact = true
}
emailStore.Addr = address
app.storage.SetEmailsKey(id, emailStore)
activityType := ActivityContactLinked activityType := ActivityContactLinked
if address == "" { if address == "" {
@ -1305,17 +1331,6 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
Value: "email", Value: "email",
Time: time.Now(), Time: time.Now(),
}, gc, false) }, gc, false)
if ombiEnabled {
ombiUser, code, err := app.getOmbiUser(id)
if code == 200 && err == nil {
ombiUser["emailAddress"] = address
code, err = app.ombi.ModifyUser(ombiUser)
if code != 200 || err != nil {
app.err.Printf("%s: Failed to change ombi email address (%d): %v", ombiUser["userName"].(string), code, err)
}
}
}
} }
} }
app.info.Println("Email list modified") app.info.Println("Email list modified")
@ -1359,12 +1374,12 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
displayprefs = profile.Displayprefs displayprefs = profile.Displayprefs
} }
policy = profile.Policy policy = profile.Policy
if app.config.Section("ombi").Key("enabled").MustBool(false) { if req.Ombi && app.config.Section("ombi").Key("enabled").MustBool(false) {
if profile.Ombi != nil && len(profile.Ombi) != 0 { if profile.Ombi != nil && len(profile.Ombi) != 0 {
ombi = profile.Ombi ombi = profile.Ombi
} }
} }
if app.config.Section("jellyseerr").Key("enabled").MustBool(false) { if req.Jellyseerr && app.config.Section("jellyseerr").Key("enabled").MustBool(false) {
if profile.Jellyseerr.Enabled { if profile.Jellyseerr.Enabled {
jellyseerr = profile.Jellyseerr jellyseerr = profile.Jellyseerr
} }

View File

@ -1595,6 +1595,14 @@
"value": false, "value": false,
"description": "Enable the Jellyseerr integration." "description": "Enable the Jellyseerr integration."
}, },
"usertype_note": {
"name": "Password Changes:",
"type": "note",
"value": "",
"depends_true": "enabled",
"required": "false",
"description": "Ensure existing users on Jellyseerr are \"Jellyfin User\"s not \"Local User\"s, as password changes are not synced with Jellyseerr."
},
"server": { "server": {
"name": "URL", "name": "URL",
"required": false, "required": false,

View File

@ -84,30 +84,40 @@
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-modify-user" href=""> <form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-modify-user" href="">
<span class="heading"><span id="header-modify-user"></span> <span class="modal-close">&times;</span></span> <span class="heading"><span id="header-modify-user"></span> <span class="modal-close">&times;</span></span>
<p class="content my-4">{{ .strings.modifySettingsDescription }}</p> <p class="content my-4">{{ .strings.modifySettingsDescription }}</p>
<div class="flex flex-row mb-4"> <div class="flex flex-col gap-4 my-2">
<label class="grow mr-2"> <div class="flex flex-row gap-2">
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-profile" checked> <label class="grow">
<span class="button ~neutral @high supra full-width center">{{ .strings.profile }}</span> <input type="radio" name="modify-user-source" class="unfocused" id="radio-use-profile" checked>
<span class="button ~neutral @high supra full-width center">{{ .strings.profile }}</span>
</label>
<label class="grow">
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-user">
<span class="button ~neutral @low supra full-width center">{{ .strings.user }}</span>
</label>
</div>
<div class="select ~neutral @low">
<select id="modify-user-profiles"></select>
</div>
<div class="select ~neutral @low unfocused">
<select id="modify-user-users"></select>
</div>
<label class="switch">
<input type="checkbox" id="modify-user-homescreen" checked>
<span>{{ .strings.applyHomescreenLayout }}</span>
</label> </label>
<label class="grow ml-2"> <label class="switch">
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-user"> <input type="checkbox" id="modify-user-ombi" checked>
<span class="button ~neutral @low supra full-width center">{{ .strings.user }}</span> <span>{{ .strings.applyOmbi }}</span>
</label>
<label class="switch">
<input type="checkbox" id="modify-user-jellyseerr" checked>
<span>{{ .strings.applyJellyseerr }}</span>
</label>
<label>
<input type="submit" class="unfocused">
<span class="button ~urge @low full-width center supra submit">{{ .strings.apply }}</span>
</label> </label>
</div> </div>
<div class="select ~neutral @low mb-4">
<select id="modify-user-profiles"></select>
</div>
<div class="select ~neutral @low mb-4 unfocused">
<select id="modify-user-users"></select>
</div>
<label class="switch mb-4">
<input type="checkbox" id="modify-user-homescreen" checked>
<span>{{ .strings.applyHomescreenLayout }}</span>
</label>
<label>
<input type="submit" class="unfocused">
<span class="button ~urge @low full-width center supra submit">{{ .strings.apply }}</span>
</label>
</form> </form>
</div> </div>
{{ if .referralsEnabled }} {{ if .referralsEnabled }}

View File

@ -16,7 +16,8 @@ import (
) )
const ( const (
API_SUFFIX = "/api/v1" API_SUFFIX = "/api/v1"
BogusIdentifier = "123412341234123456"
) )
// Jellyseerr represents a running Jellyseerr instance. // Jellyseerr represents a running Jellyseerr instance.
@ -300,6 +301,9 @@ func (js *Jellyseerr) ApplyTemplateToUser(jfID string, tmpl UserTemplate) error
} }
func (js *Jellyseerr) ModifyUser(jfID string, conf map[UserField]any) error { func (js *Jellyseerr) ModifyUser(jfID string, conf map[UserField]any) error {
if _, ok := conf[FieldEmail]; ok {
return fmt.Errorf("email is read only, set with ModifyMainUserSettings instead")
}
u, err := js.MustGetUser(jfID) u, err := js.MustGetUser(jfID)
if err != nil { if err != nil {
return err return err
@ -408,3 +412,21 @@ func (js *Jellyseerr) UserByID(jellyseerrID int64) (User, error) {
err = json.Unmarshal([]byte(resp), &data) err = json.Unmarshal([]byte(resp), &data)
return data, err return data, err
} }
func (js *Jellyseerr) ModifyMainUserSettings(jfID string, conf MainUserSettings) error {
u, err := js.MustGetUser(jfID)
if err != nil {
return err
}
_, status, err := js.put(fmt.Sprintf(js.server+"/user/%d/settings/main", u.ID), conf, false)
if err != nil {
return err
}
if status != 200 && status != 201 {
return fmt.Errorf("failed (error %d)", status)
}
// Lazily just invalidate the cache.
js.cacheExpiry = time.Now()
return nil
}

View File

@ -115,3 +115,18 @@ type NotificationsTemplate struct {
WebPushEnabled bool `json:"webPushEnabled,omitempty"` WebPushEnabled bool `json:"webPushEnabled,omitempty"`
NotifTypes NotificationTypes `json:"notificationTypes"` NotifTypes NotificationTypes `json:"notificationTypes"`
} }
type MainUserSettings struct {
Username string `json:"username,omitempty"`
Email string `json:"email,omitempty"`
DiscordID string `json:"discordId,omitempty"`
Locale string `json:"locale,omitempty"`
Region string `json:"region,omitempty"`
OriginalLanguage any `json:"originalLanguage,omitempty"`
MovieQuotaLimit any `json:"movieQuotaLimit,omitempty"`
MovieQuotaDays any `json:"movieQuotaDays,omitempty"`
TvQuotaLimit any `json:"tvQuotaLimit,omitempty"`
TvQuotaDays any `json:"tvQuotaDays,omitempty"`
WatchlistSyncMovies any `json:"watchlistSyncMovies,omitempty"`
WatchlistSyncTv any `json:"watchlistSyncTv,omitempty"`
}

View File

@ -81,6 +81,8 @@
"useInviteExpiry": "Set expiry from profile/invite", "useInviteExpiry": "Set expiry from profile/invite",
"useInviteExpiryNote": "By default, invites expire after 90 days but can be renewed by the user. Enable for the referral to be disabled after the time set.", "useInviteExpiryNote": "By default, invites expire after 90 days but can be renewed by the user. Enable for the referral to be disabled after the time set.",
"applyHomescreenLayout": "Apply homescreen layout", "applyHomescreenLayout": "Apply homescreen layout",
"applyOmbi": "Apply Ombi profile (if available)",
"applyJellyseerr": "Apply Jellyseerr profile (if available)",
"sendDeleteNotificationEmail": "Send notification message", "sendDeleteNotificationEmail": "Send notification message",
"sendDeleteNotifiationExample": "Your account has been deleted.", "sendDeleteNotifiationExample": "Your account has been deleted.",
"settingsRestart": "Restart", "settingsRestart": "Restart",

View File

@ -369,7 +369,7 @@ func start(asDaemon, firstCall bool) {
app.config.Section("jellyseerr").Key("api_key").String(), app.config.Section("jellyseerr").Key("api_key").String(),
common.NewTimeoutHandler("Jellyseerr", jellyseerrServer, true), common.NewTimeoutHandler("Jellyseerr", jellyseerrServer, true),
) )
app.js.LogRequestBodies = true // app.js.LogRequestBodies = true
} }

View File

@ -179,6 +179,8 @@ type userSettingsDTO struct {
ApplyTo []string `json:"apply_to"` // Users to apply settings to ApplyTo []string `json:"apply_to"` // Users to apply settings to
ID string `json:"id"` // ID of user (if from = "user") ID string `json:"id"` // ID of user (if from = "user")
Homescreen bool `json:"homescreen"` // Whether to apply homescreen layout or not Homescreen bool `json:"homescreen"` // Whether to apply homescreen layout or not
Ombi bool `json:"ombi"` // Whether to apply ombi profile or not
Jellyseerr bool `json:"jellyseerr"` // Whether to apply jellyseerr profile or not
} }
type announcementDTO struct { type announcementDTO struct {

View File

@ -187,6 +187,7 @@ login.onLogin = () => {
console.log("Logged in."); console.log("Logged in.");
window.updater = new Updater(); window.updater = new Updater();
// FIXME: Decide whether to autoload activity or not // FIXME: Decide whether to autoload activity or not
window.invites.reload()
setInterval(() => { window.invites.reload(); accounts.reload(); }, 30*1000); setInterval(() => { window.invites.reload(); accounts.reload(); }, 30*1000);
const currentTab = window.tabs.current; const currentTab = window.tabs.current;
switch (currentTab) { switch (currentTab) {

View File

@ -795,6 +795,10 @@ export class accountsList {
private _searchBox = document.getElementById("accounts-search") as HTMLInputElement; private _searchBox = document.getElementById("accounts-search") as HTMLInputElement;
private _search: Search; private _search: Search;
private _applyHomesreen = document.getElementById("modify-user-homescreen") as HTMLInputElement;
private _applyOmbi = document.getElementById("modify-user-ombi") as HTMLInputElement;
private _applyJellyseerr = document.getElementById("modify-user-jellyseerr") as HTMLInputElement;
private _selectAll = document.getElementById("accounts-select-all") as HTMLInputElement; private _selectAll = document.getElementById("accounts-select-all") as HTMLInputElement;
private _users: { [id: string]: user }; private _users: { [id: string]: user };
private _ordering: string[] = []; private _ordering: string[] = [];
@ -1459,6 +1463,7 @@ export class accountsList {
const modalHeader = document.getElementById("header-modify-user"); const modalHeader = document.getElementById("header-modify-user");
modalHeader.textContent = window.lang.quantity("modifySettingsFor", this._collectUsers().length) modalHeader.textContent = window.lang.quantity("modifySettingsFor", this._collectUsers().length)
let list = this._collectUsers(); let list = this._collectUsers();
(() => { (() => {
let innerHTML = ""; let innerHTML = "";
for (const profile of window.availableProfiles) { for (const profile of window.availableProfiles) {
@ -1477,6 +1482,7 @@ export class accountsList {
const form = document.getElementById("form-modify-user") as HTMLFormElement; const form = document.getElementById("form-modify-user") as HTMLFormElement;
const button = form.querySelector("span.submit") as HTMLSpanElement; const button = form.querySelector("span.submit") as HTMLSpanElement;
this._modifySettingsProfile.checked = true; this._modifySettingsProfile.checked = true;
this._modifySettingsUser.checked = false; this._modifySettingsUser.checked = false;
form.onsubmit = (event: Event) => { form.onsubmit = (event: Event) => {
@ -1484,7 +1490,9 @@ export class accountsList {
toggleLoader(button); toggleLoader(button);
let send = { let send = {
"apply_to": list, "apply_to": list,
"homescreen": (document.getElementById("modify-user-homescreen") as HTMLInputElement).checked "homescreen": this._applyHomesreen.checked,
"ombi": this._applyOmbi.checked,
"jellyseerr": this._applyJellyseerr.checked
}; };
if (this._modifySettingsProfile.checked && !this._modifySettingsUser.checked) { if (this._modifySettingsProfile.checked && !this._modifySettingsUser.checked) {
send["from"] = "profile"; send["from"] = "profile";
@ -1821,6 +1829,16 @@ export class accountsList {
}; };
this._modifySettings.onclick = this.modifyUsers; this._modifySettings.onclick = this.modifyUsers;
this._modifySettings.classList.add("unfocused"); this._modifySettings.classList.add("unfocused");
if (window.ombiEnabled)
this._applyOmbi.parentElement.classList.remove("unfocused");
else
this._applyOmbi.parentElement.classList.add("unfocused");
if (window.jellyseerrEnabled)
this._applyJellyseerr.parentElement.classList.remove("unfocused");
else
this._applyJellyseerr.parentElement.classList.add("unfocused");
const checkSource = () => { const checkSource = () => {
const profileSpan = this._modifySettingsProfile.nextElementSibling as HTMLSpanElement; const profileSpan = this._modifySettingsProfile.nextElementSibling as HTMLSpanElement;
const userSpan = this._modifySettingsUser.nextElementSibling as HTMLSpanElement; const userSpan = this._modifySettingsUser.nextElementSibling as HTMLSpanElement;
@ -1831,6 +1849,8 @@ export class accountsList {
profileSpan.classList.remove("@low"); profileSpan.classList.remove("@low");
userSpan.classList.remove("@high"); userSpan.classList.remove("@high");
userSpan.classList.add("@low"); userSpan.classList.add("@low");
this._applyOmbi.parentElement.classList.remove("unfocused");
this._applyJellyseerr.parentElement.classList.remove("unfocused");
} else { } else {
this._userSelect.parentElement.classList.remove("unfocused"); this._userSelect.parentElement.classList.remove("unfocused");
this._profileSelect.parentElement.classList.add("unfocused"); this._profileSelect.parentElement.classList.add("unfocused");
@ -1838,6 +1858,8 @@ export class accountsList {
userSpan.classList.remove("@low"); userSpan.classList.remove("@low");
profileSpan.classList.remove("@high"); profileSpan.classList.remove("@high");
profileSpan.classList.add("@low"); profileSpan.classList.add("@low");
this._applyOmbi.parentElement.classList.add("unfocused");
this._applyJellyseerr.parentElement.classList.add("unfocused");
} }
}; };
this._modifySettingsProfile.onchange = checkSource; this._modifySettingsProfile.onchange = checkSource;