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

Compare commits

...

16 Commits

Author SHA1 Message Date
a11ac1b1a3
Merge branch 'main' of github.com:hrfee/jfa-go 2024-08-26 20:01:45 +01:00
73197df8d9
Merge pull request #359 from hrfee/dependabot/go_modules/easyproxy/golang.org/x/net-0.23.0
build(deps): bump golang.org/x/net from 0.15.0 to 0.23.0 in /easyproxy
2024-08-26 19:01:32 +00:00
a492c06077
Merge pull request #362 from hrfee/dependabot/npm_and_yarn/word-wrap-1.2.5
build(deps): bump word-wrap from 1.2.3 to 1.2.5
2024-08-26 19:01:20 +00:00
bc66f46d9e
Merge pull request #360 from hrfee/dependabot/npm_and_yarn/multi-e3a4bbf809
build(deps): bump ws
2024-08-26 19:01:08 +00:00
eefd350754
Merge pull request #361 from hrfee/dependabot/npm_and_yarn/site/word-wrap-1.2.5
build(deps): bump word-wrap from 1.2.3 to 1.2.5 in /site
2024-08-26 18:59:48 +00:00
dependabot[bot]
494b8b2399
build(deps): bump word-wrap from 1.2.3 to 1.2.5
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 18:58:58 +00:00
dependabot[bot]
e901ba6bb5
build(deps): bump word-wrap from 1.2.3 to 1.2.5 in /site
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 18:58:53 +00:00
dependabot[bot]
4455c15bca
build(deps): bump ws
Bumps  and [ws](https://github.com/websockets/ws). These dependencies needed to be updated together.

Updates `ws` from 8.13.0 to 8.18.0
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.13.0...8.18.0)

Updates `ws` from 6.2.2 to 8.18.0
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.13.0...8.18.0)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 18:58:24 +00:00
dependabot[bot]
c2bdc67242
build(deps): bump golang.org/x/net from 0.15.0 to 0.23.0 in /easyproxy
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.15.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 18:58:16 +00:00
37545e1e36
Merge pull request #297 from hrfee/dependabot/npm_and_yarn/postcss-8.4.31
build(deps): bump postcss from 8.4.24 to 8.4.31
2024-08-26 18:57:51 +00:00
dependabot[bot]
19495be6e9
build(deps): bump postcss from 8.4.24 to 8.4.31
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.24 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.24...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 18:57:04 +00:00
37a062e24d
build: change deprecated goreleaser option 2024-08-26 17:47:40 +01:00
a4c60c71ea
update go.mods
tried to lower required version, most subpackages worked, however some
other dependency means the main package requires 1.22.
2024-08-26 17:26:04 +01:00
9e9f46d97b
build: remove compile_mjml script, no python!
if I had taken a second to actually read the documentation, i'd have
    realized the mjml command can process a bunch of files at once.
    On my machine, cuts time down from ~800ms to ~500ms.
While there are still some scripts using python, none are needed to
build the software anymore, so no python build deps!
2024-08-26 17:01:34 +01:00
f063b970b4
config: migrate to new yaml format
config-base.yaml is almost identical to json version, except there's no "order" field, as
"sections" and "settings" fields are now lists themselves and so Go can
parse the correct order. As such, removed enumerate_config.py. Also,
rewrote scripts/generate_ini.py in Go as scripts/ini/. Config structure
in Go form is now in common/config.go, and is used by jfa-go and the ini
script. app.configBase is now untouched once read from config-base.yaml,
and instead copied to and patched in app.patchedConfig. Patching occurs
at program start and config modification, so GetConfig is now just a
couple of lines. Discord role patching still occurs in GetConfig, as the
available roles can change regularly. Also added new "Disabled" field to
sections, to avoid the nightmare of deleting from an array.
2024-08-26 15:43:28 +01:00
711b817cff
auth: add note for self about secure cookies 2024-08-24 15:25:08 +01:00
34 changed files with 2266 additions and 2693 deletions

View File

@ -116,7 +116,7 @@ archives:
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "0.0.0-{{ .Env.JFA_GO_NFPM_EPOCH }}"
version_template: "0.0.0-{{ .Env.JFA_GO_NFPM_EPOCH }}"
changelog:
sort: asc
filters:

View File

@ -1,4 +1,4 @@
.PHONY: configuration email typescript swagger copy compile compress tailwind bundle-css inline-css variants-html install clean npm config-description config-default precompile
.PHONY: configuration email typescript swagger copy compile compress inline-css variants-html install clean npm config-description config-default precompile
all: compile
@ -101,20 +101,22 @@ else
SWAGINSTALL :=
endif
CONFIG_BASE = config/config-base.json
CONFIG_BASE = config/config-base.yaml
CONFIG_DESCRIPTION = $(DATA)/config-base.json
# CONFIG_DESCRIPTION = $(DATA)/config-base.json
CONFIG_DEFAULT = $(DATA)/config-default.ini
$(CONFIG_DESCRIPTION) &: $(CONFIG_BASE)
$(info Fixing config-base)
-mkdir -p $(DATA)
python3 scripts/enumerate_config.py -i config/config-base.json -o $(DATA)/config-base.json
# $(CONFIG_DESCRIPTION) &: $(CONFIG_BASE)
# $(info Fixing config-base)
# -mkdir -p $(DATA)
$(CONFIG_DEFAULT) &: $(CONFIG_BASE)
$(DATA):
mkdir -p $(DATA)
$(CONFIG_DEFAULT): $(DATA) $(CONFIG_BASE)
$(info Generating config-default.ini)
python3 scripts/generate_ini.py -i config/config-base.json -o $(DATA)/config-default.ini
go run scripts/ini/main.go -in $(CONFIG_BASE) -out $(DATA)/config-default.ini
configuration: $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT)
configuration: $(CONFIG_DEFAULT)
EMAIL_SRC_MJML = $(wildcard mail/*.mjml)
EMAIL_SRC_TXT = $(wildcard mail/*.txt)
@ -125,7 +127,9 @@ EMAIL_ALL = $(EMAIL_HTML) $(EMAIL_TXT)
EMAIL_TARGET = mail/confirmation.html
$(EMAIL_TARGET): $(EMAIL_SRC_MJML) $(EMAIL_SRC_TXT)
$(info Generating email html)
python3 scripts/compile_mjml.py -o $(DATA)/
npx mjml mail/*.mjml -o $(DATA)/
$(info Copying plaintext mail)
cp mail/*.txt $(DATA)/
TYPESCRIPT_FULLSRC = $(shell find ts/ -type f -name "*.ts")
TYPESCRIPT_SRC = $(wildcard ts/*.ts)
@ -179,8 +183,6 @@ $(CSS_FULLTARGET): $(TYPESCRIPT_TARGET) $(VARIANTS_TARGET) $(ALL_CSS_SRC) $(wild
# mv $(CSS_BUNDLE) $(DATA)/web/css/$(CSSVERSION)bundle.css
# npx postcss -o $(CSS_TARGET) $(CSS_TARGET)
bundle-css: tailwind
INLINE_SRC = html/crash.html
INLINE_TARGET = $(DATA)/crash.html
$(INLINE_TARGET): $(CSS_FULLTARGET) $(INLINE_SRC)
@ -197,6 +199,8 @@ COPY_SRC = images/banner.svg jfa-go.service LICENSE $(LANG_SRC) $(STATIC_SRC)
COPY_TARGET = $(DATA)/jfa-go.service
# $(DATA)/LICENSE $(LANG_TARGET) $(STATIC_TARGET) $(DATA)/web/css/$(CSSVERSION)bundle.css
$(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC)
$(info copying $(CONFIG_BASE))
cp $(CONFIG_BASE) $(DATA)/
$(info copying crash page)
cp $(DATA)/crash.html $(DATA)/html/
$(info copying static data)
@ -209,11 +213,11 @@ $(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC)
cp -r lang $(DATA)/
cp LICENSE $(DATA)/
precompile: $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET)
precompile: $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET)
GO_SRC = $(shell find ./ -name "*.go")
GO_TARGET = build/jfa-go
$(GO_TARGET): $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum
$(GO_TARGET): $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum
$(info Downloading deps)
$(GOBINARY) mod download
$(info Building)
@ -225,15 +229,6 @@ compile: $(GO_TARGET)
compress:
upx --lzma build/jfa-go
# internal-files:
# python3 scripts/embed.py internal
#
# external-files:
# python3 scripts/embed.py external
# -mkdir -p build
# $(info copying internal data into build/)
# cp -r data build/
install:
cp -r build $(DESTDIR)/jfa-go

153
api.go
View File

@ -6,6 +6,7 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/common"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser"
"github.com/itchyny/timefmt-go"
@ -229,102 +230,15 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
// @Summary Get jfa-go configuration.
// @Produce json
// @Success 200 {object} settings "Uses the same format as config-base.json"
// @Success 200 {object} common.Config "Uses the same format as config-base.json"
// @Router /config [get]
// @Security Bearer
// @tags Configuration
func (app *appContext) GetConfig(gc *gin.Context) {
resp := app.configBase
// Load language options
formOptions := app.storage.lang.User.getOptions()
fl := resp.Sections["ui"].Settings["language-form"]
fl.Options = formOptions
fl.Value = app.config.Section("ui").Key("language-form").MustString("en-us")
pwrOptions := app.storage.lang.PasswordReset.getOptions()
pl := resp.Sections["password_resets"].Settings["language"]
pl.Options = pwrOptions
pl.Value = app.config.Section("password_resets").Key("language").MustString("en-us")
adminOptions := app.storage.lang.Admin.getOptions()
al := resp.Sections["ui"].Settings["language-admin"]
al.Options = adminOptions
al.Value = app.config.Section("ui").Key("language-admin").MustString("en-us")
emailOptions := app.storage.lang.Email.getOptions()
el := resp.Sections["email"].Settings["language"]
el.Options = emailOptions
el.Value = app.config.Section("email").Key("language").MustString("en-us")
telegramOptions := app.storage.lang.Email.getOptions()
tl := resp.Sections["telegram"].Settings["language"]
tl.Options = telegramOptions
tl.Value = app.config.Section("telegram").Key("language").MustString("en-us")
if updater == "" {
delete(resp.Sections, "updates")
for i, v := range resp.Order {
if v == "updates" {
resp.Order = append(resp.Order[:i], resp.Order[i+1:]...)
break
}
}
}
if PLATFORM == "windows" {
delete(resp.Sections["smtp"].Settings, "ssl_cert")
for i, v := range resp.Sections["smtp"].Order {
if v == "ssl_cert" {
sect := resp.Sections["smtp"]
sect.Order = append(sect.Order[:i], sect.Order[i+1:]...)
resp.Sections["smtp"] = sect
}
}
}
if !MatrixE2EE() {
delete(resp.Sections["matrix"].Settings, "encryption")
for i, v := range resp.Sections["matrix"].Order {
if v == "encryption" {
sect := resp.Sections["matrix"]
sect.Order = append(sect.Order[:i], sect.Order[i+1:]...)
resp.Sections["matrix"] = sect
}
}
}
for sectName, section := range resp.Sections {
for settingName, setting := range section.Settings {
val := app.config.Section(sectName).Key(settingName)
s := resp.Sections[sectName].Settings[settingName]
switch setting.Type {
case "list":
s.Value = val.StringsWithShadows("|")
case "text", "email", "select", "password", "note":
s.Value = val.MustString("")
case "number":
s.Value = val.MustInt(0)
case "bool":
s.Value = val.MustBool(false)
}
resp.Sections[sectName].Settings[settingName] = s
}
}
if discordEnabled {
r, err := app.discord.ListRoles()
if err == nil {
roles := make([][2]string, len(r)+1)
roles[0] = [2]string{"", "None"}
for i, role := range r {
roles[i+1] = role
}
s := resp.Sections["discord"].Settings["apply_role"]
s.Options = roles
resp.Sections["discord"].Settings["apply_role"] = s
}
app.PatchConfigDiscordRoles()
}
resp.Sections["ui"].Settings["language-form"] = fl
resp.Sections["ui"].Settings["language-admin"] = al
resp.Sections["email"].Settings["language"] = el
resp.Sections["password_resets"].Settings["language"] = pl
resp.Sections["telegram"].Settings["language"] = tl
resp.Sections["discord"].Settings["language"] = tl
resp.Sections["matrix"].Settings["language"] = tl
gc.JSON(200, resp)
gc.JSON(200, app.patchedConfig)
}
// @Summary Modify app config.
@ -340,35 +254,46 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
gc.BindJSON(&req)
// Load a new config, as we set various default values in app.config that shouldn't be stored.
tempConfig, _ := ini.ShadowLoad(app.configPath)
for section, settings := range req {
if section != "restart-program" {
_, err := tempConfig.GetSection(section)
if err != nil {
tempConfig.NewSection(section)
for _, section := range app.configBase.Sections {
ns, ok := req[section.Section]
if !ok {
continue
}
newSection := ns.(map[string]any)
iniSection, err := tempConfig.GetSection(section.Section)
if err != nil {
iniSection, _ = tempConfig.NewSection(section.Section)
}
for _, setting := range section.Settings {
newValue, ok := newSection[setting.Setting]
if !ok {
continue
}
for setting, value := range settings.(map[string]interface{}) {
if section == "email" && setting == "method" && value == "disabled" {
value = ""
}
if (section == "discord" || section == "matrix") && setting == "language" {
tempConfig.Section("telegram").Key("language").SetValue(value.(string))
} else if app.configBase.Sections[section].Settings[setting].Type == "list" {
splitValues := strings.Split(value.(string), "|")
// Delete the key first to get rid of any shadow values
tempConfig.Section(section).DeleteKey(setting)
for i, v := range splitValues {
if i == 0 {
tempConfig.Section(section).Key(setting).SetValue(v)
} else {
tempConfig.Section(section).Key(setting).AddShadow(v)
}
// Patch disabled to actually be an empty string
if section.Section == "email" && setting.Setting == "method" && newValue == "disabled" {
newValue = ""
}
// Copy language preference for chatbots to root one in "telegram"
if (section.Section == "discord" || section.Section == "matrix") && setting.Setting == "language" {
iniSection.Key("language").SetValue(newValue.(string))
} else if setting.Type == common.ListType {
splitValues := strings.Split(newValue.(string), "|")
// Delete the key first to get rid of any shadow values
iniSection.DeleteKey(setting.Setting)
for i, v := range splitValues {
if i == 0 {
iniSection.Key(setting.Setting).SetValue(v)
} else {
iniSection.Key(setting.Setting).AddShadow(v)
}
} else if value.(string) != app.config.Section(section).Key(setting).MustString("") {
tempConfig.Section(section).Key(setting).SetValue(value.(string))
}
} else if newValue.(string) != iniSection.Key(setting.Setting).MustString("") {
iniSection.Key(setting.Setting).SetValue(newValue.(string))
}
}
}
tempConfig.Section("").Key("first_run").SetValue("false")
if err := tempConfig.SaveTo(app.configPath); err != nil {
app.err.Printf(lm.FailedWriting, app.configPath, err)
@ -381,6 +306,8 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
app.Restart()
}
app.loadConfig()
// Patch new settings for next GetConfig
app.PatchConfigBase()
// Reinitialize password validator on config change, as opposed to every applicable request like in python.
if _, ok := req["password_validation"]; ok {
validatorConf := ValidatorConf{

View File

@ -251,6 +251,7 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
// host := gc.Request.URL.Hostname()
host := app.ExternalDomain
// Before you think this is broken: the first "true" arg is for "secure", i.e. only HTTPS!
gc.SetCookie("refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, "/", host, true, true)
gc.JSON(200, getTokenDTO{token})
}

62
common/config.go Normal file
View File

@ -0,0 +1,62 @@
package common
type SectionMeta struct {
Name string `json:"name" yaml:"name" example:"My Section"` // friendly name of the section
Description string `json:"description" yaml:"description"`
Advanced bool `json:"advanced,omitempty" yaml:"advanced,omitempty"`
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
DependsTrue string `json:"depends_true,omitempty" yaml:"depends_true,omitempty"`
DependsFalse string `json:"depends_false,omitempty" yaml:"depends_false,omitempty"`
WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"`
}
type Option [2]string
type SettingType string
var (
BoolType SettingType = "bool"
SelectType SettingType = "select"
TextType SettingType = "text"
PasswordType SettingType = "password"
NumberType SettingType = "number"
NoteType SettingType = "note"
EmailType SettingType = "email"
ListType SettingType = "list"
)
type Setting struct {
Setting string `json:"setting" yaml:"setting" example:"my_setting"`
Name string `json:"name" yaml:"name" example:"My Setting"`
Description string `json:"description" yaml:"description"`
Required bool `json:"required" yaml:"required"`
RequiresRestart bool `json:"requires_restart" yaml:"requires_restart"`
Advanced bool `json:"advanced,omitempty" yaml:"advanced,omitempty"`
Type SettingType `json:"type" yaml:"type"` // Type (string, number, bool, etc.)
Value any `json:"value" yaml:"value"`
Options []Option `json:"options,omitempty" yaml:"options,omitempty"`
DependsTrue string `json:"depends_true,omitempty" yaml:"depends_true,omitempty"` // If specified, this field is enabled when the specified bool setting is enabled.
DependsFalse string `json:"depends_false,omitempty" yaml:"depends_false,omitempty"` // If specified, opposite behaviour of DependsTrue.
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"`
}
type Section struct {
Section string `json:"section" yaml:"section" example:"my_section"`
Meta SectionMeta `json:"meta" yaml:"meta"`
Settings []Setting `json:"settings" yaml:"settings"`
}
type Config struct {
Sections []Section `json:"sections" yaml:"sections"`
}
func (c *Config) removeSection(section string) {
for i, v := range c.Sections {
if v.Section == section {
c.Sections = append(c.Sections[:i], c.Sections[i+1:]...)
break
}
}
}

View File

@ -2,6 +2,6 @@ module github.com/hrfee/jfa-go/common
replace github.com/hrfee/jfa-go/logmessages => ../logmessages
go 1.22.4
go 1.18
require github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a

View File

@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/hrfee/jfa-go/common"
"github.com/hrfee/jfa-go/easyproxy"
lm "github.com/hrfee/jfa-go/logmessages"
"gopkg.in/ini.v1"
@ -250,3 +251,98 @@ func (app *appContext) loadConfig() error {
return nil
}
func (app *appContext) PatchConfigBase() {
conf := app.configBase
// Load language options
formOptions := app.storage.lang.User.getOptions()
pwrOptions := app.storage.lang.PasswordReset.getOptions()
adminOptions := app.storage.lang.Admin.getOptions()
emailOptions := app.storage.lang.Email.getOptions()
telegramOptions := app.storage.lang.Email.getOptions()
for i, section := range app.configBase.Sections {
if section.Section == "updates" && updater == "" {
section.Meta.Disabled = true
}
for j, setting := range section.Settings {
if section.Section == "ui" {
if setting.Setting == "language-form" {
setting.Options = formOptions
setting.Value = "en-us"
} else if setting.Setting == "language-admin" {
setting.Options = adminOptions
setting.Value = "en-us"
}
} else if section.Section == "password_resets" {
if setting.Setting == "language" {
setting.Options = pwrOptions
setting.Value = "en-us"
}
} else if section.Section == "email" {
if setting.Setting == "language" {
setting.Options = emailOptions
setting.Value = "en-us"
}
} else if section.Section == "telegram" {
if setting.Setting == "language" {
setting.Options = telegramOptions
setting.Value = "en-us"
}
} else if section.Section == "smtp" {
if setting.Setting == "ssl_cert" && PLATFORM == "windows" {
// Not accurate but the effect is hiding the option, which we want.
setting.Deprecated = true
}
} else if section.Section == "matrix" {
if setting.Setting == "encryption" && !MatrixE2EE() {
// Not accurate but the effect is hiding the option, which we want.
setting.Deprecated = true
}
}
val := app.config.Section(section.Section).Key(setting.Setting)
switch setting.Type {
case "list":
setting.Value = val.StringsWithShadows("|")
case "text", "email", "select", "password", "note":
setting.Value = val.MustString("")
case "number":
setting.Value = val.MustInt(0)
case "bool":
setting.Value = val.MustBool(false)
}
section.Settings[j] = setting
}
conf.Sections[i] = section
}
app.patchedConfig = conf
}
func (app *appContext) PatchConfigDiscordRoles() {
if !discordEnabled {
return
}
r, err := app.discord.ListRoles()
if err != nil {
return
}
roles := make([]common.Option, len(r)+1)
roles[0] = common.Option{"", "None"}
for i, role := range r {
roles[i+1] = role
}
for i, section := range app.patchedConfig.Sections {
if section.Section != "discord" {
continue
}
for j, setting := range section.Settings {
if setting.Setting != "apply_role" {
continue
}
setting.Options = roles
section.Settings[j] = setting
}
app.patchedConfig.Sections[i] = section
}
}

View File

@ -1,4 +0,0 @@
### fixconfig
Python's `json` library retains the order of data in a JSON file, which meant settings sent to the web page would be in the right order. Go's `encoding/json` and maps do not retain order, so `enumerate/enumerate_config.py` opens the json file, and for each section, adds an "order" array which tells the web page in which order to display settings.
Specify the input and output files with `-i` and `-o` respectively.

1
config/README.txt Normal file
View File

@ -0,0 +1 @@
The two python scripts here, `config-json-to-new-yaml.py` and `gen-rough-schema.py` were used to convert the old format, which was stored in a JSON file, to the new format in YAML. The latter script is used to get the possible values for settings and sections, so they could be properly defined in common/config.go.

File diff suppressed because it is too large Load Diff

1577
config/config-base.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
from ruamel.yaml import YAML
import json
from pathlib import Path
import sys
yaml = YAML()
# c = yaml.load(Path(sys.argv[len(sys.argv)-1]))
with open(sys.argv[len(sys.argv)-1], 'r') as f:
c = json.load(f)
c.pop("order")
c1 = c.copy()
c1["sections"] = []
for section in c["sections"]:
codeSection = { "section": section }
s = codeSection | c["sections"][section]
s.pop("order")
c1["sections"].append(s)
c2 = c.copy()
c2["sections"] = []
for section in c1["sections"]:
sArray = []
for setting in section["settings"]:
codeSetting = { "setting": setting }
s = codeSetting | section["settings"][setting]
sArray.append(s)
section["settings"] = sArray
c2["sections"].append(section)
yaml.dump(c2, sys.stdout)

View File

@ -0,0 +1,40 @@
import json
import sys
sectionSchema = {}
metaSchema = {}
settingSchema = {}
typeValues = {}
# c = yaml.load(Path(sys.argv[len(sys.argv)-1]))
with open(sys.argv[len(sys.argv)-1], 'r') as f:
c = json.load(f)
for section in c["sections"]:
for key in c["sections"][section]:
sectionSchema[key] = True
for key in c["sections"][section]["meta"]:
metaSchema[key] = c["sections"][section]["meta"][key]
for setting in c["sections"][section]["settings"]:
for field in c["sections"][section]["settings"][setting]:
settingSchema[field] = c["sections"][section]["settings"][setting][field]
typeValues[c["sections"][section]["settings"][setting]["type"]] = True
print("Section Content:")
for v in sectionSchema:
print(v)
print("---")
print("Meta Schema")
for v in metaSchema:
print(v, "=", type(metaSchema[v]))
print("---")
print("Setting Schema")
for v in settingSchema:
print(v, "=", type(settingSchema[v]))
print("---")
print("Possible Types")
for v in typeValues:
print(v)

View File

@ -1,7 +1,7 @@
module github.com/hrfee/jfa-go/easyproxy
go 1.20
go 1.18
require golang.org/x/net v0.15.0
require golang.org/x/net v0.23.0
require github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b // indirect
require github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b

View File

@ -1,4 +1,4 @@
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b h1:xZ59n7Frzh8CwyfAapUZLSg+gXH5m63YEaFCMpDHhpI=
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b/go.mod h1:uDd4sYVYsqcxAB8j+Q7uhL6IJCs/r1kxib1HV4bgOMg=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=

39
go.mod
View File

@ -1,6 +1,8 @@
module github.com/hrfee/jfa-go
go 1.22.4
go 1.22
toolchain go1.22.4
replace github.com/hrfee/jfa-go/docs => ./docs
@ -33,18 +35,18 @@ require (
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/gomarkdown/markdown v0.0.0-20240730141124-034f12af3bf6
github.com/hrfee/jfa-go/common v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/docs v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/easyproxy v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/jellyseerr v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/linecache v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/logger v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/ombi v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/common v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/docs v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/easyproxy v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/jellyseerr v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/linecache v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/logger v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/logmessages v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/ombi v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/mediabrowser v0.3.18
github.com/itchyny/timefmt-go v0.1.6
github.com/lithammer/shortuuid/v3 v3.0.7
github.com/mailgun/mailgun-go/v4 v4.14.0
github.com/mailgun/mailgun-go/v4 v4.15.0
github.com/mattn/go-sqlite3 v1.14.22
github.com/robert-nix/ansihtml v1.0.1
github.com/steambap/captcha v1.4.1
@ -54,7 +56,8 @@ require (
github.com/writeas/go-strip-markdown v2.0.1+incompatible
github.com/xhit/go-simple-mail/v2 v2.16.0
gopkg.in/ini.v1 v1.67.0
maunium.net/go/mautrix v0.19.0
gopkg.in/yaml.v3 v3.0.1
maunium.net/go/mautrix v0.20.0
)
require (
@ -110,7 +113,8 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/swaggo/swag v1.16.3 // indirect
@ -122,21 +126,20 @@ require (
github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.mau.fi/util v0.6.0 // indirect
go.mau.fi/util v0.7.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/arch v0.9.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
golang.org/x/image v0.19.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

18
go.sum
View File

@ -258,6 +258,8 @@ github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b h1:xZ59n
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b/go.mod h1:uDd4sYVYsqcxAB8j+Q7uhL6IJCs/r1kxib1HV4bgOMg=
github.com/mailgun/mailgun-go/v4 v4.14.0 h1:N9GMNs0XUq5nn3v2TlZHCknF9khSh88MkA1hdRm1o7I=
github.com/mailgun/mailgun-go/v4 v4.14.0/go.mod h1:L9s941Lgk7iB3TgywTPz074pK2Ekkg4kgbnAaAyJ2z8=
github.com/mailgun/mailgun-go/v4 v4.15.0 h1:3NQU0r2XItJbyIZ21iBI9ps0+vPIMoGyI2XK7ZTN/DQ=
github.com/mailgun/mailgun-go/v4 v4.15.0/go.mod h1:L9s941Lgk7iB3TgywTPz074pK2Ekkg4kgbnAaAyJ2z8=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -290,6 +292,10 @@ github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwU
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -384,17 +390,25 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mau.fi/util v0.6.0 h1:W6SyB3Bm/GjenQ5iq8Z8WWdN85Gy2xS6L0wmnR7SVjg=
go.mau.fi/util v0.6.0/go.mod h1:ljYdq3sPfpICc3zMU+/mHV/sa4z0nKxc67hSBwnrk8U=
go.mau.fi/util v0.7.0 h1:l31z+ivrSQw+cv/9eFebEqtQW2zhxivGypn+JT0h/ws=
go.mau.fi/util v0.7.0/go.mod h1:bWYreIoTULL/UiRbZdfddPh7uWDFW5yX4YCv5FB0eE0=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -419,6 +433,8 @@ golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn5
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
@ -577,4 +593,6 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
maunium.net/go/mautrix v0.19.0 h1:67eSJWam93mw44Q0/1SiOG7zQzXMUknUv5UaWkrODDU=
maunium.net/go/mautrix v0.19.0/go.mod h1:UE+mSQ4sDUuJMbjN0aB9EjQSGgXd48AzMvZ6+QJV1k8=
maunium.net/go/mautrix v0.20.0 h1:bzQnVQR+LvQxV1YlAr7BSWCS8AWa0Ov0lyPhbbChM0o=
maunium.net/go/mautrix v0.20.0/go.mod h1:V725r8w7oddsS7CxnmTAp634A4nwJCFY7J3jiTMUz2c=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View File

@ -2,7 +2,7 @@ module github.com/hrfee/jfa-go/jellyseerr
replace github.com/hrfee/jfa-go/common => ../common
go 1.22.4
go 1.18
require github.com/hrfee/jfa-go/common v0.0.0-20240728190513-dabef831d769

38
lang.go
View File

@ -1,5 +1,7 @@
package main
import "github.com/hrfee/jfa-go/common"
type langMeta struct {
Name string `json:"name"`
// Language to fall back on if strings are missing. Defaults to en-us.
@ -13,11 +15,11 @@ type quantityString struct {
type adminLangs map[string]adminLang
func (ls *adminLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
func (ls *adminLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name}
opts[i] = common.Option{key, lang.Meta.Name}
i++
}
return opts
@ -42,11 +44,11 @@ type adminLang struct {
type userLangs map[string]userLang
func (ls *userLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
func (ls *userLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name}
opts[i] = common.Option{key, lang.Meta.Name}
i++
}
return opts
@ -65,11 +67,11 @@ type userLang struct {
type pwrLangs map[string]pwrLang
func (ls *pwrLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
func (ls *pwrLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name}
opts[i] = common.Option{key, lang.Meta.Name}
i++
}
return opts
@ -82,11 +84,11 @@ type pwrLang struct {
type emailLangs map[string]emailLang
func (ls *emailLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
func (ls *emailLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name}
opts[i] = common.Option{key, lang.Meta.Name}
i++
}
return opts
@ -135,11 +137,11 @@ type setupLang struct {
JSON string
}
func (ls *setupLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
func (ls *setupLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name}
opts[i] = common.Option{key, lang.Meta.Name}
i++
}
return opts
@ -152,11 +154,11 @@ type telegramLang struct {
Strings langSection `json:"strings"`
}
func (ts *telegramLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ts))
func (ts *telegramLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ts))
i := 0
for key, lang := range *ts {
opts[i] = [2]string{key, lang.Meta.Name}
opts[i] = common.Option{key, lang.Meta.Name}
i++
}
return opts

View File

@ -1,3 +1,3 @@
module github.com/hrfee/jfa-go/logmessages
go 1.22.4
go 1.18

10
main.go
View File

@ -32,6 +32,7 @@ import (
"github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3"
"gopkg.in/ini.v1"
"gopkg.in/yaml.v3"
)
var (
@ -93,7 +94,8 @@ type appContext struct {
config *ini.File
configPath string
configBasePath string
configBase settings
configBase common.Config
patchedConfig common.Config
dataPath string
webFS httpFS
cssClass string // Default theme, "light"|"dark".
@ -388,9 +390,11 @@ func start(asDaemon, firstCall bool) {
defer app.storage.db.Close()
// Read config-base for settings on web.
app.configBasePath = "config-base.json"
app.configBasePath = "config-base.yaml"
configBase, _ := fs.ReadFile(localFS, app.configBasePath)
json.Unmarshal(configBase, &app.configBase)
yaml.Unmarshal(configBase, &app.configBase)
// copy it to app.patchedConfig, and patch in settings from app.config, and language stuff.
app.PatchConfigBase()
secret, err := generateSecret(16)
if err != nil {

View File

@ -212,43 +212,6 @@ type errorListDTO map[string]map[string]string
type configDTO map[string]interface{}
// Below are for sending config
type meta struct {
Name string `json:"name"`
Description string `json:"description"`
Advanced bool `json:"advanced,omitempty"`
DependsTrue string `json:"depends_true,omitempty"`
DependsFalse string `json:"depends_false,omitempty"`
WikiLink string `json:"wiki_link,omitempty"`
}
type setting struct {
Name string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Advanced bool `json:"advanced,omitempty"`
RequiresRestart bool `json:"requires_restart"`
Type string `json:"type"` // Type (string, number, bool, etc.)
Value interface{} `json:"value"`
Options [][2]string `json:"options,omitempty"`
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.
Style string `json:"style,omitempty"`
Deprecated bool `json:"deprecated,omitempty"`
}
type section struct {
Meta meta `json:"meta"`
Order []string `json:"order"`
Settings map[string]setting `json:"settings"`
}
type settings struct {
Order []string `json:"order"`
Sections map[string]section `json:"sections"`
}
type langDTO map[string]string
type emailListDTO map[string]emailListEl

View File

@ -2,7 +2,7 @@ module github.com/hrfee/jfa-go/ombi
replace github.com/hrfee/jfa-go/common => ../common
go 1.22.4
go 1.18
require github.com/hrfee/jfa-go/common v0.0.0-20240806200606-6308db495a0a

50
package-lock.json generated
View File

@ -22,7 +22,7 @@
"mjml": "^4.15.3",
"nightwind": "^1.1.13",
"perl-regex": "^1.0.4",
"postcss": "^8.4.24",
"postcss": "^8.4.31",
"remixicon": "^4.3.0",
"remove-markdown": "^0.5.0",
"tailwindcss": "^3.3.2",
@ -4669,9 +4669,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",
@ -6653,9 +6653,9 @@
}
},
"node_modules/uncss/node_modules/ws": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
"integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
"dependencies": {
"async-limiter": "~1.0.0"
}
@ -7167,9 +7167,9 @@
}
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"engines": {
"node": ">=0.10.0"
}
@ -7215,9 +7215,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"engines": {
"node": ">=10.0.0"
},
@ -10690,9 +10690,9 @@
"dev": true
},
"postcss": {
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"requires": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
@ -12180,9 +12180,9 @@
}
},
"ws": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
"integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
"requires": {
"async-limiter": "~1.0.0"
}
@ -12546,9 +12546,9 @@
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
},
"wrap-ansi": {
"version": "7.0.0",
@ -12576,9 +12576,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"requires": {}
},
"xml-name-validator": {

View File

@ -30,7 +30,7 @@
"mjml": "^4.15.3",
"nightwind": "^1.1.13",
"perl-regex": "^1.0.4",
"postcss": "^8.4.24",
"postcss": "^8.4.31",
"remixicon": "^4.3.0",
"remove-markdown": "^0.5.0",
"tailwindcss": "^3.3.2",

View File

@ -1,5 +1,5 @@
module github.com/hrfee/jfa-go/scripts/account-gen
go 1.20
go 1.18
require github.com/hrfee/mediabrowser v0.3.8 // indirect

View File

@ -1,52 +0,0 @@
import subprocess
import shutil
import os
import argparse
from pathlib import Path
from multiprocessing import Process
parser = argparse.ArgumentParser()
parser.add_argument("-o", "--output", help="output directory for .html and .txt files")
args = parser.parse_args()
def runcmd(cmd):
if os.name == "nt":
return subprocess.check_output(cmd, shell=True)
with subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) as proc:
return proc.communicate()
def compile(mjml: Path):
fname = mjml.with_suffix(".html")
runcmd(f"npx mjml {str(mjml)} -o {str(fname)}")
if fname.is_file():
print(f"Compiled {mjml.name}")
local_path = Path("mail")
threads = []
for mjml in [f for f in local_path.iterdir() if f.is_file() and "mjml" in f.suffix]:
p = Process(target=compile, args=(mjml,))
p.start()
threads.append(p)
for thread in threads:
thread.join()
html = [f for f in local_path.iterdir() if f.is_file() and "html" in f.suffix]
output = Path(args.output) # local_path.parent / "build" / "data"
output.mkdir(parents=True, exist_ok=True)
for f in html:
shutil.copy(str(f), str(output / f.name))
print(f"Copied {f.name} to {str(output / f.name)}")
txtfile = f.with_suffix(".txt")
if txtfile.is_file():
shutil.copy(str(txtfile), str(output / txtfile.name))
print(f"Copied {txtfile.name} to {str(output / txtfile.name)}")
else:
print(
f"Warning: {txtfile.name} does not exist. Text versions of emails should be supplied."
)

View File

@ -1,29 +0,0 @@
# Since go doesn't order its json, this script adds ordered lists
# of section/setting names for the settings tab to use.
import json, argparse
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input", help="input config base from jf-accounts")
parser.add_argument("-o", "--output", help="output config base for jfa-go")
args = parser.parse_args()
with open(args.input, 'r') as f:
config = json.load(f)
newconfig = {"sections": {}, "order": []}
for sect in config["sections"]:
newconfig["order"].append(sect)
newconfig["sections"][sect] = {}
newconfig["sections"][sect]["order"] = []
newconfig["sections"][sect]["meta"] = config["sections"][sect]["meta"]
newconfig["sections"][sect]["settings"] = {}
for setting in config["sections"][sect]["settings"]:
newconfig["sections"][sect]["order"].append(setting)
newconfig["sections"][sect]["settings"][setting] = config["sections"][sect]["settings"][setting]
with open(args.output, 'w') as f:
f.write(json.dumps(newconfig, indent=4))

View File

@ -1,47 +0,0 @@
# Generates config file
import configparser
import json
import argparse
from pathlib import Path
def fix_description(desc):
return "; " + desc.replace("\n", "\n; ")
def generate_ini(base_file, ini_file):
"""
Generates .ini file from config-base file.
"""
with open(Path(base_file), "r") as f:
config_base = json.load(f)
ini = configparser.RawConfigParser(allow_no_value=True)
for section in config_base["sections"]:
ini.add_section(section)
if "meta" in config_base["sections"][section]:
ini.set(section, fix_description(config_base["sections"][section]["meta"]["description"]))
for entry in config_base["sections"][section]["settings"]:
if config_base["sections"][section]["settings"][entry]["type"] == "note":
continue
if "description" in config_base["sections"][section]["settings"][entry]:
ini.set(section, fix_description(config_base["sections"][section]["settings"][entry]["description"]))
value = config_base["sections"][section]["settings"][entry]["value"]
if isinstance(value, bool):
value = str(value).lower()
else:
value = str(value)
ini.set(section, entry, value)
with open(Path(ini_file), "w") as config_file:
ini.write(config_file)
return True
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input", help="input config base from jf-accounts")
parser.add_argument("-o", "--output", help="output ini")
args = parser.parse_args()
print(generate_ini(base_file=args.input, ini_file=args.output))

12
scripts/ini/go.mod Normal file
View File

@ -0,0 +1,12 @@
module github.com/hrfee/jfa-go/scripts/ini
replace github.com/hrfee/jfa-go/common => ../../common
go 1.18
require (
github.com/hrfee/jfa-go/common v0.0.0-20240824141650-fcdd4e451882 // indirect
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

7
scripts/ini/go.sum Normal file
View File

@ -0,0 +1,7 @@
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a h1:qbXZgCqb9eaPSJfLEXczQD2lxTv6jb6silMPIWW9j6o=
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a/go.mod h1:c5HKkLayo0GrEUDlJwT12b67BL9cdPjP271Xlv/KDRQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

130
scripts/ini/main.go Normal file
View File

@ -0,0 +1,130 @@
package main
import (
"errors"
"flag"
"fmt"
"os"
"strings"
"github.com/hrfee/jfa-go/common"
"gopkg.in/ini.v1"
"gopkg.in/yaml.v3"
)
func fixDescription(desc string) string {
return "; " + strings.ReplaceAll(desc, "\n", "\n; ")
}
func generateIni(yamlPath string, iniPath string) {
yamlFile, err := os.ReadFile(yamlPath)
if err != nil {
panic(err)
}
configBase := common.Config{}
err = yaml.Unmarshal(yamlFile, &configBase)
if err != nil {
panic(err)
}
conf := ini.Empty()
for _, section := range configBase.Sections {
cSection, err := conf.NewSection(section.Section)
if err != nil {
panic(err)
}
if section.Meta.Description != "" {
cSection.Comment = fixDescription(section.Meta.Description)
}
for _, setting := range section.Settings {
if setting.Type == common.NoteType {
continue
}
val := ""
if setting.Value != nil {
// Easy way to convert bools and numbers to strings,
// Instead of checking setting.Type
val = fmt.Sprintf("%v", setting.Value)
}
cKey, err := cSection.NewKey(setting.Setting, val)
if err != nil {
panic(err)
}
if setting.Description != "" {
cKey.Comment = fixDescription(setting.Description)
}
// Explain how to use list type
if setting.Type == common.ListType {
if cKey.Comment != "" {
cKey.Comment += "\n"
}
cKey.Comment += `List type: duplicate and edit the line to add more entries.`
}
}
}
err = conf.SaveTo(iniPath)
if err != nil {
panic(err)
}
}
// Compares two inis, used to check this script does the equivalent of the old generate_ini.py.
func compareInis(p1, p2 string) {
cA, err := ini.ShadowLoad(p1)
if err != nil {
panic(err)
}
cB, err := ini.ShadowLoad(p2)
if err != nil {
panic(err)
}
for _, pair := range [][2]*ini.File{{cA, cB}, {cB, cA}} {
s1 := pair[0].Sections()
s2 := pair[1].Sections()
for i := range s1 {
if s1[i].Name() != s2[i].Name() {
panic(fmt.Errorf("mismatching section order: s0[i]=%s, s1[i]=%s", s1[i].Name(), s2[i].Name()))
}
// fmt.Println("Section order matches")
st1 := s1[i].Keys()
st2 := s2[i].Keys()
for i := range st1 {
if st1[i].Name() != st2[i].Name() {
panic(fmt.Errorf("mismatching setting order: st1[i]=%s, st2[i]=%s", st1[i].Name(), st2[i].Name()))
}
if st1[i].Value() != st2[i].Value() {
panic(fmt.Errorf("mismatching setting values: st1[i]=%s, st2[i]=%s", st1[i].Value(), st2[i].Value()))
}
// fmt.Println("Setting matches")
}
}
}
}
func main() {
var yamlPath string
var iniPath string
var comparePath string
flag.StringVar(&yamlPath, "in", "", "Input of the config base in yaml.")
flag.StringVar(&iniPath, "out", "", "Output path of an ini file.")
flag.StringVar(&comparePath, "comp", "", "Path to ini file to compare against.")
flag.Parse()
if yamlPath == "" {
panic(errors.New("invalid yaml path"))
}
if iniPath == "" {
panic(errors.New("invalid ini path"))
}
generateIni(yamlPath, iniPath)
if comparePath != "" {
compareInis(iniPath, comparePath)
fmt.Println("Passed.")
}
}

12
site/package-lock.json generated
View File

@ -4462,9 +4462,9 @@
}
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"engines": {
"node": ">=0.10.0"
}
@ -7828,9 +7828,9 @@
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
},
"wrappy": {
"version": "1.0.2",

View File

@ -19,20 +19,33 @@ interface settingsChangedEvent extends Event {
detail: string;
}
type SettingType = string;
const BoolType: SettingType = "bool";
const SelectType: SettingType = "select";
const TextType: SettingType = "text";
const PasswordType: SettingType = "password";
const NumberType: SettingType = "number";
const NoteType: SettingType = "note";
const EmailType: SettingType = "email";
const ListType: SettingType = "list";
interface Meta {
name: string;
description: string;
advanced?: boolean;
disabled?: boolean;
depends_true?: string;
depends_false?: string;
wiki_link?: string;
}
interface Setting {
setting: string;
name: string;
description: string;
required: boolean;
requires_restart: boolean;
required?: boolean;
requires_restart?: boolean;
advanced?: boolean;
type: string;
value: string | boolean | number | string[];
@ -67,17 +80,17 @@ class DOMSetting {
protected _restart: HTMLSpanElement;
protected _advanced: boolean;
protected _section: string;
protected _name: string;
setting: string;
hide = () => {
this._hideEl.classList.add("unfocused");
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": false })
const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": false })
document.dispatchEvent(event);
};
show = () => {
this._hideEl.classList.remove("unfocused");
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() })
const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": this.valueAsString() })
document.dispatchEvent(event);
};
@ -142,8 +155,8 @@ class DOMSetting {
valueAsString = (): string => { return ""+this.value; };
onValueChange = () => {
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() })
const setEvent = new CustomEvent(`settings-set-${this._section}-${this._name}`, { "detail": this.valueAsString() })
const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": this.valueAsString() })
const setEvent = new CustomEvent(`settings-set-${this._section}-${this.setting}`, { "detail": this.valueAsString() })
document.dispatchEvent(event);
document.dispatchEvent(setEvent);
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
@ -151,7 +164,7 @@ class DOMSetting {
constructor(input: string, setting: Setting, section: string, name: string, inputOnTop: boolean = false) {
this._section = section;
this._name = name;
this.setting = name;
this._container = document.createElement("div");
this._container.classList.add("setting");
this._container.setAttribute("data-name", name);
@ -223,7 +236,7 @@ interface SText extends Setting {
}
class DOMText extends DOMInput implements SText {
constructor(setting: Setting, section: string, name: string) { super("text", setting, section, name); }
type: string = "text";
type: SettingType = TextType;
get value(): string { return this._input.value }
set value(v: string) { this._input.value = v; }
}
@ -233,7 +246,7 @@ interface SPassword extends Setting {
}
class DOMPassword extends DOMInput implements SPassword {
constructor(setting: Setting, section: string, name: string) { super("password", setting, section, name); }
type: string = "password";
type: SettingType = PasswordType;
get value(): string { return this._input.value }
set value(v: string) { this._input.value = v; }
}
@ -243,7 +256,7 @@ interface SEmail extends Setting {
}
class DOMEmail extends DOMInput implements SEmail {
constructor(setting: Setting, section: string, name: string) { super("email", setting, section, name); }
type: string = "email";
type: SettingType = EmailType;
get value(): string { return this._input.value }
set value(v: string) { this._input.value = v; }
}
@ -253,7 +266,7 @@ interface SNumber extends Setting {
}
class DOMNumber extends DOMInput implements SNumber {
constructor(setting: Setting, section: string, name: string) { super("number", setting, section, name); }
type: string = "number";
type: SettingType = NumberType;
get value(): number { return +this._input.value; }
set value(v: number) { this._input.value = ""+v; }
}
@ -263,7 +276,7 @@ interface SList extends Setting {
}
class DOMList extends DOMSetting implements SList {
protected _inputs: HTMLDivElement;
type: string = "list";
type: SettingType = ListType;
valueAsString = (): string => { return this.value.join("|"); };
@ -334,7 +347,7 @@ interface SBool extends Setting {
value: boolean;
}
class DOMBool extends DOMSetting implements SBool {
type: string = "bool";
type: SettingType = BoolType;
get value(): boolean { return this._input.checked; }
set value(state: boolean) { this._input.checked = state; }
@ -357,7 +370,7 @@ interface SSelect extends Setting {
value: string;
}
class DOMSelect extends DOMSetting implements SSelect {
type: string = "bool";
type: SettingType = SelectType;
private _options: string[][];
get options(): string[][] { return this._options; }
@ -395,7 +408,7 @@ interface SNote extends Setting {
class DOMNote extends DOMSetting implements SNote {
private _nameEl: HTMLElement;
private _description: HTMLElement;
type: string = "note";
type: SettingType = NoteType;
private _style: string;
// We're a note, no one depends on us so we don't need to broadcast a state change.
@ -457,9 +470,9 @@ class DOMNote extends DOMSetting implements SNote {
}
interface Section {
section: string;
meta: Meta;
order: string[];
settings: { [settingName: string]: Setting };
settings: Setting[];
}
class sectionPanel {
@ -491,50 +504,49 @@ class sectionPanel {
this.update(s);
}
update = (s: Section) => {
for (let name of s.order) {
let setting: Setting = s.settings[name];
if (name in this._settings) {
this._settings[name].update(setting);
for (let setting of s.settings) {
if (setting.setting in this._settings) {
this._settings[setting.setting].update(setting);
} else {
if (setting.deprecated) continue;
switch (setting.type) {
case "text":
setting = new DOMText(setting, this._sectionName, name);
case TextType:
setting = new DOMText(setting, this._sectionName, setting.setting);
break;
case "password":
setting = new DOMPassword(setting, this._sectionName, name);
case PasswordType:
setting = new DOMPassword(setting, this._sectionName, setting.setting);
break;
case "email":
setting = new DOMEmail(setting, this._sectionName, name);
case EmailType:
setting = new DOMEmail(setting, this._sectionName, setting.setting);
break;
case "number":
setting = new DOMNumber(setting, this._sectionName, name);
case NumberType:
setting = new DOMNumber(setting, this._sectionName, setting.setting);
break;
case "bool":
setting = new DOMBool(setting as SBool, this._sectionName, name);
case BoolType:
setting = new DOMBool(setting as SBool, this._sectionName, setting.setting);
break;
case "select":
setting = new DOMSelect(setting as SSelect, this._sectionName, name);
case SelectType:
setting = new DOMSelect(setting as SSelect, this._sectionName, setting.setting);
break;
case "note":
case NoteType:
setting = new DOMNote(setting as SNote, this._sectionName);
break;
case "list":
setting = new DOMList(setting as SList, this._sectionName, name);
case ListType:
setting = new DOMList(setting as SList, this._sectionName, setting.setting);
break;
}
if (setting.type != "note") {
this.values[name] = ""+setting.value;
this.values[setting.setting] = ""+setting.value;
// settings-section-name: Implies the setting changed or was shown/hidden.
// settings-set-section-name: Implies the setting changed.
document.addEventListener(`settings-set-${this._sectionName}-${name}`, (event: CustomEvent) => {
document.addEventListener(`settings-set-${this._sectionName}-${setting.setting}`, (event: CustomEvent) => {
// const oldValue = this.values[name];
this.values[name] = event.detail;
this.values[setting.setting] = event.detail;
document.dispatchEvent(new CustomEvent("settings-section-changed"));
});
}
this._section.appendChild(setting.asElement());
this._settings[name] = setting;
this._settings[setting.setting] = setting;
}
}
}
@ -552,8 +564,7 @@ class sectionPanel {
}
interface Settings {
order: string[];
sections: { [sectionName: string]: Section };
sections: Section[];
}
export class settingsList {
@ -854,65 +865,65 @@ export class settingsList {
}
addLoader(this._loader, false, true);
_get("/config", null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status != 200) {
window.notifications.customError("settingsLoadError", window.lang.notif("errorLoadSettings"));
return;
}
this._settings = req.response as Settings;
for (let name of this._settings.order) {
if (name in this._sections) {
this._sections[name].update(this._settings.sections[name]);
} else {
if (name == "messages" || name == "user_page") {
const editButton = document.createElement("div");
editButton.classList.add("tooltip", "left");
editButton.innerHTML = `
<span class="button ~neutral @low">
<i class="icon ri-edit-line"></i>
</span>
<span class="content sm">
${window.lang.get("strings", "customizeMessages")}
</span>
`;
(editButton.querySelector("span.button") as HTMLSpanElement).onclick = () => {
this._messageEditor.showList(name == "messages" ? "email" : "user");
};
this.addSection(name, this._settings.sections[name], editButton);
} else if (name == "updates") {
const icon = document.createElement("span") as HTMLSpanElement;
if (window.updater.updateAvailable) {
icon.classList.add("button", "~urge");
icon.innerHTML = `<i class="ri-download-line" title="${window.lang.strings("update")}"></i>`;
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
}
this.addSection(name, this._settings.sections[name], icon);
} else if (name == "matrix" && !window.matrixEnabled) {
const addButton = document.createElement("div");
addButton.classList.add("tooltip", "left");
addButton.innerHTML = `
<span class="button ~neutral @low">+</span>
<span class="content sm">
${window.lang.strings("linkMatrix")}
</span>
`;
(addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix;
this.addSection(name, this._settings.sections[name], addButton);
} else {
this.addSection(name, this._settings.sections[name]);
if (req.readyState != 4) return;
if (req.status != 200) {
window.notifications.customError("settingsLoadError", window.lang.notif("errorLoadSettings"));
return;
}
this._settings = req.response as Settings;
for (let section of this._settings.sections) {
if (section.meta.disabled) continue;
if (section.section in this._sections) {
this._sections[section.section].update(section);
} else {
if (section.section == "messages" || section.section == "user_page") {
const editButton = document.createElement("div");
editButton.classList.add("tooltip", "left");
editButton.innerHTML = `
<span class="button ~neutral @low">
<i class="icon ri-edit-line"></i>
</span>
<span class="content sm">
${window.lang.get("strings", "customizeMessages")}
</span>
`;
(editButton.querySelector("span.button") as HTMLSpanElement).onclick = () => {
this._messageEditor.showList(section.section == "messages" ? "email" : "user");
};
this.addSection(section.section, section, editButton);
} else if (section.section == "updates") {
const icon = document.createElement("span") as HTMLSpanElement;
if (window.updater.updateAvailable) {
icon.classList.add("button", "~urge");
icon.innerHTML = `<i class="ri-download-line" title="${window.lang.strings("update")}"></i>`;
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
}
this.addSection(section.section, section, icon);
} else if (section.section == "matrix" && !window.matrixEnabled) {
const addButton = document.createElement("div");
addButton.classList.add("tooltip", "left");
addButton.innerHTML = `
<span class="button ~neutral @low">+</span>
<span class="content sm">
${window.lang.strings("linkMatrix")}
</span>
`;
(addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix;
this.addSection(section.section, section, addButton);
} else {
this.addSection(section.section, section);
}
}
removeLoader(this._loader);
for (let i = 0; i < this._loader.children.length; i++) {
this._loader.children[i].classList.remove("invisible");
}
this._showPanel(this._settings.order[0]);
document.dispatchEvent(new CustomEvent("settings-loaded"));
document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: false }));
this._saveButton.classList.add("unfocused");
this._needsRestart = false;
}
removeLoader(this._loader);
for (let i = 0; i < this._loader.children.length; i++) {
this._loader.children[i].classList.remove("invisible");
}
this._showPanel(this._settings.sections[0].section);
document.dispatchEvent(new CustomEvent("settings-loaded"));
document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: false }));
this._saveButton.classList.add("unfocused");
this._needsRestart = false;
})
};
@ -923,31 +934,31 @@ export class settingsList {
if (query.replace(/\s+/g, "") == "") query = "";
let firstVisibleSection = "";
for (let section of this._settings.order) {
for (let section of this._settings.sections) {
let dependencyCard = this._sections[section].asElement().querySelector(".settings-dependency-message");
let dependencyCard = this._sections[section.section].asElement().querySelector(".settings-dependency-message");
if (dependencyCard) dependencyCard.remove();
dependencyCard = null;
let dependencyList = null;
// hide button, unhide if matched
this._buttons[section].classList.add("unfocused");
this._buttons[section.section].classList.add("unfocused");
let matchedSection = false;
if (section.toLowerCase().includes(query) ||
this._settings.sections[section].meta.name.toLowerCase().includes(query) ||
this._settings.sections[section].meta.description.toLowerCase().includes(query)) {
if ((this._settings.sections[section].meta.advanced && this._advanced) || !(this._settings.sections[section].meta.advanced)) {
this._buttons[section].classList.remove("unfocused");
firstVisibleSection = firstVisibleSection || section;
if (section.section.toLowerCase().includes(query) ||
section.meta.name.toLowerCase().includes(query) ||
section.meta.description.toLowerCase().includes(query)) {
if ((section.meta.advanced && this._advanced) || !(section.meta.advanced)) {
this._buttons[section.section].classList.remove("unfocused");
firstVisibleSection = firstVisibleSection || section.section;
matchedSection = true;
}
}
const sectionElement = this._sections[section].asElement();
for (let setting of this._settings.sections[section].order) {
if (this._settings.sections[section].settings[setting].type == "note") continue;
const element = sectionElement.querySelector(`div[data-name="${setting}"]`) as HTMLElement;
const sectionElement = this._sections[section.section].asElement();
for (let setting of section.settings) {
if (setting.type == "note") continue;
const element = sectionElement.querySelector(`div[data-name="${setting.setting}"]`) as HTMLElement;
// If we match the whole section, don't bother searching settings.
if (matchedSection) {
@ -959,17 +970,17 @@ export class settingsList {
// element.classList.remove("-mx-2", "my-2", "p-2", "aside", "~neutral", "@low");
element.classList.add("opacity-50", "pointer-events-none");
element.setAttribute("aria-disabled", "true");
if (setting.toLowerCase().includes(query) ||
this._settings.sections[section].settings[setting].name.toLowerCase().includes(query) ||
this._settings.sections[section].settings[setting].description.toLowerCase().includes(query) ||
String(this._settings.sections[section].settings[setting].value).toLowerCase().includes(query)) {
if ((this._settings.sections[section].meta.advanced && this._advanced) || !(this._settings.sections[section].meta.advanced)) {
this._buttons[section].classList.remove("unfocused");
firstVisibleSection = firstVisibleSection || section;
if (setting.setting.toLowerCase().includes(query) ||
setting.name.toLowerCase().includes(query) ||
setting.description.toLowerCase().includes(query) ||
String(setting.value).toLowerCase().includes(query)) {
if ((section.meta.advanced && this._advanced) || !(section.meta.advanced)) {
this._buttons[section.section].classList.remove("unfocused");
firstVisibleSection = firstVisibleSection || section.section;
}
const shouldShow = (query != "" &&
((this._settings.sections[section].settings[setting].advanced && this._advanced) ||
!(this._settings.sections[section].settings[setting].advanced)));
((setting.advanced && this._advanced) ||
!(setting.advanced)));
if (shouldShow || query == "") {
// element.classList.add("-mx-2", "my-2", "p-2", "aside", "~neutral", "@low");
element.classList.remove("opacity-50", "pointer-events-none");
@ -989,21 +1000,21 @@ export class settingsList {
`;
dependencyList = dependencyCard.querySelector(".settings-dependency-list") as HTMLUListElement;
// Insert it right after the description
this._sections[section].asElement().insertBefore(dependencyCard, this._sections[section].asElement().querySelector(".settings-section-description").nextElementSibling);
this._sections[section.section].asElement().insertBefore(dependencyCard, this._sections[section.section].asElement().querySelector(".settings-section-description").nextElementSibling);
}
const li = document.createElement("li");
if (shouldShow) {
const depCode = this._settings.sections[section].settings[setting].depends_true || this._settings.sections[section].settings[setting].depends_false;
const dep = splitDependant(section, depCode);
const depCode = setting.depends_true || setting.depends_false;
const dep = splitDependant(section.section, depCode);
let depName = this._settings.sections[dep[0]].settings[dep[1]].name;
if (dep[0] != section) {
if (dep[0] != section.section) {
depName = this._settings.sections[dep[0]].meta.name + " > " + depName;
}
li.textContent = window.lang.strings("settingsDependsOn").replace("{setting}", `"`+this._settings.sections[section].settings[setting].name+`"`).replace("{dependency}", `"`+depName+`"`);
li.textContent = window.lang.strings("settingsDependsOn").replace("{setting}", `"`+setting.name+`"`).replace("{dependency}", `"`+depName+`"`);
} else {
li.textContent = window.lang.strings("settingsAdvancedMode").replace("{setting}", `"`+this._settings.sections[section].settings[setting].name+`"`);
li.textContent = window.lang.strings("settingsAdvancedMode").replace("{setting}", `"`+setting.name+`"`);
}
dependencyList.appendChild(li);
}