1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-10-18 09:00:11 +00:00

Compare commits

..

No commits in common. "90c6cee78083b71d9a316ca4098daf120dc2079d" and "ee026714d470277cae92de4ffe80444006dc6b55" have entirely different histories.

11 changed files with 129 additions and 161 deletions

77
api.go
View File

@ -116,7 +116,7 @@ func (app *appContext) checkInvites() {
} }
app.debug.Printf("Housekeeping: Deleting old invite %s", code) app.debug.Printf("Housekeeping: Deleting old invite %s", code)
notify := data.Notify notify := data.Notify
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { if app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
app.debug.Printf("%s: Expiry notification", code) app.debug.Printf("%s: Expiry notification", code)
var wait sync.WaitGroup var wait sync.WaitGroup
for address, settings := range notify { for address, settings := range notify {
@ -160,7 +160,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
if currentTime.After(expiry) { if currentTime.After(expiry) {
app.debug.Printf("Housekeeping: Deleting old invite %s", code) app.debug.Printf("Housekeeping: Deleting old invite %s", code)
notify := inv.Notify notify := inv.Notify
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 { if app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
app.debug.Printf("%s: Expiry notification", code) app.debug.Printf("%s: Expiry notification", code)
for address, settings := range notify { for address, settings := range notify {
if settings["notify-expiry"] { if settings["notify-expiry"] {
@ -301,7 +301,7 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
} }
} }
} }
if emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "" { if app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "" {
app.debug.Printf("%s: Sending welcome email to %s", req.Username, req.Email) app.debug.Printf("%s: Sending welcome email to %s", req.Username, req.Email)
msg, err := app.email.constructWelcome(req.Username, app) msg, err := app.email.constructWelcome(req.Username, app)
if err != nil { if err != nil {
@ -332,7 +332,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
success = false success = false
return return
} }
if emailEnabled && app.config.Section("email_confirmation").Key("enabled").MustBool(false) && !confirmed { if app.config.Section("email_confirmation").Key("enabled").MustBool(false) && !confirmed {
claims := jwt.MapClaims{ claims := jwt.MapClaims{
"valid": true, "valid": true,
"invite": req.Code, "invite": req.Code,
@ -385,7 +385,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
app.storage.loadProfiles() app.storage.loadProfiles()
invite := app.storage.invites[req.Code] invite := app.storage.invites[req.Code]
app.checkInvite(req.Code, true, req.Username) app.checkInvite(req.Code, true, req.Username)
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) { if app.config.Section("notifications").Key("enabled").MustBool(false) {
for address, settings := range invite.Notify { for address, settings := range invite.Notify {
if settings["notify-creation"] { if settings["notify-creation"] {
go func() { go func() {
@ -450,7 +450,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
} }
} }
} }
if emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "" { if app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "" {
app.debug.Printf("%s: Sending welcome email to %s", req.Username, req.Email) app.debug.Printf("%s: Sending welcome email to %s", req.Username, req.Email)
msg, err := app.email.constructWelcome(req.Username, app) msg, err := app.email.constructWelcome(req.Username, app)
if err != nil { if err != nil {
@ -544,7 +544,7 @@ func (app *appContext) DeleteUser(gc *gin.Context) {
errors[userID] += msg errors[userID] += msg
} }
} }
if emailEnabled && req.Notify { if req.Notify {
addr, ok := app.storage.emails[userID] addr, ok := app.storage.emails[userID]
if addr != nil && ok { if addr != nil && ok {
go func(userID, reason, address string) { go func(userID, reason, address string) {
@ -611,7 +611,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
invite.RemainingUses = 1 invite.RemainingUses = 1
} }
invite.ValidTill = validTill invite.ValidTill = validTill
if emailEnabled && req.Email != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) { if req.Email != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
app.debug.Printf("%s: Sending invite email", inviteCode) app.debug.Printf("%s: Sending invite email", inviteCode)
invite.Email = req.Email invite.Email = req.Email
msg, err := app.email.constructInvite(inviteCode, invite, app) msg, err := app.email.constructInvite(inviteCode, invite, app)
@ -1190,18 +1190,18 @@ func (app *appContext) GetConfig(gc *gin.Context) {
app.info.Println("Config requested") app.info.Println("Config requested")
resp := app.configBase resp := app.configBase
// Load language options // Load language options
formOptions := app.storage.lang.Form.getOptions() formChosen, formOptions := app.storage.lang.Form.getOptions(app.config.Section("ui").Key("language-form").MustString("en-us"))
fl := resp.Sections["ui"].Settings["language-form"] fl := resp.Sections["ui"].Settings["language-form"]
fl.Options = formOptions fl.Options = formOptions
fl.Value = app.config.Section("ui").Key("language-form").MustString("en-us") fl.Value = formChosen
adminOptions := app.storage.lang.Admin.getOptions() adminChosen, adminOptions := app.storage.lang.Admin.getOptions(app.config.Section("ui").Key("language-admin").MustString("en-us"))
al := resp.Sections["ui"].Settings["language-admin"] al := resp.Sections["ui"].Settings["language-admin"]
al.Options = adminOptions al.Options = adminOptions
al.Value = app.config.Section("ui").Key("language-admin").MustString("en-us") al.Value = adminChosen
emailOptions := app.storage.lang.Email.getOptions() emailChosen, emailOptions := app.storage.lang.Email.getOptions(app.config.Section("email").Key("language").MustString("en-us"))
el := resp.Sections["email"].Settings["language"] el := resp.Sections["email"].Settings["language"]
el.Options = emailOptions el.Options = emailOptions
el.Value = app.config.Section("email").Key("language").MustString("en-us") el.Value = emailChosen
for sectName, section := range resp.Sections { for sectName, section := range resp.Sections {
for settingName, setting := range section.Settings { for settingName, setting := range section.Settings {
val := app.config.Section(sectName).Key(settingName) val := app.config.Section(sectName).Key(settingName)
@ -1221,6 +1221,17 @@ func (app *appContext) GetConfig(gc *gin.Context) {
resp.Sections["ui"].Settings["language-admin"] = al resp.Sections["ui"].Settings["language-admin"] = al
resp.Sections["email"].Settings["language"] = el resp.Sections["email"].Settings["language"] = el
t := resp.Sections["jellyfin"].Settings["type"]
opts := make([]string, len(serverTypes))
i := 0
for _, v := range serverTypes {
opts[i] = v
i++
}
t.Options = opts
t.Value = serverTypes[app.config.Section("jellyfin").Key("type").MustString("jellyfin")]
resp.Sections["jellyfin"].Settings["type"] = t
gc.JSON(200, resp) gc.JSON(200, resp)
} }
@ -1243,7 +1254,35 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
tempConfig.NewSection(section) tempConfig.NewSection(section)
} }
for setting, value := range settings.(map[string]interface{}) { for setting, value := range settings.(map[string]interface{}) {
if value.(string) != app.config.Section(section).Key(setting).MustString("") { if section == "ui" && setting == "language-form" {
for key, lang := range app.storage.lang.Form {
if lang.Meta.Name == value.(string) || value.(string) == key {
tempConfig.Section("ui").Key("language-form").SetValue(key)
break
}
}
} else if section == "ui" && setting == "language-admin" {
for key, lang := range app.storage.lang.Admin {
if lang.Meta.Name == value.(string) || value.(string) == key {
tempConfig.Section("ui").Key("language-admin").SetValue(key)
break
}
}
} else if section == "email" && setting == "language" {
for key, lang := range app.storage.lang.Email {
if lang.Meta.Name == value.(string) || value.(string) == key {
tempConfig.Section("email").Key("language").SetValue(key)
break
}
}
} else if section == "jellyfin" && setting == "type" {
for k, v := range serverTypes {
if v == value.(string) {
tempConfig.Section("jellyfin").Key("type").SetValue(k)
break
}
}
} else if value.(string) != app.config.Section(section).Key(setting).MustString("") {
tempConfig.Section(section).Key(setting).SetValue(value.(string)) tempConfig.Section(section).Key(setting).SetValue(value.(string))
} }
} }
@ -1330,14 +1369,6 @@ func (app *appContext) GetLanguages(gc *gin.Context) {
gc.JSON(200, resp) gc.JSON(200, resp)
} }
func (app *appContext) restart(gc *gin.Context) {
app.info.Println("Restarting...")
err := app.Restart()
if err != nil {
app.err.Printf("Couldn't restart, try restarting manually. (%s)", err)
}
}
func (app *appContext) ServeLang(gc *gin.Context) { func (app *appContext) ServeLang(gc *gin.Context) {
page := gc.Param("page") page := gc.Param("page")
lang := strings.Replace(gc.Param("file"), ".json", "", 1) lang := strings.Replace(gc.Param("file"), ".json", "", 1)

View File

@ -9,8 +9,6 @@ import (
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
var emailEnabled = false
func (app *appContext) loadConfig() error { func (app *appContext) loadConfig() error {
var err error var err error
app.config, err = ini.Load(app.configPath) app.config, err = ini.Load(app.configPath)
@ -57,12 +55,6 @@ func (app *appContext) loadConfig() error {
app.config.Section("jellyfin").Key("device").SetValue("jfa-go") app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", VERSION, COMMIT)) app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", VERSION, COMMIT))
if app.config.Section("email").Key("method").MustString("") == "" {
emailEnabled = false
} else {
emailEnabled = true
}
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("") substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
oldFormLang := app.config.Section("ui").Key("language").MustString("") oldFormLang := app.config.Section("ui").Key("language").MustString("")

View File

@ -61,8 +61,8 @@
"requires_restart": true, "requires_restart": true,
"type": "select", "type": "select",
"options": [ "options": [
["jellyfin", "Jellyfin"], "jellyfin",
["emby", "Emby"] "emby"
], ],
"value": "jellyfin", "value": "jellyfin",
"description": "Note: Emby integration works is missing some features, such as Password Resets." "description": "Note: Emby integration works is missing some features, such as Password Resets."
@ -90,7 +90,7 @@
"requires_restart": true, "requires_restart": true,
"type": "select", "type": "select",
"options": [ "options": [
["en-us", "English (US)"] "en-us"
], ],
"value": "en-us", "value": "en-us",
"description": "Default Account Form Language. Submit a PR on github if you'd like to translate." "description": "Default Account Form Language. Submit a PR on github if you'd like to translate."
@ -101,7 +101,7 @@
"requires_restart": true, "requires_restart": true,
"type": "select", "type": "select",
"options": [ "options": [
["en-us", "English (US)"] "en-us"
], ],
"value": "en-us", "value": "en-us",
"description": "Default Admin page Language. Settings has not been translated. Submit a PR on github if you'd like to translate." "description": "Default Admin page Language. Settings has not been translated. Submit a PR on github if you'd like to translate."
@ -112,8 +112,8 @@
"requires_restart": true, "requires_restart": true,
"type": "select", "type": "select",
"options": [ "options": [
["Jellyfin (Dark)", "Jellyfin (Dark)"], "Jellyfin (Dark)",
["Default (Light)", "Default (Light)"] "Default (Light)"
], ],
"value": "Jellyfin (Dark)", "value": "Jellyfin (Dark)",
"description": "Default appearance for all users." "description": "Default appearance for all users."
@ -318,7 +318,7 @@
"order": [], "order": [],
"meta": { "meta": {
"name": "Email", "name": "Email",
"description": "General email settings." "description": "General email settings. Ignore if not using email features."
}, },
"settings": { "settings": {
"language": { "language": {
@ -328,7 +328,7 @@
"depends_true": "method", "depends_true": "method",
"type": "select", "type": "select",
"options": [ "options": [
["en-us", "English (US)"] "en-us"
], ],
"value": "en-us", "value": "en-us",
"description": "Default email language. Submit a PR on github if you'd like to translate." "description": "Default email language. Submit a PR on github if you'd like to translate."
@ -374,9 +374,8 @@
"requires_restart": false, "requires_restart": false,
"type": "select", "type": "select",
"options": [ "options": [
["", "Disabled"], "smtp",
["smtp", "SMTP"], "mailgun"
["mailgun", "Mailgun"]
], ],
"value": "smtp", "value": "smtp",
"description": "Method of sending email to use." "description": "Method of sending email to use."
@ -405,8 +404,7 @@
"order": [], "order": [],
"meta": { "meta": {
"name": "Password Resets", "name": "Password Resets",
"description": "Settings for the password reset handler.", "description": "Settings for the password reset handler."
"depends_true": "email|method"
}, },
"settings": { "settings": {
"enabled": { "enabled": {
@ -459,8 +457,7 @@
"order": [], "order": [],
"meta": { "meta": {
"name": "Invite emails", "name": "Invite emails",
"description": "Settings for sending invites directly to users.", "description": "Settings for sending invites directly to users."
"depends_true": "email|method"
}, },
"settings": { "settings": {
"enabled": { "enabled": {
@ -512,8 +509,7 @@
"order": [], "order": [],
"meta": { "meta": {
"name": "Notifications", "name": "Notifications",
"description": "Notification related settings.", "description": "Notification related settings."
"depends_true": "email|method"
}, },
"settings": { "settings": {
"enabled": { "enabled": {
@ -566,8 +562,7 @@
"order": [], "order": [],
"meta": { "meta": {
"name": "Mailgun (Email)", "name": "Mailgun (Email)",
"description": "Mailgun API connection settings", "description": "Mailgun API connection settings"
"depends_true": "email|method"
}, },
"settings": { "settings": {
"api_url": { "api_url": {
@ -590,8 +585,7 @@
"order": [], "order": [],
"meta": { "meta": {
"name": "SMTP (Email)", "name": "SMTP (Email)",
"description": "SMTP Server connection settings.", "description": "SMTP Server connection settings."
"depends_true": "email|method"
}, },
"settings": { "settings": {
"username": { "username": {
@ -608,8 +602,8 @@
"requires_restart": false, "requires_restart": false,
"type": "select", "type": "select",
"options": [ "options": [
["ssl_tls", "SSL/TLS"], "ssl_tls",
["starttls", "STARTTLS"] "starttls"
], ],
"value": "starttls", "value": "starttls",
"description": "Your email provider should provide different ports for each encryption method. Generally 465 for ssl_tls, 587 for starttls." "description": "Your email provider should provide different ports for each encryption method. Generally 465 for ssl_tls, 587 for starttls."
@ -677,8 +671,7 @@
"order": [], "order": [],
"meta": { "meta": {
"name": "Welcome Emails", "name": "Welcome Emails",
"description": "Optionally send a welcome email to new users with the Jellyfin URL and their username.", "description": "Optionally send a welcome email to new users with the Jellyfin URL and their username."
"depends_true": "email|method"
}, },
"settings": { "settings": {
"enabled": { "enabled": {
@ -719,8 +712,7 @@
"order": [], "order": [],
"meta": { "meta": {
"name": "Email confirmation", "name": "Email confirmation",
"description": "If enabled, a user will be sent an email confirmation link to ensure their password is right before they can make an account.", "description": "If enabled, a user will be sent an email confirmation link to ensure their password is right before they can make an account."
"depends_true": "email|method"
}, },
"settings": { "settings": {
"enabled": { "enabled": {
@ -760,8 +752,7 @@
"order": [], "order": [],
"meta": { "meta": {
"name": "Account Deletion", "name": "Account Deletion",
"description": "Subject/email files for account deletion emails.", "description": "Subject/email files for account deletion emails."
"depends_true": "email|method"
}, },
"settings": { "settings": {
"subject": { "subject": {

View File

@ -278,8 +278,7 @@
<div class="card ~neutral !low settings overflow"> <div class="card ~neutral !low settings overflow">
<span class="heading">{{ .strings.settings }}</span> <span class="heading">{{ .strings.settings }}</span>
<div class="fr"> <div class="fr">
<span class="button ~neutral !normal" id="settings-restart">{{ .strings.settingsRestart }}</span> <span class="button ~neutral !normal unfocused" id="settings-save">{{ .strings.settingsSave }}</span>
<span class="button ~urge !normal unfocused" id="settings-save">{{ .strings.settingsSave }}</span>
</div> </div>
<div class="row"> <div class="row">
<div class="card ~neutral !normal col" id="settings-sidebar"> <div class="card ~neutral !normal col" id="settings-sidebar">

44
lang.go
View File

@ -15,14 +15,15 @@ type quantityString struct {
type adminLangs map[string]adminLang type adminLangs map[string]adminLang
func (ls *adminLangs) getOptions() [][2]string { func (ls *adminLangs) getOptions(chosen string) (string, []string) {
opts := make([][2]string, len(*ls)) opts := make([]string, len(*ls))
chosenLang := (*ls)[chosen].Meta.Name
i := 0 i := 0
for key, lang := range *ls { for _, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name} opts[i] = lang.Meta.Name
i++ i++
} }
return opts return chosenLang, opts
} }
type commonLangs map[string]commonLang type commonLangs map[string]commonLang
@ -42,14 +43,15 @@ type adminLang struct {
type formLangs map[string]formLang type formLangs map[string]formLang
func (ls *formLangs) getOptions() [][2]string { func (ls *formLangs) getOptions(chosen string) (string, []string) {
opts := make([][2]string, len(*ls)) opts := make([]string, len(*ls))
chosenLang := (*ls)[chosen].Meta.Name
i := 0 i := 0
for key, lang := range *ls { for _, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name} opts[i] = lang.Meta.Name
i++ i++
} }
return opts return chosenLang, opts
} }
type formLang struct { type formLang struct {
@ -63,14 +65,15 @@ type formLang struct {
type emailLangs map[string]emailLang type emailLangs map[string]emailLang
func (ls *emailLangs) getOptions() [][2]string { func (ls *emailLangs) getOptions(chosen string) (string, []string) {
opts := make([][2]string, len(*ls)) opts := make([]string, len(*ls))
chosenLang := (*ls)[chosen].Meta.Name
i := 0 i := 0
for key, lang := range *ls { for _, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name} opts[i] = lang.Meta.Name
i++ i++
} }
return opts return chosenLang, opts
} }
type emailLang struct { type emailLang struct {
@ -107,14 +110,15 @@ type setupLang struct {
JSON string JSON string
} }
func (ls *setupLangs) getOptions() [][2]string { func (ls *setupLangs) getOptions(chosen string) (string, []string) {
opts := make([][2]string, len(*ls)) opts := make([]string, len(*ls))
chosenLang := (*ls)[chosen].Meta.Name
i := 0 i := 0
for key, lang := range *ls { for _, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name} opts[i] = lang.Meta.Name
i++ i++
} }
return opts return chosenLang, opts
} }
type langSection map[string]string type langSection map[string]string

View File

@ -35,14 +35,12 @@
"applyHomescreenLayout": "Apply homescreen layout", "applyHomescreenLayout": "Apply homescreen layout",
"sendDeleteNotificationEmail": "Send notification email", "sendDeleteNotificationEmail": "Send notification email",
"sendDeleteNotifiationExample": "Your account has been deleted.", "sendDeleteNotifiationExample": "Your account has been deleted.",
"settingsRestart": "Restart",
"settingsRestarting": "Restarting...",
"settingsRestartRequired": "Restart needed", "settingsRestartRequired": "Restart needed",
"settingsRestartRequiredDescription": "A restart is necessary to apply some settings you changed. Restart now or later?", "settingsRestartRequiredDescription": "A restart is necessary to apply some settings you changed. Restart now or later?",
"settingsApplyRestartLater": "Apply, restart later", "settingsApplyRestartLater": "Apply, restart later",
"settingsApplyRestartNow": "Apply & restart", "settingsApplyRestartNow": "Apply & restart",
"settingsApplied": "Settings applied.", "settingsApplied": "Settings applied.",
"settingsRefreshPage": "Refresh the page in a few seconds.", "settingsRefreshPage": "Refresh the page in a few seconds",
"settingsRequiredOrRestartMessage": "Note: {n} indicates a required field, {n} indicates changes require a restart.", "settingsRequiredOrRestartMessage": "Note: {n} indicates a required field, {n} indicates changes require a restart.",
"settingsSave": "Save", "settingsSave": "Save",
"ombiUserDefaults": "Ombi user defaults", "ombiUserDefaults": "Ombi user defaults",

View File

@ -619,7 +619,6 @@ func start(asDaemon, firstCall bool) {
api.POST("/users/settings", app.ApplySettings) api.POST("/users/settings", app.ApplySettings)
api.GET("/config", app.GetConfig) api.GET("/config", app.GetConfig)
api.POST("/config", app.ModifyConfig) api.POST("/config", app.ModifyConfig)
api.POST("/restart", app.restart)
if app.config.Section("ombi").Key("enabled").MustBool(false) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
api.GET("/ombi/users", app.OmbiUsers) api.GET("/ombi/users", app.OmbiUsers)
api.POST("/ombi/defaults", app.SetOmbiDefaults) api.POST("/ombi/defaults", app.SetOmbiDefaults)

View File

@ -138,10 +138,8 @@ type configDTO map[string]interface{}
// Below are for sending config // Below are for sending config
type meta struct { type meta struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
DependsTrue string `json:"depends_true,omitempty"`
DependsFalse string `json:"depends_false,omitempty"`
} }
type setting struct { type setting struct {
@ -151,7 +149,7 @@ type setting struct {
RequiresRestart bool `json:"requires_restart"` RequiresRestart bool `json:"requires_restart"`
Type string `json:"type"` // Type (string, number, bool, etc.) Type string `json:"type"` // Type (string, number, bool, etc.)
Value interface{} `json:"value"` Value interface{} `json:"value"`
Options [][2]string `json:"options,omitempty"` Options []string `json:"options,omitempty"`
DependsTrue string `json:"depends_true,omitempty"` // If specified, this field is enabled when the specified bool setting is enabled. DependsTrue string `json:"depends_true,omitempty"` // If specified, this field is enabled when the specified bool setting is enabled.
DependsFalse string `json:"depends_false,omitempty"` // If specified, opposite behaviour of DependsTrue. DependsFalse string `json:"depends_false,omitempty"` // If specified, opposite behaviour of DependsTrue.
} }

View File

@ -42,9 +42,6 @@ type PasswordReset struct {
} }
func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) { func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
if !emailEnabled {
return
}
for { for {
select { select {
case event, ok := <-watcher.Events: case event, ok := <-watcher.Events:

View File

@ -105,14 +105,9 @@ document.addEventListener("tab-change", (event: CustomEvent) => {
if (lang) { if (lang) {
tab += "?lang=" + lang tab += "?lang=" + lang
} }
window.history.pushState(event.detail, "Admin - jfa-go", tab); window.history.replaceState("", "Admin - jfa-go", tab);
}); });
window.onpopstate = (event: PopStateEvent) => {
console.log(event.state);
window.tabs.switch(event.state);
}
function login(username: string, password: string, run?: (state?: number) => void) { function login(username: string, password: string, run?: (state?: number) => void) {
const req = new XMLHttpRequest(); const req = new XMLHttpRequest();
req.responseType = 'json'; req.responseType = 'json';

View File

@ -7,8 +7,6 @@ interface settingsBoolEvent extends Event {
interface Meta { interface Meta {
name: string; name: string;
description: string; description: string;
depends_true?: string;
depends_false?: string;
} }
interface Setting { interface Setting {
@ -18,21 +16,13 @@ interface Setting {
requires_restart: boolean; requires_restart: boolean;
type: string; type: string;
value: string | boolean | number; value: string | boolean | number;
depends_true?: string; depends_true?: Setting;
depends_false?: string; depends_false?: Setting;
asElement: () => HTMLElement; asElement: () => HTMLElement;
update: (s: Setting) => void; update: (s: Setting) => void;
} }
const splitDependant = (section: string, dep: string): string[] => {
let parts = dep.split("|");
if (parts.length == 1) {
parts = [section, parts[0]];
}
return parts
};
class DOMInput { class DOMInput {
protected _input: HTMLInputElement; protected _input: HTMLInputElement;
private _container: HTMLDivElement; private _container: HTMLDivElement;
@ -86,7 +76,7 @@ class DOMInput {
<i class="icon ri-information-line"></i> <i class="icon ri-information-line"></i>
<span class="content sm"></span> <span class="content sm"></span>
</div> </div>
<input type="${inputType}" class="input ~neutral !normal mt-half mb-half"> <input type="${inputType}" class="input ~neutral !normal mt-half">
</label> </label>
`; `;
this._tooltip = this._container.querySelector("div.setting-tooltip") as HTMLDivElement; this._tooltip = this._container.querySelector("div.setting-tooltip") as HTMLDivElement;
@ -94,15 +84,11 @@ class DOMInput {
this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement; this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement;
this._input = this._container.querySelector("input[type=" + inputType + "]") as HTMLInputElement; this._input = this._container.querySelector("input[type=" + inputType + "]") as HTMLInputElement;
if (setting.depends_false || setting.depends_true) { if (setting.depends_false || setting.depends_true) {
let dependant = splitDependant(section, setting.depends_true || setting.depends_false); let dependant = setting.depends_true || setting.depends_false;
let state = true; let state = true;
if (setting.depends_false) { state = false; } if (setting.depends_false) { state = false; }
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => { document.addEventListener(`settings-${section}-${dependant}`, (event: settingsBoolEvent) => {
if (Boolean(event.detail) !== state) { this._input.disabled = (event.detail !== state);
this._input.parentElement.classList.add("unfocused");
} else {
this._input.parentElement.classList.remove("unfocused");
}
}); });
} }
const onValueChange = () => { const onValueChange = () => {
@ -220,7 +206,7 @@ class DOMBool implements SBool {
this._container = document.createElement("div"); this._container = document.createElement("div");
this._container.classList.add("setting"); this._container.classList.add("setting");
this._container.innerHTML = ` this._container.innerHTML = `
<label class="switch mb-half"> <label class="switch">
<input type="checkbox"> <input type="checkbox">
<span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span> <span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span>
<div class="setting-tooltip tooltip right unfocused"> <div class="setting-tooltip tooltip right unfocused">
@ -244,15 +230,11 @@ class DOMBool implements SBool {
document.addEventListener(`settings-loaded`, onValueChange); document.addEventListener(`settings-loaded`, onValueChange);
if (setting.depends_false || setting.depends_true) { if (setting.depends_false || setting.depends_true) {
let dependant = splitDependant(section, setting.depends_true || setting.depends_false); let dependant = setting.depends_true || setting.depends_false;
let state = true; let state = true;
if (setting.depends_false) { state = false; } if (setting.depends_false) { state = false; }
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => { document.addEventListener(`settings-${section}-${dependant}`, (event: settingsBoolEvent) => {
if (Boolean(event.detail) !== state) { this._input.disabled = (event.detail !== state);
this._input.parentElement.classList.add("unfocused");
} else {
this._input.parentElement.classList.remove("unfocused");
}
}); });
} }
this.update(setting); this.update(setting);
@ -269,7 +251,7 @@ class DOMBool implements SBool {
} }
interface SSelect extends Setting { interface SSelect extends Setting {
options: string[][]; options: string[];
value: string; value: string;
} }
class DOMSelect implements SSelect { class DOMSelect implements SSelect {
@ -278,7 +260,7 @@ class DOMSelect implements SSelect {
private _tooltip: HTMLDivElement; private _tooltip: HTMLDivElement;
private _required: HTMLSpanElement; private _required: HTMLSpanElement;
private _restart: HTMLSpanElement; private _restart: HTMLSpanElement;
private _options: string[][]; private _options: string[];
type: string = "bool"; type: string = "bool";
get name(): string { return this._container.querySelector("span.setting-label").textContent; } get name(): string { return this._container.querySelector("span.setting-label").textContent; }
@ -319,12 +301,12 @@ class DOMSelect implements SSelect {
get value(): string { return this._select.value; } get value(): string { return this._select.value; }
set value(v: string) { this._select.value = v; } set value(v: string) { this._select.value = v; }
get options(): string[][] { return this._options; } get options(): string[] { return this._options; }
set options(opt: string[][]) { set options(opt: string[]) {
this._options = opt; this._options = opt;
let innerHTML = ""; let innerHTML = "";
for (let option of this._options) { for (let option of this._options) {
innerHTML += `<option value="${option[0]}">${option[1]}</option>`; innerHTML += `<option value="${option}">${option}</option>`;
} }
this._select.innerHTML = innerHTML; this._select.innerHTML = innerHTML;
} }
@ -340,7 +322,7 @@ class DOMSelect implements SSelect {
<i class="icon ri-information-line"></i> <i class="icon ri-information-line"></i>
<span class="content sm"></span> <span class="content sm"></span>
</div> </div>
<div class="select ~neutral !normal mt-half mb-half"> <div class="select ~neutral !normal mt-half">
<select class="settings-select"></select> <select class="settings-select"></select>
</div> </div>
</label> </label>
@ -350,15 +332,11 @@ class DOMSelect implements SSelect {
this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement; this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement;
this._select = this._container.querySelector("select.settings-select") as HTMLSelectElement; this._select = this._container.querySelector("select.settings-select") as HTMLSelectElement;
if (setting.depends_false || setting.depends_true) { if (setting.depends_false || setting.depends_true) {
let dependant = splitDependant(section, setting.depends_true || setting.depends_false); let dependant = setting.depends_true || setting.depends_false;
let state = true; let state = true;
if (setting.depends_false) { state = false; } if (setting.depends_false) { state = false; }
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => { document.addEventListener(`settings-${section}-${dependant}`, (event: settingsBoolEvent) => {
if (Boolean(event.detail) !== state) { this._input.disabled = (event.detail !== state);
this._container.classList.add("unfocused");
} else {
this._container.classList.remove("unfocused");
}
}); });
} }
const onValueChange = () => { const onValueChange = () => {
@ -367,7 +345,6 @@ class DOMSelect implements SSelect {
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); } if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
}; };
this._select.onchange = onValueChange; this._select.onchange = onValueChange;
document.addEventListener(`settings-loaded`, onValueChange);
const message = document.getElementById("settings-message") as HTMLElement; const message = document.getElementById("settings-message") as HTMLElement;
message.innerHTML = window.lang.var("strings", message.innerHTML = window.lang.var("strings",
@ -376,6 +353,9 @@ class DOMSelect implements SSelect {
`<span class="badge ~info">R</span>` `<span class="badge ~info">R</span>`
); );
this.update(setting); this.update(setting);
} }
update = (s: SSelect) => { update = (s: SSelect) => {
@ -411,7 +391,6 @@ class sectionPanel {
<span class="heading">${s.meta.name}</span> <span class="heading">${s.meta.name}</span>
<p class="support lg">${s.meta.description}</p> <p class="support lg">${s.meta.description}</p>
`; `;
this.update(s); this.update(s);
} }
update = (s: Section) => { update = (s: Section) => {
@ -464,6 +443,8 @@ class sectionPanel {
asElement = (): HTMLDivElement => { return this._section; } asElement = (): HTMLDivElement => { return this._section; }
} }
interface Settings { interface Settings {
order: string[]; order: string[];
sections: { [sectionName: string]: Section }; sections: { [sectionName: string]: Section };
@ -488,18 +469,6 @@ export class settingsList {
button.classList.add("button", "~neutral", "!low", "settings-section-button", "mb-half"); button.classList.add("button", "~neutral", "!low", "settings-section-button", "mb-half");
button.textContent = s.meta.name; button.textContent = s.meta.name;
button.onclick = () => { this._showPanel(name); }; button.onclick = () => { this._showPanel(name); };
if (s.meta.depends_true || s.meta.depends_false) {
let dependant = splitDependant(name, s.meta.depends_true || s.meta.depends_false);
let state = true;
if (s.meta.depends_false) { state = false; }
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => {
if (Boolean(event.detail) !== state) {
button.classList.add("unfocused");
} else {
button.classList.remove("unfocused");
}
});
}
this._buttons[name] = button; this._buttons[name] = button;
this._sidebar.appendChild(this._buttons[name]); this._sidebar.appendChild(this._buttons[name]);
} }
@ -556,11 +525,6 @@ export class settingsList {
this._sections = {}; this._sections = {};
this._buttons = {}; this._buttons = {};
document.addEventListener("settings-section-changed", () => this._saveButton.classList.remove("unfocused")); document.addEventListener("settings-section-changed", () => this._saveButton.classList.remove("unfocused"));
document.getElementById("settings-restart").onclick = () => {
_post("/restart", null, () => {});
window.modals.settingsRefresh.modal.querySelector("span.heading").textContent = window.lang.strings("settingsRestarting");
window.modals.settingsRefresh.show();
};
this._saveButton.onclick = this._save; this._saveButton.onclick = this._save;
document.addEventListener("settings-requires-restart", () => { this._needsRestart = true; }); document.addEventListener("settings-requires-restart", () => { this._needsRestart = true; });
@ -585,9 +549,9 @@ export class settingsList {
} }
} }
this._showPanel(settings.order[0]); this._showPanel(settings.order[0]);
this._needsRestart = false;
document.dispatchEvent(new CustomEvent("settings-loaded")); document.dispatchEvent(new CustomEvent("settings-loaded"));
this._saveButton.classList.add("unfocused"); this._saveButton.classList.add("unfocused");
this._needsRestart = false;
} }
}) })
} }