diff --git a/.gitignore b/.gitignore
index 131f700..392aa51 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,26 +1,10 @@
node_modules/
-passwordreset*.json
mail/*.html
-scss/*.css*
-scss/bs4/*.css*
-scss/bs5/*.css*
-data/static/*.css
-data/static/*.js
-data/static/*.js.map
-data/static/ts/
-data/static/modules/
-!data/static/setup.js
-data/config-base.json
-data/config-default.ini
-data/*.html
-data/*.txt
-data/docs/
-dist/*
-jfa-go
+dist/
build/
-pkg/
-old/
+data/
version.go
notes
docs/*
+config-payload.json
!docs/go.mod
diff --git a/.goreleaser.yml b/.goreleaser.yml
index f07b59b..1551ec0 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -7,14 +7,20 @@ release:
before:
hooks:
- go mod download
+ - rm -rf data/web
+ - mkdir -p data
+ - cp -r static data/web
+ - cp -r css data/web/
+ - npm install
+ - cp node_modules/a17t/dist/a17t.css data/web/css/
+ - cp node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 data/web/css/
+ - cp -r html data/
+ - cp -r lang data/
- python3 config/fixconfig.py -i config/config-base.json -o data/config-base.json
- python3 config/generate_ini.py -i config/config-base.json -o data/config-default.ini
- - python3 -m pip install libsass
- - npm install
- - python3 scss/compile.py
- - python3 mail/generate.py
+ - python3 mail/generate.py -o data/
- python3 version.py {{.Version}} version.go
- - bash -c 'npx esbuild ts/*.ts ts/modules/*.ts --outdir=data/static --minify'
+ - bash -c 'npx esbuild ts/*.ts ts/modules/*.ts --outdir=./data/web/js/ --minify'
- go get -u github.com/swaggo/swag/cmd/swag
- swag init -g main.go
builds:
@@ -37,9 +43,8 @@ archives:
amd64: x86_64
files:
- data/*
- - data/templates/*
- - data/static/*
- - data/static/modules/*
+ - data/**/*
+ - data/**/**/*
checksum:
name_template: 'checksums.txt'
snapshot:
diff --git a/Dockerfile b/Dockerfile
index ea15e75..7ddbe32 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,7 +7,7 @@ RUN apt update -y \
&& (curl -sL https://deb.nodesource.com/setup_14.x | bash -) \
&& apt install nodejs \
&& (cd /opt/build; make all; make compress) \
- && sed -i 's#id="pwrJfPath" placeholder="Folder"#id="pwrJfPath" value="/jf" disabled#g' /opt/build/build/data/templates/setup.html
+ && sed -i 's#id="pwrJfPath" placeholder="Folder"#id="pwrJfPath" value="/jf" disabled#g' /opt/build/build/data/html/setup.html
FROM golang:latest
diff --git a/Makefile b/Makefile
index d126cfb..6927345 100644
--- a/Makefile
+++ b/Makefile
@@ -1,33 +1,30 @@
+npm:
+ $(info installing npm dependencies)
+ npm install
+
configuration:
$(info Fixing config-base)
- python3 config/fixconfig.py -i config/config-base.json -o data/config-base.json
+ -mkdir -p build/data
+ python3 config/fixconfig.py -i config/config-base.json -o build/data/config-base.json
$(info Generating config-default.ini)
- python3 config/generate_ini.py -i config/config-base.json -o data/config-default.ini
-
-sass:
- $(info Getting libsass)
- python3 -m pip install libsass
- $(info Getting node dependencies)
- npm install
- $(info Compiling sass)
- python3 scss/compile.py
+ python3 config/generate_ini.py -i config/config-base.json -o build/data/config-default.ini
email:
$(info Generating email html)
- python3 mail/generate.py
+ python3 mail/generate.py -o build/data/
-typescript:
- $(info Compiling typescript)
- npx esbuild ts/*.ts ts/modules/*.ts --outdir=data/static --minify
- -rm -r data/static/ts
- -rm -r data/static/typings
- -rm data/static/*.map
+ts:
+ $(info compiling typescript)
+ -mkdir -p build/data/web/js
+ -npx esbuild ts/*.ts ts/modules/*.ts --outdir=./build/data/web/js/
ts-debug:
- -npx tsc -p ts/ --sourceMap
- -rm -r data/static/ts
- -rm -r data/static/typings
- cp -r ts data/static/
+ $(info compiling typescript w/ sourcemaps)
+ -mkdir -p build/data/web/js
+ -npx esbuild ts/*.ts ts/modules/*.ts --sourcemap --outdir=./build/data/web/js/
+ -rm -r build/data/web/js/ts
+ $(info copying typescript)
+ cp -r ts build/data/web/js
swagger:
go get github.com/swaggo/swag/cmd/swag
@@ -47,11 +44,22 @@ compress:
upx --lzma build/jfa-go
copy:
- $(info Copying data)
- cp -r data build/
+ $(info copying css)
+ -mkdir -p build/data/web/css
+ cp -r css build/data/web/
+ cp node_modules/a17t/dist/a17t.css build/data/web/css/
+ cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 build/data/web/css/
+ $(info copying html)
+ cp -r html build/data/
+ $(info copying static data)
+ -mkdir -p build/data/web
+ cp -r static/* build/data/web/
+ $(info copying language files)
+ cp -r lang build/data/
+
install:
cp -r build $(DESTDIR)/jfa-go
-all: configuration sass email version typescript swagger compile copy
-debug: configuration sass email version ts-debug swagger compile copy
+all: configuration npm email version ts swagger compile copy
+debug: configuration npm email version ts-debug swagger compile copy
diff --git a/README.md b/README.md
index d52f9b6..187d999 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# ![jfa-go](data/static/banner.svg)
+# ![jfa-go](images/banner.svg)
jfa-go is a user management app for [Jellyfin](https://github.com/jellyfin/jellyfin) that provides invite-based account creation as well as other features that make one's instance much easier to manage.
@@ -21,16 +21,15 @@ I chose to rewrite the python [jellyfin-accounts](https://github.com/hrfee/jelly
* 🌓 Customizable look
* Specify contact and help messages to appear in emails and pages
* Light and dark themes available
- * Optionally provide custom CSS
## Interface
-
+
-
-
+
+
#### Install
diff --git a/README.md.old b/README.md.old
new file mode 100644
index 0000000..c27a77e
--- /dev/null
+++ b/README.md.old
@@ -0,0 +1,38 @@
+This branch is for experimenting with [a17t](https://a17t.miles.land/) to replace bootstrap. Page structure is pretty much done (except setup.html), so i'm currently integrating this with the main app and existing web code.
+
+#### todo
+**general**
+* [x] modal implementation
+* [x] animations
+* [x] utilities
+* [x] CSS for light & dark
+
+**admin**
+* [x] invites tab
+* [x] accounts tab
+* [x] settings tab
+* [x] modals
+* [ ] integration with existing code
+
+**invites**
+* [x] page design
+* [ ] integration with existing code
+
+#### screenshots
+##### dark
+
+
+
+
+
+
+
+
+##### light
+
+
+
+
+
+
+
diff --git a/api.go b/api.go
index e01920f..9c46b78 100644
--- a/api.go
+++ b/api.go
@@ -667,6 +667,9 @@ func (app *appContext) DeleteProfile(gc *gin.Context) {
gc.BindJSON(&req)
name := req.Name
if _, ok := app.storage.profiles[name]; ok {
+ if app.storage.defaultProfile == name {
+ app.storage.defaultProfile = ""
+ }
delete(app.storage.profiles, name)
}
app.storage.storeProfiles()
@@ -1072,13 +1075,14 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
// @Summary Get jfa-go configuration.
// @Produce json
-// @Success 200 {object} configDTO "Uses the same format as config-base.json"
+// @Success 200 {object} settings "Uses the same format as config-base.json"
// @Router /config [get]
// @Security Bearer
// @tags Configuration
func (app *appContext) GetConfig(gc *gin.Context) {
app.info.Println("Config requested")
- resp := map[string]interface{}{}
+ resp := app.configBase
+ // Load language options
langPath := filepath.Join(app.localPath, "lang", "form")
app.lang.langFiles, _ = ioutil.ReadDir(langPath)
app.lang.langOptions = make([]string, len(app.lang.langFiles))
@@ -1095,37 +1099,25 @@ func (app *appContext) GetConfig(gc *gin.Context) {
app.lang.langOptions[i] = meta.(map[string]interface{})["name"].(string)
}
}
- for section, settings := range app.configBase {
- if section == "order" {
- resp[section] = settings.([]interface{})
- } else {
- resp[section] = make(map[string]interface{})
- for key, values := range settings.(map[string]interface{}) {
- if key == "order" {
- resp[section].(map[string]interface{})[key] = values.([]interface{})
- } else {
- resp[section].(map[string]interface{})[key] = values.(map[string]interface{})
- if key != "meta" {
- dataType := resp[section].(map[string]interface{})[key].(map[string]interface{})["type"].(string)
- configKey := app.config.Section(section).Key(key)
- if dataType == "number" {
- if val, err := configKey.Int(); err == nil {
- resp[section].(map[string]interface{})[key].(map[string]interface{})["value"] = val
- }
- } else if dataType == "bool" {
- resp[section].(map[string]interface{})[key].(map[string]interface{})["value"] = configKey.MustBool(false)
- } else if dataType == "select" && key == "language" {
- resp[section].(map[string]interface{})[key].(map[string]interface{})["options"] = app.lang.langOptions
- resp[section].(map[string]interface{})[key].(map[string]interface{})["value"] = app.lang.langOptions[app.lang.chosenIndex]
- } else {
- resp[section].(map[string]interface{})[key].(map[string]interface{})["value"] = configKey.String()
- }
- }
- }
+ s := resp.Sections["ui"].Settings["language"]
+ 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 "text", "email", "select", "password":
+ s.Value = val.MustString("")
+ case "number":
+ s.Value = val.MustInt(0)
+ case "bool":
+ s.Value = val.MustBool(false)
}
+ resp.Sections[sectName].Settings[settingName] = s
}
}
- // resp["jellyfin"].(map[string]interface{})["language"].(map[string]interface{})["options"].([]string)
+ s.Options = app.lang.langOptions
+ s.Value = app.lang.langOptions[app.lang.chosenIndex]
+ resp.Sections["ui"].Settings["language"] = s
gc.JSON(200, resp)
}
@@ -1176,11 +1168,11 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
if _, ok := req["password_validation"]; ok {
app.debug.Println("Reinitializing validator")
validatorConf := ValidatorConf{
- "characters": app.config.Section("password_validation").Key("min_length").MustInt(0),
- "uppercase characters": app.config.Section("password_validation").Key("upper").MustInt(0),
- "lowercase characters": app.config.Section("password_validation").Key("lower").MustInt(0),
- "numbers": app.config.Section("password_validation").Key("number").MustInt(0),
- "special characters": app.config.Section("password_validation").Key("special").MustInt(0),
+ "length": app.config.Section("password_validation").Key("min_length").MustInt(0),
+ "uppercase": app.config.Section("password_validation").Key("upper").MustInt(0),
+ "lowercase": app.config.Section("password_validation").Key("lower").MustInt(0),
+ "number": app.config.Section("password_validation").Key("number").MustInt(0),
+ "special": app.config.Section("password_validation").Key("special").MustInt(0),
}
if !app.config.Section("password_validation").Key("enabled").MustBool(false) {
for key := range validatorConf {
diff --git a/config/config-base.json b/config/config-base.json
index 21e95a2..9d5c920 100644
--- a/config/config-base.json
+++ b/config/config-base.json
@@ -1,668 +1,690 @@
{
- "jellyfin": {
- "meta": {
- "name": "Jellyfin",
- "description": "Settings for connecting to Jellyfin"
- },
- "username": {
- "name": "Jellyfin Username",
- "required": true,
- "requires_restart": true,
- "type": "text",
- "value": "username",
- "description": "It is recommended to create a limited admin account for this program."
- },
- "password": {
- "name": "Jellyfin Password",
- "required": true,
- "requires_restart": true,
- "type": "password",
- "value": "password"
- },
- "server": {
- "name": "Server address",
- "required": true,
- "requires_restart": true,
- "type": "text",
- "value": "http://jellyfin.local:8096",
- "description": "Jellyfin server address. Can be public, or local for security purposes."
- },
- "public_server": {
- "name": "Public address",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "https://jellyf.in:443",
- "description": "Publicly accessible Jellyfin address for invite form. Leave blank to reuse the above address."
- },
- "client": {
- "name": "Client Name",
- "required": true,
- "requires_restart": true,
- "type": "text",
- "value": "jfa-go",
- "description": "The name of the client that will show up in the Jellyfin dashboard."
- },
- "cache_timeout": {
- "name": "User cache timeout (minutes)",
- "required": false,
- "requires_restart": true,
- "type": "number",
- "value": 30,
- "description": "Timeout of user cache in minutes. Set to 0 to disable."
- }
- },
- "ui": {
- "meta": {
- "name": "General",
- "description": "Settings related to the UI and program functionality."
- },
- "language": {
- "name": "Language",
- "required": false,
- "requires_restart": true,
- "type": "select",
- "options": [
- "en-us"
- ],
- "value": "en-US",
- "description": "UI Language. Currently only implemented for account creation form. Submit a PR on github if you'd like to translate."
- },
- "theme": {
- "name": "Default Look",
- "required": false,
- "requires_restart": true,
- "type": "select",
- "options": [
- "Bootstrap (Light)",
- "Jellyfin (Dark)",
- "Custom CSS"
- ],
- "value": "Jellyfin (Dark)",
- "description": "Default appearance for all users."
- },
- "host": {
- "name": "Address",
- "required": true,
- "requires_restart": true,
- "type": "text",
- "value": "0.0.0.0",
- "description": "Set 0.0.0.0 to run on localhost"
- },
- "port": {
- "name": "Port",
- "required": true,
- "requires_restart": true,
- "type": "number",
- "value": 8056
- },
- "jellyfin_login": {
- "name": "Use Jellyfin for authentication",
- "required": false,
- "requires_restart": true,
- "type": "bool",
- "value": true,
- "description": "Enable this to use Jellyfin users instead of the below username and pw."
- },
- "admin_only": {
- "name": "Allow admin users only",
- "required": false,
- "requires_restart": true,
- "depends_true": "jellyfin_login",
- "type": "bool",
- "value": true,
- "description": "Allows only admin users on Jellyfin to access the admin page."
- },
- "username": {
- "name": "Web Username",
- "required": true,
- "requires_restart": true,
- "depends_false": "jellyfin_login",
- "type": "text",
- "value": "your username",
- "description": "Username for admin page (Leave blank if using jellyfin_login)"
- },
- "password": {
- "name": "Web Password",
- "required": true,
- "requires_restart": true,
- "depends_false": "jellyfin_login",
- "type": "password",
- "value": "your password",
- "description": "Password for admin page (Leave blank if using jellyfin_login)"
+ "order": [],
+ "sections": {
+ "jellyfin": {
+ "order": [],
+ "meta": {
+ "name": "Jellyfin",
+ "description": "Settings for connecting to Jellyfin"
+ },
+ "settings": {
+ "username": {
+ "name": "Jellyfin Username",
+ "required": true,
+ "requires_restart": true,
+ "type": "text",
+ "value": "username",
+ "description": "It is recommended to create a limited admin account for this program."
+ },
+ "password": {
+ "name": "Jellyfin Password",
+ "required": true,
+ "requires_restart": true,
+ "type": "password",
+ "value": "password"
+ },
+ "server": {
+ "name": "Server address",
+ "required": true,
+ "requires_restart": true,
+ "type": "text",
+ "value": "http://jellyfin.local:8096",
+ "description": "Jellyfin server address. Can be public, or local for security purposes."
+ },
+ "public_server": {
+ "name": "Public address",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "https://jellyf.in:443",
+ "description": "Publicly accessible Jellyfin address for invite form. Leave blank to reuse the above address."
+ },
+ "client": {
+ "name": "Client Name",
+ "required": true,
+ "requires_restart": true,
+ "type": "text",
+ "value": "jfa-go",
+ "description": "The name of the client that will show up in the Jellyfin dashboard."
+ },
+ "cache_timeout": {
+ "name": "User cache timeout (minutes)",
+ "required": false,
+ "requires_restart": true,
+ "type": "number",
+ "value": 30,
+ "description": "Timeout of user cache in minutes. Set to 0 to disable."
+ }
+ }
+ },
+ "ui": {
+ "order": [],
+ "meta": {
+ "name": "General",
+ "description": "Settings related to the UI and program functionality."
+ },
+ "settings": {
+ "language": {
+ "name": "Language",
+ "required": false,
+ "requires_restart": true,
+ "type": "select",
+ "options": [
+ "en-us"
+ ],
+ "value": "en-US",
+ "description": "UI Language. Currently only implemented for account creation form. Submit a PR on github if you'd like to translate."
+ },
+ "theme": {
+ "name": "Default Look",
+ "required": false,
+ "requires_restart": true,
+ "type": "select",
+ "options": [
+ "Jellyfin (Dark)",
+ "Default (Light)"
+ ],
+ "value": "Jellyfin (Dark)",
+ "description": "Default appearance for all users."
+ },
+ "host": {
+ "name": "Address",
+ "required": true,
+ "requires_restart": true,
+ "type": "text",
+ "value": "0.0.0.0",
+ "description": "Set 0.0.0.0 to run on localhost"
+ },
+ "port": {
+ "name": "Port",
+ "required": true,
+ "requires_restart": true,
+ "type": "number",
+ "value": 8056
+ },
+ "jellyfin_login": {
+ "name": "Use Jellyfin for authentication",
+ "required": false,
+ "requires_restart": true,
+ "type": "bool",
+ "value": true,
+ "description": "Enable this to use Jellyfin users instead of the below username and pw."
+ },
+ "admin_only": {
+ "name": "Allow admin users only",
+ "required": false,
+ "requires_restart": true,
+ "depends_true": "jellyfin_login",
+ "type": "bool",
+ "value": true,
+ "description": "Allows only admin users on Jellyfin to access the admin page."
+ },
+ "username": {
+ "name": "Web Username",
+ "required": true,
+ "requires_restart": true,
+ "depends_false": "jellyfin_login",
+ "type": "text",
+ "value": "your username",
+ "description": "Username for admin page (Leave blank if using jellyfin_login)"
+ },
+ "password": {
+ "name": "Web Password",
+ "required": true,
+ "requires_restart": true,
+ "depends_false": "jellyfin_login",
+ "type": "password",
+ "value": "your password",
+ "description": "Password for admin page (Leave blank if using jellyfin_login)"
+ },
+ "email": {
+ "name": "Admin email address",
+ "required": false,
+ "requires_restart": false,
+ "depends_false": "jellyfin_login",
+ "type": "text",
+ "value": "example@example.com",
+ "description": "Address to send notifications to (Leave blank if using jellyfin_login)"
+ },
+ "debug": {
+ "name": "Debug logging",
+ "required": false,
+ "requires_restart": true,
+ "type": "bool",
+ "value": false,
+ "description": "Enables debug logging and exposes pprof as a route (Don't use in production!)"
+ },
+ "contact_message": {
+ "name": "Contact message",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "Need help? contact me.",
+ "description": "Displayed at bottom of all pages except admin"
+ },
+ "help_message": {
+ "name": "Help message",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "Enter your details to create an account.",
+ "description": "Displayed at top of invite form."
+ },
+ "success_message": {
+ "name": "Success message",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "Your account has been created. Click below to continue to Jellyfin.",
+ "description": "Displayed when a user creates an account"
+ },
+ "url_base": {
+ "name": "URL Base",
+ "required": false,
+ "requires_restart": true,
+ "type": "text",
+ "value": "",
+ "description": "URL base for when running jfa-go with a reverse proxy in a subfolder."
+ }
+ }
+ },
+ "password_validation": {
+ "order": [],
+ "meta": {
+ "name": "Password Validation",
+ "description": "Password validation (minimum length, etc.)"
+ },
+ "settings": {
+ "enabled": {
+ "name": "Enabled",
+ "required": false,
+ "requires_restart": false,
+ "type": "bool",
+ "value": true
+ },
+ "min_length": {
+ "name": "Minimum Length",
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "8"
+ },
+ "upper": {
+ "name": "Minimum uppercase characters",
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "1"
+ },
+ "lower": {
+ "name": "Minimum lowercase characters",
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "0"
+ },
+ "number": {
+ "name": "Minimum number count",
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "1"
+ },
+ "special": {
+ "name": "Minimum number of special characters",
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "0"
+ }
+ }
},
"email": {
- "name": "Admin email address",
- "required": false,
- "requires_restart": false,
- "depends_false": "jellyfin_login",
- "type": "text",
- "value": "example@example.com",
- "description": "Address to send notifications to (Leave blank if using jellyfin_login)"
- },
- "debug": {
- "name": "Debug logging",
- "required": false,
- "requires_restart": true,
- "type": "bool",
- "value": false,
- "description": "Enables debug logging and exposes pprof as a route (Don't use in production!)"
- },
- "contact_message": {
- "name": "Contact message",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "Need help? contact me.",
- "description": "Displayed at bottom of all pages except admin"
- },
- "help_message": {
- "name": "Help message",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "Enter your details to create an account.",
- "description": "Displayed at top of invite form."
- },
- "success_message": {
- "name": "Success message",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "Your account has been created. Click below to continue to Jellyfin.",
- "description": "Displayed when a user creates an account"
- },
- "bs5": {
- "name": "Use Bootstrap 5",
- "required": false,
- "requires_restart": true,
- "type": "bool",
- "value": false,
- "description": "Use the Bootstrap 5 Alpha. Looks better and removes the need for jQuery, so the page should load faster."
- },
- "url_base": {
- "name": "URL Base",
- "required": false,
- "requires_restart": true,
- "type": "text",
- "value": "",
- "description": "URL base for when running jfa-go with a reverse proxy in a subfolder."
- }
- },
- "password_validation": {
- "meta": {
- "name": "Password Validation",
- "description": "Password validation (minimum length, etc.)"
- },
- "enabled": {
- "name": "Enabled",
- "required": false,
- "requires_restart": false,
- "type": "bool",
- "value": true
- },
- "min_length": {
- "name": "Minimum Length",
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "8"
- },
- "upper": {
- "name": "Minimum uppercase characters",
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "1"
- },
- "lower": {
- "name": "Minimum lowercase characters",
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "0"
- },
- "number": {
- "name": "Minimum number count",
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "1"
- },
- "special": {
- "name": "Minimum number of special characters",
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "0"
- }
- },
- "email": {
- "meta": {
- "name": "Email",
- "description": "General email settings. Ignore if not using email features."
- },
- "no_username": {
- "name": "Use email addresses as username",
- "required": false,
- "requires_restart": false,
- "depends_true": "method",
- "type": "bool",
- "value": false,
- "description": "Use email address from invite form as username on Jellyfin."
- },
- "use_24h": {
- "name": "Use 24h time",
- "required": false,
- "requires_restart": false,
- "depends_true": "method",
- "type": "bool",
- "value": true
- },
- "date_format": {
- "name": "Date format",
- "required": false,
- "requires_restart": false,
- "depends_true": "method",
- "type": "text",
- "value": "%d/%m/%y",
- "description": "Date format used in emails. Follows datetime.strftime format."
- },
- "message": {
- "name": "Help message",
- "required": false,
- "requires_restart": false,
- "depends_true": "method",
- "type": "text",
- "value": "Need help? contact me.",
- "description": "Message displayed at bottom of emails."
- },
- "method": {
- "name": "Email method",
- "required": false,
- "requires_restart": false,
- "type": "select",
- "options": [
- "smtp",
- "mailgun"
- ],
- "value": "smtp",
- "description": "Method of sending email to use."
- },
- "address": {
- "name": "Sent from (address)",
- "required": false,
- "requires_restart": false,
- "depends_true": "method",
- "type": "email",
- "value": "jellyfin@jellyf.in",
- "description": "Address to send emails from"
- },
- "from": {
- "name": "Sent from (name)",
- "required": false,
- "requires_restart": false,
- "depends_true": "method",
- "type": "text",
- "value": "Jellyfin",
- "description": "The name of the sender"
- }
- },
- "password_resets": {
- "meta": {
- "name": "Password Resets",
- "description": "Settings for the password reset handler."
- },
- "enabled": {
- "name": "Enabled",
- "required": false,
- "requires_restart": true,
- "type": "bool",
- "value": true,
- "description": "Enable to store provided email addresses, monitor Jellyfin directory for pw-resets, and send reset pins"
- },
- "watch_directory": {
- "name": "Jellyfin directory",
- "required": false,
- "requires_restart": true,
- "depends_true": "enabled",
- "type": "text",
- "value": "/path/to/jellyfin",
- "description": "Path to the folder Jellyfin puts password-reset files."
- },
- "email_html": {
- "name": "Custom email (HTML)",
- "required": false,
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "",
- "description": "Path to custom email html"
- },
- "email_text": {
- "name": "Custom email (plaintext)",
- "required": false,
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "",
- "description": "Path to custom email in plain text"
- },
- "subject": {
- "name": "Email subject",
- "required": false,
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "Password Reset - Jellyfin",
- "description": "Subject of password reset emails."
- }
- },
- "invite_emails": {
- "meta": {
- "name": "Invite emails",
- "description": "Settings for sending invites directly to users."
- },
- "enabled": {
- "name": "Enabled",
- "required": false,
- "requires_restart": false,
- "type": "bool",
- "value": true
- },
- "email_html": {
- "name": "Custom email (HTML)",
- "required": false,
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "",
- "description": "Path to custom email HTML"
- },
- "email_text": {
- "name": "Custom email (plaintext)",
- "required": false,
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "",
- "description": "Path to custom email in plain text"
- },
- "subject": {
- "name": "Email subject",
- "required": true,
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "Invite - Jellyfin",
- "description": "Subject of invite emails."
- },
- "url_base": {
- "name": "URL Base",
- "required": true,
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "http://accounts.jellyf.in:8056/invite",
- "description": "Base URL for jfa-go. This is necessary because using a reverse proxy means the program has no way of knowing the URL itself."
- }
- },
- "notifications": {
- "meta": {
- "name": "Notifications",
- "description": "Notification related settings."
- },
- "enabled": {
- "name": "Enabled",
- "required": "false",
- "requires_restart": true,
- "type": "bool",
- "value": true,
- "description": "Enabling adds optional toggles to invites to notify on expiry and user creation."
- },
- "expiry_html": {
- "name": "Expiry email (HTML)",
- "required": false,
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "",
- "description": "Path to expiry notification email HTML."
- },
- "expiry_text": {
- "name": "Expiry email (Plaintext)",
- "required": false,
- "requires_restart": "false",
- "depends_true": "enabled",
- "type": "text",
- "value": "",
- "description": "Path to expiry notification email in plaintext."
- },
- "created_html": {
- "name": "User created email (HTML)",
- "required": false,
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "",
- "description": "Path to user creation notification email HTML."
- },
- "created_text": {
- "name": "User created email (Plaintext)",
- "required": false,
- "requires_restart": false,
- "depends_true": "enabled",
- "type": "text",
- "value": "",
- "description": "Path to user creation notification email in plaintext."
- }
- },
- "mailgun": {
- "meta": {
- "name": "Mailgun (Email)",
- "description": "Mailgun API connection settings"
- },
- "api_url": {
- "name": "API URL",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "https://api.mailgun.net..."
- },
- "api_key": {
- "name": "API Key",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "your api key"
- }
- },
- "smtp": {
- "meta": {
- "name": "SMTP (Email)",
- "description": "SMTP Server connection settings."
- },
- "username": {
- "name": "Username",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "",
- "description": "Username for SMTP. Leave blank to user send from address as username."
- },
- "encryption": {
- "name": "Encryption Method",
- "required": false,
- "requires_restart": false,
- "type": "select",
- "options": [
- "ssl_tls",
- "starttls"
- ],
- "value": "starttls",
- "description": "Your email provider should provide different ports for each encryption method. Generally 465 for ssl_tls, 587 for starttls."
- },
- "server": {
- "name": "Server address",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "smtp.jellyf.in",
- "description": "SMTP Server address."
- },
- "port": {
- "name": "Port",
- "required": false,
- "requires_restart": false,
- "type": "number",
- "value": 465
- },
- "password": {
- "name": "Password",
- "required": false,
- "requires_restart": false,
- "type": "password",
- "value": "smtp password"
- }
- },
- "ombi": {
- "meta": {
- "name": "Ombi Integration",
- "description": "Connect to Ombi to automatically create both Ombi and Jellyfin accounts for new users. You'll need to create a user template for this to work. Once enabled, refresh to see an option in settings for this."
- },
- "enabled": {
- "name": "Enabled",
- "required": false,
- "requires_restart": true,
- "type": "bool",
- "value": false,
- "description": "Enable to create an Ombi account for new Jellyfin users"
- },
- "server": {
- "name": "URL",
- "required": false,
- "requires_restart": true,
- "type": "text",
- "value": "localhost:5000",
- "depends_true": "enabled",
- "description": "Ombi server URL, including http(s)://."
- },
- "api_key": {
- "name": "API Key",
- "required": false,
- "requires_restart": true,
- "type": "text",
- "value": "",
- "depends_true": "enabled",
- "description": "API Key. Get this from the first tab in Ombi settings."
- }
- },
- "deletion": {
- "meta": {
- "name": "Account Deletion",
- "description": "Subject/email files for account deletion emails."
- },
- "subject": {
- "name": "Email subject",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "Your account was deleted - Jellyfin",
- "description": "Subject of account deletion emails."
- },
- "email_html": {
- "name": "Custom email (HTML)",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "",
- "description": "Path to custom email html"
- },
- "email_text": {
- "name": "Custom email (plaintext)",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "",
- "description": "Path to custom email in plain text"
- }
- },
- "files": {
- "meta": {
- "name": "File Storage",
- "description": "Optional settings for changing storage locations."
- },
- "invites": {
- "name": "Invite Storage",
- "required": false,
- "requires_restart": true,
- "type": "text",
- "value": "",
- "description": "Location of stored invites (json)."
- },
- "emails": {
- "name": "Email Addresses",
- "required": false,
- "requires_restart": true,
- "type": "text",
- "value": "",
- "description": "Location of stored email addresses (json)."
- },
- "ombi_template": {
- "name": "Ombi user template",
- "required": false,
- "requires_restart": false,
- "type": "text",
- "value": "",
- "description": "Location of stored Ombi user template."
- },
- "user_template": {
- "name": "User Template (Deprecated)",
- "required": false,
- "requires_restart": true,
- "type": "text",
- "value": "",
- "description": "Deprecated in favor of User Profiles. Location of stored user policy template (json)."
- },
- "user_configuration": {
- "name": "userConfiguration (Deprecated)",
- "required": false,
- "requires_restart": true,
- "type": "text",
- "value": "",
- "description": "Deprecated in favor of User Profiles. Location of stored user configuration template (used for setting homescreen layout) (json)"
- },
- "user_displayprefs": {
- "name": "displayPreferences (Deprecated)",
- "required": false,
- "requires_restart": true,
- "type": "text",
- "value": "",
- "description": "Deprecated in favor of User Profiles. Location of stored displayPreferences template (also used for homescreen layout) (json)"
- },
- "user_profiles": {
- "name": "User Profiles",
- "required": false,
- "requires_restart": true,
- "type": "text",
- "value": "",
- "description": "Location of stored user profiles (encompasses template and configuration and displayprefs) (json)"
- },
- "custom_css": {
- "name": "Custom CSS",
- "required": false,
- "requires_restart": true,
- "type": "text",
- "value": "",
- "description": "Location of custom bootstrap CSS."
- },
- "html_templates": {
- "name": "Custom HTML Template Directory",
- "required": false,
- "requires_restart": true,
- "type": "text",
- "value": "",
- "description": "Path to directory containing custom versions of web ui pages. See wiki for more info."
+ "order": [],
+ "meta": {
+ "name": "Email",
+ "description": "General email settings. Ignore if not using email features."
+ },
+ "settings": {
+ "no_username": {
+ "name": "Use email addresses as username",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "method",
+ "type": "bool",
+ "value": false,
+ "description": "Use email address from invite form as username on Jellyfin."
+ },
+ "use_24h": {
+ "name": "Use 24h time",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "method",
+ "type": "bool",
+ "value": true
+ },
+ "date_format": {
+ "name": "Date format",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "method",
+ "type": "text",
+ "value": "%d/%m/%y",
+ "description": "Date format used in emails. Follows datetime.strftime format."
+ },
+ "message": {
+ "name": "Help message",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "method",
+ "type": "text",
+ "value": "Need help? contact me.",
+ "description": "Message displayed at bottom of emails."
+ },
+ "method": {
+ "name": "Email method",
+ "required": false,
+ "requires_restart": false,
+ "type": "select",
+ "options": [
+ "smtp",
+ "mailgun"
+ ],
+ "value": "smtp",
+ "description": "Method of sending email to use."
+ },
+ "address": {
+ "name": "Sent from (address)",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "method",
+ "type": "email",
+ "value": "jellyfin@jellyf.in",
+ "description": "Address to send emails from"
+ },
+ "from": {
+ "name": "Sent from (name)",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "method",
+ "type": "text",
+ "value": "Jellyfin",
+ "description": "The name of the sender"
+ }
+ }
+ },
+ "password_resets": {
+ "order": [],
+ "meta": {
+ "name": "Password Resets",
+ "description": "Settings for the password reset handler."
+ },
+ "settings": {
+ "enabled": {
+ "name": "Enabled",
+ "required": false,
+ "requires_restart": true,
+ "type": "bool",
+ "value": true,
+ "description": "Enable to store provided email addresses, monitor Jellyfin directory for pw-resets, and send reset pins"
+ },
+ "watch_directory": {
+ "name": "Jellyfin directory",
+ "required": false,
+ "requires_restart": true,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "/path/to/jellyfin",
+ "description": "Path to the folder Jellyfin puts password-reset files."
+ },
+ "email_html": {
+ "name": "Custom email (HTML)",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "",
+ "description": "Path to custom email html"
+ },
+ "email_text": {
+ "name": "Custom email (plaintext)",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "",
+ "description": "Path to custom email in plain text"
+ },
+ "subject": {
+ "name": "Email subject",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "Password Reset - Jellyfin",
+ "description": "Subject of password reset emails."
+ }
+ }
+ },
+ "invite_emails": {
+ "order": [],
+ "meta": {
+ "name": "Invite emails",
+ "description": "Settings for sending invites directly to users."
+ },
+ "settings": {
+ "enabled": {
+ "name": "Enabled",
+ "required": false,
+ "requires_restart": false,
+ "type": "bool",
+ "value": true
+ },
+ "email_html": {
+ "name": "Custom email (HTML)",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "",
+ "description": "Path to custom email HTML"
+ },
+ "email_text": {
+ "name": "Custom email (plaintext)",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "",
+ "description": "Path to custom email in plain text"
+ },
+ "subject": {
+ "name": "Email subject",
+ "required": true,
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "Invite - Jellyfin",
+ "description": "Subject of invite emails."
+ },
+ "url_base": {
+ "name": "URL Base",
+ "required": true,
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "http://accounts.jellyf.in:8056/invite",
+ "description": "Base URL for jfa-go. This is necessary because using a reverse proxy means the program has no way of knowing the URL itself."
+ }
+ }
+ },
+ "notifications": {
+ "order": [],
+ "meta": {
+ "name": "Notifications",
+ "description": "Notification related settings."
+ },
+ "settings": {
+ "enabled": {
+ "name": "Enabled",
+ "required": "false",
+ "requires_restart": true,
+ "type": "bool",
+ "value": true,
+ "description": "Enabling adds optional toggles to invites to notify on expiry and user creation."
+ },
+ "expiry_html": {
+ "name": "Expiry email (HTML)",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "",
+ "description": "Path to expiry notification email HTML."
+ },
+ "expiry_text": {
+ "name": "Expiry email (Plaintext)",
+ "required": false,
+ "requires_restart": "false",
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "",
+ "description": "Path to expiry notification email in plaintext."
+ },
+ "created_html": {
+ "name": "User created email (HTML)",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "",
+ "description": "Path to user creation notification email HTML."
+ },
+ "created_text": {
+ "name": "User created email (Plaintext)",
+ "required": false,
+ "requires_restart": false,
+ "depends_true": "enabled",
+ "type": "text",
+ "value": "",
+ "description": "Path to user creation notification email in plaintext."
+ }
+ }
+ },
+ "mailgun": {
+ "order": [],
+ "meta": {
+ "name": "Mailgun (Email)",
+ "description": "Mailgun API connection settings"
+ },
+ "settings": {
+ "api_url": {
+ "name": "API URL",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "https://api.mailgun.net..."
+ },
+ "api_key": {
+ "name": "API Key",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "your api key"
+ }
+ }
+ },
+ "smtp": {
+ "order": [],
+ "meta": {
+ "name": "SMTP (Email)",
+ "description": "SMTP Server connection settings."
+ },
+ "settings": {
+ "username": {
+ "name": "Username",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "",
+ "description": "Username for SMTP. Leave blank to user send from address as username."
+ },
+ "encryption": {
+ "name": "Encryption Method",
+ "required": false,
+ "requires_restart": false,
+ "type": "select",
+ "options": [
+ "ssl_tls",
+ "starttls"
+ ],
+ "value": "starttls",
+ "description": "Your email provider should provide different ports for each encryption method. Generally 465 for ssl_tls, 587 for starttls."
+ },
+ "server": {
+ "name": "Server address",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "smtp.jellyf.in",
+ "description": "SMTP Server address."
+ },
+ "port": {
+ "name": "Port",
+ "required": false,
+ "requires_restart": false,
+ "type": "number",
+ "value": 465
+ },
+ "password": {
+ "name": "Password",
+ "required": false,
+ "requires_restart": false,
+ "type": "password",
+ "value": "smtp password"
+ }
+ }
+ },
+ "ombi": {
+ "order": [],
+ "meta": {
+ "name": "Ombi Integration",
+ "description": "Connect to Ombi to automatically create both Ombi and Jellyfin accounts for new users. You'll need to create a user template for this to work. Once enabled, refresh to see an option in settings for this."
+ },
+ "settings": {
+ "enabled": {
+ "name": "Enabled",
+ "required": false,
+ "requires_restart": true,
+ "type": "bool",
+ "value": false,
+ "description": "Enable to create an Ombi account for new Jellyfin users"
+ },
+ "server": {
+ "name": "URL",
+ "required": false,
+ "requires_restart": true,
+ "type": "text",
+ "value": "localhost:5000",
+ "depends_true": "enabled",
+ "description": "Ombi server URL, including http(s)://."
+ },
+ "api_key": {
+ "name": "API Key",
+ "required": false,
+ "requires_restart": true,
+ "type": "text",
+ "value": "",
+ "depends_true": "enabled",
+ "description": "API Key. Get this from the first tab in Ombi settings."
+ }
+ }
+ },
+ "deletion": {
+ "order": [],
+ "meta": {
+ "name": "Account Deletion",
+ "description": "Subject/email files for account deletion emails."
+ },
+ "settings": {
+ "subject": {
+ "name": "Email subject",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "Your account was deleted - Jellyfin",
+ "description": "Subject of account deletion emails."
+ },
+ "email_html": {
+ "name": "Custom email (HTML)",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "",
+ "description": "Path to custom email html"
+ },
+ "email_text": {
+ "name": "Custom email (plaintext)",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "",
+ "description": "Path to custom email in plain text"
+ }
+ }
+ },
+ "files": {
+ "order": [],
+ "meta": {
+ "name": "File Storage",
+ "description": "Optional settings for changing storage locations."
+ },
+ "settings": {
+ "invites": {
+ "name": "Invite Storage",
+ "required": false,
+ "requires_restart": true,
+ "type": "text",
+ "value": "",
+ "description": "Location of stored invites (json)."
+ },
+ "emails": {
+ "name": "Email Addresses",
+ "required": false,
+ "requires_restart": true,
+ "type": "text",
+ "value": "",
+ "description": "Location of stored email addresses (json)."
+ },
+ "ombi_template": {
+ "name": "Ombi user template",
+ "required": false,
+ "requires_restart": false,
+ "type": "text",
+ "value": "",
+ "description": "Location of stored Ombi user template."
+ },
+ "user_template": {
+ "name": "User Template (Deprecated)",
+ "required": false,
+ "requires_restart": true,
+ "type": "text",
+ "value": "",
+ "description": "Deprecated in favor of User Profiles. Location of stored user policy template (json)."
+ },
+ "user_configuration": {
+ "name": "userConfiguration (Deprecated)",
+ "required": false,
+ "requires_restart": true,
+ "type": "text",
+ "value": "",
+ "description": "Deprecated in favor of User Profiles. Location of stored user configuration template (used for setting homescreen layout) (json)"
+ },
+ "user_displayprefs": {
+ "name": "displayPreferences (Deprecated)",
+ "required": false,
+ "requires_restart": true,
+ "type": "text",
+ "value": "",
+ "description": "Deprecated in favor of User Profiles. Location of stored displayPreferences template (also used for homescreen layout) (json)"
+ },
+ "user_profiles": {
+ "name": "User Profiles",
+ "required": false,
+ "requires_restart": true,
+ "type": "text",
+ "value": "",
+ "description": "Location of stored user profiles (encompasses template and configuration and displayprefs) (json)"
+ },
+ "html_templates": {
+ "name": "Custom HTML Template Directory",
+ "required": false,
+ "requires_restart": true,
+ "type": "text",
+ "value": "",
+ "description": "Path to directory containing custom versions of web ui pages. See wiki for more info."
+ }
+ }
}
}
}
diff --git a/config/fixconfig.py b/config/fixconfig.py
index 180f2e4..2cf90b4 100644
--- a/config/fixconfig.py
+++ b/config/fixconfig.py
@@ -9,17 +9,17 @@ args = parser.parse_args()
with open(args.input, 'r') as f:
config = json.load(f)
-newconfig = {"order": []}
+newconfig = {"sections": {}, "order": []}
-for sect in config:
+for sect in config["sections"]:
newconfig["order"].append(sect)
- newconfig[sect] = {}
- newconfig[sect]["order"] = []
- newconfig[sect]["meta"] = config[sect]["meta"]
- for setting in config[sect]:
- if setting != "meta":
- newconfig[sect]["order"].append(setting)
- newconfig[sect][setting] = config[sect][setting]
+ 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))
diff --git a/config/generate_ini.py b/config/generate_ini.py
index efc8c0a..a801b8b 100644
--- a/config/generate_ini.py
+++ b/config/generate_ini.py
@@ -14,18 +14,19 @@ def generate_ini(base_file, ini_file):
ini = configparser.RawConfigParser(allow_no_value=True)
- for section in config_base:
+ for section in config_base["sections"]:
ini.add_section(section)
- for entry in config_base[section]:
- if "description" in config_base[section][entry]:
- ini.set(section, "; " + config_base[section][entry]["description"])
- if entry != "meta":
- value = config_base[section][entry]["value"]
- if isinstance(value, bool):
- value = str(value).lower()
- else:
- value = str(value)
- ini.set(section, entry, value)
+ if "meta" in config_base["sections"][section]:
+ ini.set(section, "; " + config_base["sections"][section]["meta"]["description"])
+ for entry in config_base["sections"][section]["settings"]:
+ if "description" in config_base["sections"][section]["settings"][entry]:
+ ini.set(section, "; " + 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)
diff --git a/css/base.css b/css/base.css
new file mode 100644
index 0000000..2b919d2
--- /dev/null
+++ b/css/base.css
@@ -0,0 +1,372 @@
+@import "a17t.css";
+@import "remixicon.css";
+@import "modal.css";
+@import "dark.css";
+@import "tooltip.css";
+@import "loader.css";
+
+:root {
+ --border-width-default: 2px;
+ --border-width-2: 3px;
+ --border-width-4: 5px;
+ --border-width-8: 8px;
+}
+
+.light-theme {
+ --settings-section-button-filter: 90%;
+}
+
+.body {
+ background-color: #101010;
+}
+
+.page-container {
+ margin: 5% 20% 5% 20%;
+}
+
+@media (max-width: 1100px) {
+ .page-container {
+ margin: 2%;
+ }
+}
+
+@media screen and (max-width: 750px) {
+ :root {
+ font-size: 0.9rem;
+ }
+ .table-responsive table {
+ min-width: 660px;
+ }
+}
+
+
+.tab-button {
+ font-size: 2rem;
+}
+
+.mb-half {
+ margin-bottom: 0.5rem;
+}
+
+.mb-1 {
+ margin-bottom: 1rem;
+}
+
+.mb-2 {
+ margin-bottom: 2rem;
+}
+
+.mt-half {
+ margin-top: 0.5rem;
+}
+
+.mt-1 {
+ margin-top: 1rem;
+}
+
+.ml-1 {
+ margin-left: 1rem;
+}
+
+.ml-half {
+ margin-left: 0.5rem;
+}
+
+.mr-1 {
+ margin-right: 1rem;
+}
+
+.pb-1 {
+ padding-bottom: 1rem;
+}
+
+.pl-1 {
+ padding-left: 1rem;
+}
+
+.al {
+ text-align: left;
+}
+
+.ar {
+ text-align: right;
+}
+
+.inline-block {
+ display: inline-block;
+}
+
+.align-top {
+ align-items: top;
+}
+
+.flex {
+ display: flex;
+}
+
+.flex-expand {
+ display: flex;
+ justify-content: space-between;
+}
+
+.flex-row {
+ display: flex;
+ flex-direction: row;
+}
+
+.flex-row-group {
+ display: block;
+ flex-grow: 1;
+}
+
+.row {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.row.baseline {
+ align-items: baseline;
+}
+
+.col {
+ flex: 1;
+ margin: 0.5rem;
+}
+
+@media screen and (max-width: 400px) {
+ .row {
+ flex-direction: column;
+ }
+ .col {
+ flex: 45%;
+ }
+}
+
+.fr {
+ float: right;
+}
+
+.monospace {
+ background-color: inherit; /* so we can use a17t code blocks */
+}
+
+sup.\~critical, .text-critical {
+ color: var(--color-critical-normal-content);
+}
+
+.grey {
+ color: var(--color-neutral-500);
+}
+
+.aside.sm {
+ font-size: 0.8rem;
+ padding: 0.8rem;
+}
+
+.support.lg {
+ font-size: 1rem;
+}
+
+.badge.lg {
+ font-size: 1rem;
+}
+
+.inv-created-users strong,p {
+ padding-left: 0.5rem;
+ padding-bottom: 0.2rem;
+}
+
+.inv-created-users.empty strong,p {
+ padding: 0;
+}
+
+.inv {
+ overflow: visible;
+}
+
+.inv-table {
+ font-size: 0.8rem;
+}
+
+.inv-profilearea {
+ min-width: 20%;
+}
+
+.inv-profileselect {
+ min-width: 100%;
+}
+
+.inv-codearea {
+ max-width: 40%;
+ min-width: 10rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.inv-empty .inv-codearea {
+ justify-content: start;
+}
+
+
+.invite-link {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ width: auto;
+}
+
+.ellipsis {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.no-pad {
+ padding: 0px 0px 0px 0px;
+}
+
+.elem-pad > * {
+ margin: var(--spacing-4, 1rem);
+}
+
+.icon.clickable {
+ padding: 0.5rem 0.6rem;
+}
+
+.input {
+ box-sizing: border-box; /* fixes weird length issue with inputs */
+}
+
+.button.lg {
+ height: 2.5rem;
+}
+
+.submit {
+ border: none;
+ outline: none; /* remove browser styling on submit buttons */
+}
+
+.full-width { /* full width inputs */
+ box-sizing: border-box; /* TODO: maybe remove if we figure out input thing? */
+ width: 100%;
+}
+
+.center {
+ justify-content: center;
+}
+
+.no-lp {
+ padding-left: 0px;
+}
+
+.block {
+ display: block;
+}
+
+.focused {
+ display: block;
+}
+
+.unfocused {
+ display: none;
+}
+
+.rotated {
+ transform: rotate(180deg);
+ -webkit-transition: all 0.3s cubic-bezier(0,.89,.27,.92);
+ -moz-transition: all 0.3s cubic-bezier(0,.89,.27,.92);
+ -o-transition: all 0.3s cubic-bezier(0,.89,.27,.92);
+ transition: all 0.3s cubic-bezier(0,.89,.27,.92);
+}
+
+.not-rotated {
+ transform: rotate(0deg);
+ -webkit-transition: all 0.3s cubic-bezier(0,.89,.27,.92);
+ -moz-transition: all 0.3s cubic-bezier(0,.89,.27,.92);
+ -o-transition: all 0.3s cubic-bezier(0,.89,.27,.92);
+ transition: all 0.3s cubic-bezier(0,.89,.27,.92);
+}
+
+.stealth-input {
+ font-size: 1rem;
+ padding-top: 0.1rem;
+ padding-bottom: 0.1rem;
+ margin-left: 0.5rem;
+ margin-right: 1rem;
+ max-width: 75%;
+}
+
+.stealth-input-hidden {
+ border-style: none;
+ --fallback-box-shadow: none;
+ --field-hover-box-shadow: none;
+ --field-focus-box-shadow: none;
+ padding-top: 0.1rem;
+ padding-bottom: 0.1rem;
+}
+
+.settings-section-button {
+ box-sizing: border-box;
+ width: 100%;
+ height: 2.5rem;
+ background-color: rgba(0,0,0,0);
+}
+
+.settings-section-button:hover, .settings-section-button:focus {
+ box-sizing: border-box;
+ width: 100%;
+ height: 2.5rem;
+ background-color: var(--color-neutral-normal-fill);
+ filter: brightness(var(--settings-section-button-filter)) !important;
+}
+
+.settings-section-button.selected {
+ background-color: var(--color-neutral-normal-fill);
+ --buton-filter-brightness: var(--settings-section-button-filter);
+ filter: brightness(var(--settings-section-button-filter)) !important;
+}
+
+.setting {
+ margin-bottom: 0.25rem;
+}
+
+.textarea {
+ resize: vertical;
+}
+
+.overflow {
+ overflow: visible;
+}
+
+.overflow-y {
+ overflow-y: visible;
+}
+
+select, textarea {
+ color: inherit;
+ border: 0 solid var(--color-neutral-300);
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+}
+
+input {
+ color: inherit;
+ border: 0 solid var(--color-neutral-300);
+}
+
+p.top {
+ margin-top: 0px;
+}
+
+.table-responsive {
+ overflow-x: auto;
+}
+
+#notification-box {
+ position: fixed;
+ right: 1rem;
+ bottom: 1rem;
+ z-index: 16;
+}
diff --git a/css/dark.css b/css/dark.css
new file mode 100644
index 0000000..b21de54
--- /dev/null
+++ b/css/dark.css
@@ -0,0 +1,87 @@
+.dark-theme {
+
+ --settings-section-button-filter: 110%;
+
+ --color-neutral-900: rgba(255, 255, 255, 0.87);
+ --color-neutral-800: rgba(255, 255, 255, 0.8);
+ --color-neutral-700: rgba(255, 255, 255, 0.73);
+ --color-neutral-600: rgba(255, 255, 255, 0.66);
+ --color-neutral-500: rgb(153, 153, 153);
+ --color-neutral-400: #383838;
+ --color-neutral-300: #303030;
+ --color-neutral-200: #292929;
+ --color-neutral-100: #242424;
+ --color-neutral-50: #202020;
+ --color-neutral-000: #101010;
+
+ --color-critical-900: #fef2f2;
+ --color-critical-800: #fee2e2;
+ --color-critical-700: #fecaca;
+ --color-critical-600: #fca5a5;
+ --color-critical-500: #f87171;
+ --color-critical-400: #ef4444;
+ --color-critical-300: #dc2626;
+ --color-critical-200: #b91c1c;
+ --color-critical-100: #991b1b;
+ --color-critical-50: #7f1d1d;
+ --color-critical-000: #441313;
+
+ --color-warning-900: #fffbeb;
+ --color-warning-800: #fef3c7;
+ --color-warning-700: #fde68a;
+ --color-warning-600: #fcd34d;
+ --color-warning-500: #fbbf24;
+ --color-warning-400: #f59e0b;
+ --color-warning-300: #d97706;
+ --color-warning-200: #b45309;
+ --color-warning-100: #92400e;
+ --color-warning-50: #783900;
+ --color-warning-000: #411e01;
+
+ --color-positive-900: #f0fdf4;
+ --color-positive-800: #dcfce7;
+ --color-positive-700: #bbf7d0;
+ --color-positive-600: #86efac;
+ --color-positive-500: #4ade80;
+ --color-positive-400: #22c55e;
+ --color-positive-300: #16a34a;
+ --color-positive-200: #15803d;
+ --color-positive-100: #166534;
+ --color-positive-50: #14532d;
+ --color-positive-000: #0f2e1b;
+
+ --color-urge-900: #e0ffff;
+ --color-urge-800: #c0fbff;
+ --color-urge-700: #a0f4ff;
+ --color-urge-600: #80e9ff;
+ --color-urge-500: #60dbfb;
+ --color-urge-400: #40cbf3;
+ --color-urge-300: #20b9e9;
+ --color-urge-200: #00a4dc; /* tab buttons */
+ --color-urge-100: #0054bc;
+ --color-urge-50: #00169a;
+ --color-urge-000: #050076;
+
+ --color-info-900: #f5f3ff;
+ --color-info-800: #ede9fe;
+ --color-info-700: #ddd6fe;
+ --color-info-600: #c4b5fd;
+ --color-info-500: #a78bfa;
+ --color-info-400: #8b5cf6;
+ --color-info-300: #7c3aed;
+ --color-info-200: #6d28d9;
+ --color-info-100: #5b21b6;
+ --color-info-50: #4c1d95;
+ --color-info-000: #240e44;
+
+
+ --color-neutral-normal-content: #ffffff;
+}
+
+.light-only {
+ display: none;
+}
+
+.dark-only {
+ display: initial;
+}
diff --git a/css/loader.css b/css/loader.css
new file mode 100644
index 0000000..2b7951a
--- /dev/null
+++ b/css/loader.css
@@ -0,0 +1,40 @@
+.loader {
+ height: auto;
+ color: rgba(0, 0, 0, 0);
+}
+
+.loader .dot {
+ --diameter: 0.5rem;
+ --radius: calc(var(--diameter) / 2);
+ --deviation: 20%;
+ height: var(--diameter);
+ width: var(--diameter);
+ background-color: var(--color-content);
+ border-radius: 50%;
+ position: absolute;
+ left: calc(50% - var(--radius));
+ animation: osc 1s cubic-bezier(.72,.16,.31,.97) infinite;
+}
+.loader.loader-sm .dot {
+ --deviation: 10%;
+}
+
+@keyframes osc {
+ 25% {
+ left: calc(50% + var(--deviation) - var(--radius));
+ }
+ 75% {
+ left: calc(50% - var(--deviation));
+ }
+ 0%, 50%, 100% {
+ left: calc(50% - var(--radius));
+ }
+/*
+ 0%, 100% {
+ left: calc(50% - var(--deviation))
+ }
+ 50% {
+ left: calc(50% + var(--deviation) - var(--radius));
+ }
+ */
+}
diff --git a/css/modal.css b/css/modal.css
new file mode 100644
index 0000000..3f27a99
--- /dev/null
+++ b/css/modal.css
@@ -0,0 +1,72 @@
+.modal {
+ display: none;
+ position: fixed;
+ z-index: 12;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background-color: rgba(0,0,0,40%);
+}
+
+.modal-shown {
+ display: block;
+}
+
+@keyframes modal-hide {
+ from { opacity: 1; }
+ to { opacity: 0; }
+}
+
+.modal-hiding {
+ animation: modal-hide 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+}
+
+@keyframes modal-content-show {
+ from {
+ opacity: 0;
+ top: -6rem;
+ }
+ to {
+ opacity: 1;
+ top: 0;
+ }
+}
+
+.modal-content {
+ position: relative;
+ margin: 10% auto;
+ width: 30%;
+}
+
+.modal-content.wide {
+ width: 60%;
+}
+
+.modal-shown .modal-content {
+ animation: modal-content-show 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+}
+
+@media screen and (max-width: 1000px) {
+ .modal-content.wide {
+ width: 75%;
+ }
+}
+
+@media screen and (max-width: 400px) {
+ .modal-content, .modal-content.wide {
+ width: 90%;
+ }
+}
+
+.modal-close {
+ float: right;
+ color: #aaa;
+ font-weight: normal;
+}
+
+.modal-close:hover,
+.modal-close:focus {
+ filter: brightness(60%);
+}
diff --git a/css/tooltip.css b/css/tooltip.css
new file mode 100644
index 0000000..4566f30
--- /dev/null
+++ b/css/tooltip.css
@@ -0,0 +1,36 @@
+.tooltip {
+ position: relative;
+ display: inline-block;
+}
+
+.tooltip .content {
+ visibility: hidden;
+ max-width: 10rem;
+ min-width: 6rem;
+ background-color: rgba(0, 0, 0, 0.6);
+ color: #fff;
+ padding: 0.5rem;
+ border-radius: 6px;
+ overflow-wrap: break-word;
+ text-align: center;
+
+ position: absolute;
+ z-index: 1;
+ top: -1rem;
+}
+
+.tooltip.right .content {
+ left: 120%;
+}
+
+.tooltip.left .content {
+ right: 120%;
+}
+
+.tooltip .content.sm {
+ font-size: 0.8rem;
+}
+
+.tooltip:hover .content {
+ visibility: visible;
+}
diff --git a/data/static/setup.js b/data/static/setup.js
deleted file mode 100644
index e9f5fc6..0000000
--- a/data/static/setup.js
+++ /dev/null
@@ -1,275 +0,0 @@
-document.getElementById('page-1').scrollIntoView({
- behavior: 'auto',
- block: 'center',
- inline: 'center' });
-
-function checkAuthRadio() {
- if (document.getElementById('manualAuthRadio').checked) {
- document.getElementById('adminOnlyArea').style.display = 'none';
- document.getElementById('manualAuthArea').style.display = '';
- } else {
- document.getElementById('manualAuthArea').style.display = 'none';
- document.getElementById('adminOnlyArea').style.display = '';
- };
-};
-var authRadios = ['manualAuthRadio', 'jfAuthRadio'];
-for (var i = 0; i < authRadios.length; i++) {
- document.getElementById(authRadios[i]).addEventListener('change', function() {
- checkAuthRadio();
- });
-};
-
-function checkEmailRadio() {
- document.getElementById('emailNextButton').href = '#page-5';
- document.getElementById('valBackButton').href = '#page-7';
- if (document.getElementById('emailSMTPRadio').checked) {
- document.getElementById('emailCommonArea').style.display = '';
- document.getElementById('emailSMTPArea').style.display = '';
- document.getElementById('emailMailgunArea').style.display = 'none';
- document.getElementById('notificationsEnabled').checked = true;
- } else if (document.getElementById('emailMailgunRadio').checked) {
- document.getElementById('emailCommonArea').style.display = '';
- document.getElementById('emailSMTPArea').style.display = 'none';
- document.getElementById('emailMailgunArea').style.display = '';
- document.getElementById('notificationsEnabled').checked = true;
- } else if (document.getElementById('emailDisabledRadio').checked) {
- document.getElementById('emailCommonArea').style.display = 'none';
- document.getElementById('emailSMTPArea').style.display = 'none';
- document.getElementById('emailMailgunArea').style.display = 'none';
- document.getElementById('emailNextButton').href = '#page-8';
- document.getElementById('valBackButton').href = '#page-4';
- document.getElementById('notificationsEnabled').checked = false;
- };
-};
-var emailRadios = ['emailDisabledRadio', 'emailSMTPRadio', 'emailMailgunRadio'];
-for (var i = 0; i < emailRadios.length; i++) {
- document.getElementById(emailRadios[i]).addEventListener('change', function() {
- checkEmailRadio();
- });
-};
-
-function checkSSL() {
- var label = document.getElementById('emailSSL_TLSLabel');
- if (document.getElementById('emailSSL_TLS').checked) {
- label.textContent = 'Use SSL/TLS';
- } else {
- label.textContent = 'Use STARTTLS';
- };
-};
-document.getElementById('emailSSL_TLS').addEventListener('change', function() {
- checkSSL();
-});
-
-function checkPwrEnabled() {
- if (document.getElementById('pwrEnabled').checked) {
- document.getElementById('pwrArea').style.display = '';
- } else {
- document.getElementById('pwrArea').style.display = 'none';
- };
-};
-var pwrEnabled = document.getElementById('pwrEnabled');
-pwrEnabled.addEventListener('change', function() {
- checkPwrEnabled();
-});
-
-function checkInvEnabled() {
- if (document.getElementById('invEnabled').checked) {
- document.getElementById('invArea').style.display = '';
- } else {
- document.getElementById('invArea').style.display = 'none';
- };
-};
-document.getElementById('invEnabled').addEventListener('change', function() {
- checkInvEnabled();
-});
-
-function checkValEnabled() {
- if (document.getElementById('valEnabled').checked) {
- document.getElementById('valArea').style.display = '';
- } else {
- document.getElementById('valArea').style.display = 'none';
- };
-};
-document.getElementById('valEnabled').addEventListener('change', function() {
- checkValEnabled();
-});
-checkValEnabled();
-checkInvEnabled();
-checkSSL();
-checkAuthRadio();
-checkEmailRadio();
-checkPwrEnabled();
-
-var jfValid = false
-document.getElementById('jfTestButton').onclick = function() {
- var testButton = document.getElementById('jfTestButton');
- var nextButton = document.getElementById('jfNextButton');
- var jfData = {};
- jfData['jfHost'] = document.getElementById('jfHost').value;
- jfData['jfUser'] = document.getElementById('jfUser').value;
- jfData['jfPassword'] = document.getElementById('jfPassword').value;
- let valid = true;
- for (val in jfData) {
- if (jfData[val] == "") {
- valid = false;
- }
- }
- if (!valid) {
- if (!testButton.classList.contains('btn-danger')) {
- testButton.classList.add('btn-danger');
- testButton.textContent = 'Fill out fields above.';
- setTimeout(function() {
- if (testButton.classList.contains('btn-danger')) {
- testButton.classList.remove('btn-danger');
- testButton.textContent = 'Test';
- }
- }, 2000);
- }
- } else {
- testButton.disabled = true;
- testButton.innerHTML =
- '' +
- 'Testing...';
- nextButton.classList.add('disabled');
- nextButton.setAttribute('aria-disabled', 'true');
- var req = new XMLHttpRequest();
- req.open("POST", "/jellyfin/test", true);
- req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
- req.responseType = 'json';
- req.onreadystatechange = function() {
- if (this.readyState == 4) {
- testButton.disabled = false;
- testButton.className = '';
- if (this.response['success'] == true) {
- testButton.classList.add('btn', 'btn-success');
- testButton.textContent = 'Success';
- nextButton.classList.remove('disabled');
- nextButton.setAttribute('aria-disabled', 'false');
- } else {
- testButton.classList.add('btn', 'btn-danger');
- testButton.textContent = 'Failed';
- };
- };
- };
- req.send(JSON.stringify(jfData));
- }
-};
-
-document.getElementById('submitButton').onclick = function() {
- var submitButton = document.getElementById('submitButton');
- submitButton.disabled = true;
- submitButton.innerHTML =
- '' +
- 'Submitting...';
- var config = {};
- config['jellyfin'] = {};
- config['ui'] = {};
- config['password_validation'] = {};
- config['email'] = {};
- config['password_resets'] = {};
- config['invite_emails'] = {};
- config['mailgun'] = {};
- config['smtp'] = {};
- config['notifications'] = {};
- // Page 2: Auth
- if (document.getElementById('jfAuthRadio').checked) {
- config['ui']['jellyfin_login'] = 'true';
- if (document.getElementById('jfAuthAdminOnly').checked) {
- config['ui']['admin_only'] = 'true';
- } else {
- config['ui']['admin_only'] = 'false'
- };
- } else {
- config['ui']['username'] = document.getElementById('manualAuthUsername').value;
- config['ui']['password'] = document.getElementById('manualAuthPassword').value;
- config['ui']['email'] = document.getElementById('manualAuthEmail').value;
- };
- // Page 3: Connect to jellyfin
- config['jellyfin']['server'] = document.getElementById('jfHost').value;
- let publicAddress = document.getElementById('jfPublicHost').value;
- if (publicAddress != "") {
- config['jellyfin']['public_server'] = publicAddress;
- }
- config['jellyfin']['username'] = document.getElementById('jfUser').value;
- config['jellyfin']['password'] = document.getElementById('jfPassword').value;
- // Page 4: Email (Page 5, 6, 7 are only used if this is enabled)
- if (document.getElementById('emailDisabledRadio').checked) {
- config['password_resets']['enabled'] = 'false';
- config['invite_emails']['enabled'] = 'false';
- config['notifications']['enabled'] = 'false';
- } else {
- if (document.getElementById('emailSMTPRadio').checked) {
- if (document.getElementById('emailSSL_TLS').checked) {
- config['smtp']['encryption'] = 'ssl_tls';
- } else {
- config['smtp']['encryption'] = 'starttls';
- };
- config['email']['method'] = 'smtp';
- config['smtp']['server'] = document.getElementById('emailSMTPServer').value;
- config['smtp']['port'] = document.getElementById('emailSMTPPort').value;
- config['smtp']['password'] = document.getElementById('emailSMTPPassword').value;
- config['email']['address'] = document.getElementById('emailSMTPAddress').value;
- } else {
- config['email']['method'] = 'mailgun';
- config['mailgun']['api_url'] = document.getElementById('emailMailgunURL').value;
- config['mailgun']['api_key'] = document.getElementById('emailMailgunKey').value;
- config['email']['address'] = document.getElementById('emailMailgunAddress').value;
- };
- config['notifications']['enabled'] = document.getElementById('notificationsEnabled').checked.toString();
- // Page 5: Email formatting
- config['email']['from'] = document.getElementById('emailSender').value;
- config['email']['date_format'] = document.getElementById('emailDateFormat').value;
- if (document.getElementById('email24hTimeRadio').checked) {
- config['email']['use_24h'] = 'true';
- } else {
- config['email']['use_24h'] = 'false';
- };
- config['email']['message'] = document.getElementById('emailMessage').value;
- // Page 6: Password Resets
- if (document.getElementById('pwrEnabled').checked) {
- config['password_resets']['enabled'] = 'true';
- config['password_resets']['watch_directory'] = document.getElementById('pwrJfPath').value;
- config['password_resets']['subject'] = document.getElementById('pwrSubject').value;
- } else {
- config['password_resets']['enabled'] = 'false';
- };
- // Page 7: Invite Emails
- if (document.getElementById('invEnabled').checked) {
- config['invite_emails']['enabled'] = 'true';
- config['invite_emails']['url_base'] = document.getElementById('invURLBase').value;
- config['invite_emails']['subject'] = document.getElementById('invSubject').value;
- } else {
- config['invite_emails']['enabled'] = 'false';
- };
- };
- // Page 8: Password Validation
- if (document.getElementById('valEnabled').checked) {
- config['password_validation']['enabled'] = 'true';
- config['password_validation']['min_length'] = document.getElementById('valLength').value;
- config['password_validation']['upper'] = document.getElementById('valUpper').value;
- config['password_validation']['lower'] = document.getElementById('valLower').value;
- config['password_validation']['number'] = document.getElementById('valNumber').value;
- config['password_validation']['special'] = document.getElementById('valSpecial').value;
- } else {
- config['password_validation']['enabled'] = 'false';
- };
- // Page 9: Messages
- config['ui']['contact_message'] = document.getElementById('msgContact').value;
- config['ui']['help_message'] = document.getElementById('msgHelp').value;
- config['ui']['success_message'] = document.getElementById('msgSuccess').value;
- // Send it
- config["restart-program"] = true;
- var req = new XMLHttpRequest();
- req.open("POST", "/config", true);
- req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
- req.responseType = 'json';
- req.onreadystatechange = function() {
- if (this.readyState == 4) {
- submitButton.disabled = false;
- submitButton.className = '';
- submitButton.classList.add('btn', 'btn-success');
- submitButton.textContent = 'Success';
- };
- };
- req.send(JSON.stringify(config));
-};
diff --git a/data/templates/404.html b/data/templates/404.html
deleted file mode 100644
index 22593ad..0000000
--- a/data/templates/404.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
- 404 - jfa-go
-
- {{ template "header.html" . }}
-
-
-
-
-
Page not found.
-
- {{ .contactMessage }}
-
-
-
-
diff --git a/data/templates/admin.html b/data/templates/admin.html
deleted file mode 100644
index c1d7885..0000000
--- a/data/templates/admin.html
+++ /dev/null
@@ -1,473 +0,0 @@
-
-
-
-
- {{ template "header.html" . }}
- Admin - jfa-go
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ if .ombiEnabled }}
-
-
-
-
-
-
Create an Ombi user and configure it to your liking, then choose it from below to store the settings and permissions as a template for all new users.
-
-
-
-
-
-
- {{ end }}
-
-
-
-
-
-
A restart is needed to apply some settings. Restart now, later, or cancel?
-
-
-
-
-
-
-
-
-
-
-
Refresh the page in a few seconds.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Note: * Indicates required field, R Indicates changes require a restart.
-
-
- {{ if .ombiEnabled }}
-
- {{ end }}
-
-
-
-
-
-
-
-
-
-
Profiles are applied to users when they create an account. They include things like access rights and homescreen layout. You can create them here.
-
-
-
- Name |
- Default |
- From |
- Admin? |
- Libraries |
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ if .ombiEnabled }}
-
- {{ end }}
-
-
diff --git a/data/templates/form.html b/data/templates/form.html
deleted file mode 100644
index 80c9635..0000000
--- a/data/templates/form.html
+++ /dev/null
@@ -1,106 +0,0 @@
-
-
-
-
- {{ template "header.html" . }}
-
- {{ .lang.pageTitle }}
-
-
-
-
-
-
-
-
{{ .successMessage }}
-
-
-
-
-
-
-
- {{ .lang.createAccountHeader }}
-
-
{{ .helpMessage }}
-
{{ .contactMessage }}
-
-
-
- {{ if .validate }}
-
-
-
-
-
- {{ range $key, $value := .requirements }}
- -
-
-
- {{ end }}
-
-
-
-
- {{ end }}
-
-
-
-
- {{ template "form-base" . }}
-
-
-
diff --git a/data/templates/header.html b/data/templates/header.html
deleted file mode 100644
index 5f2975b..0000000
--- a/data/templates/header.html
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-{{ if not .bs5 }}
-
-{{ end }}
-
-{{ if .bs5 }}
-
-{{ else }}
-
-{{ end }}
-
diff --git a/data/templates/invalidCode.html b/data/templates/invalidCode.html
deleted file mode 100644
index 99ed9e0..0000000
--- a/data/templates/invalidCode.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- Invalid Code - jfa-go
-
- {{ template "header.html" . }}
-
-
-
-
-
Invalid Code.
-
The above code is either incorrect, or has expired.
-
{{ .contactMessage }}
-
-
-
diff --git a/esbuild.sh b/esbuild.sh
deleted file mode 100755
index a9cc078..0000000
--- a/esbuild.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/bash
-# set +e
-# npx tsc -p ts/
-# set -e
-npx esbuild ts/* --outdir=data/static --minify
diff --git a/go.mod b/go.mod
index d9eae46..017aba2 100644
--- a/go.mod
+++ b/go.mod
@@ -19,6 +19,7 @@ require (
github.com/gin-gonic/gin v1.6.3
github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
+ github.com/go-openapi/spec v0.20.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/gofrs/uuid v3.3.0+incompatible // indirect
github.com/golang/protobuf v1.4.3
@@ -45,10 +46,9 @@ require (
github.com/ugorji/go v1.2.0 // indirect
github.com/urfave/cli/v2 v2.3.0 // indirect
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect
- golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
- golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba // indirect
+ golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
golang.org/x/text v0.3.4 // indirect
+ golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/ini.v1 v1.62.0
- gopkg.in/yaml.v2 v2.3.0 // indirect
)
diff --git a/go.sum b/go.sum
index 421c4c0..c20e0ce 100644
--- a/go.sum
+++ b/go.sum
@@ -15,6 +15,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSY
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM=
@@ -66,6 +67,8 @@ github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+j
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg=
github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
+github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
+github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
@@ -79,6 +82,8 @@ github.com/go-openapi/spec v0.19.13 h1:AcZVcWsrfW7LqyHKVbTZYpFF7jQcMxmAsWrw2p/b9
github.com/go-openapi/spec v0.19.13/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA=
github.com/go-openapi/spec v0.19.14 h1:r4fbYFo6N4ZelmSX8G6p+cv/hZRXzcuqQIADGT1iNKM=
github.com/go-openapi/spec v0.19.14/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA=
+github.com/go-openapi/spec v0.20.0 h1:HGLc8AJ7ynOxwv0Lq4TsnwLsWMawHAYiJIFzbcML86I=
+github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
@@ -89,6 +94,8 @@ github.com/go-openapi/swag v0.19.10 h1:A1SWXruroGP15P1sOiegIPbaKio+G9N5TwWTFaVPm
github.com/go-openapi/swag v0.19.10/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY=
github.com/go-openapi/swag v0.19.11 h1:RFTu/dlFySpyVvJDfp/7674JY4SDglYWKztbiIGFpmc=
github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY=
+github.com/go-openapi/swag v0.19.12 h1:Bc0bnY2c3AoF7Gc+IMIAQQsD8fLHjHpc19wXvYuayQI=
+github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
@@ -153,6 +160,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU=
@@ -202,6 +210,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pdrum/swagger-automation v0.0.0-20190629163613-c8c7c80ba858 h1:lgbJiJQx8bXo+eM88AFdd0VxUvaTLzCBXpK+H9poJ+Y=
github.com/pdrum/swagger-automation v0.0.0-20190629163613-c8c7c80ba858/go.mod h1:y02HeaN0visd95W6cEX2NXDv5sCwyqfzucWTdDGEwYY=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
@@ -314,6 +323,8 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTi
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -344,6 +355,9 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba h1:xmhUJGQGbxlod18iJGqVEp9cHIPLl7QiX2aA3to708s=
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -382,6 +396,8 @@ golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b h1:Ych5r0Z6MLML1fgf5hTg9p5
golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e h1:t96dS3DO8DGjawSLJL/HIdz8CycAd2v07XxqB3UPTi0=
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee h1:5xKxdl/RhlelmSPaxyVeq5PYSmJ4H14yeQT58qP1F6o=
+golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -407,6 +423,7 @@ google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
@@ -426,6 +443,9 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/html/404.html b/html/404.html
new file mode 100644
index 0000000..171610a
--- /dev/null
+++ b/html/404.html
@@ -0,0 +1,16 @@
+
+
+
+
+ {{ template "header.html" . }}
+ 404 - jfa-go
+
+
+
+
Page not found.
+
+ {{ .contactMessage }}
+
+
+
+
diff --git a/html/admin.html b/html/admin.html
new file mode 100644
index 0000000..d86e90b
--- /dev/null
+++ b/html/admin.html
@@ -0,0 +1,296 @@
+
+
+
+
+
+
+ {{ template "header.html" . }}
+ Admin - jfa-go
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Restart needed ×
+
A restart is needed to apply some settings you changed. Do it now or later?
+
+ Apply, restart later
+ Apply & restart
+
+
+
+
+
+
Settings applied.
+
Refresh the page in a few seconds.
+
+
+
+
+
+
+
+
User profiles ×
+
Profiles are applied to users when they create an account. A profile includes library access rights and homescreen layout.
+
+
+
+
+ Name |
+ Default |
+ From |
+ Libraries |
+ Create |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Warning invites with infinite uses can be used abusively.
+
+
+
+
+
+
Create
+
+
+
+
+
+
+
Accounts
+
+ Add User
+ Modify Settings
+ Delete User
+
+
+
+
+
+
+
Settings
+
+ Save
+
+
+
+
+
+
+
+
diff --git a/data/templates/form-base.html b/html/form-base.html
similarity index 78%
rename from data/templates/form-base.html
rename to html/form-base.html
index 35f3155..432d6a4 100644
--- a/data/templates/form-base.html
+++ b/html/form-base.html
@@ -1,10 +1,9 @@
{{ define "form-base" }}
-
+
{{ end }}
diff --git a/data/templates/form-loader.html b/html/form-loader.html
similarity index 100%
rename from data/templates/form-loader.html
rename to html/form-loader.html
diff --git a/html/form.html b/html/form.html
new file mode 100644
index 0000000..88c5685
--- /dev/null
+++ b/html/form.html
@@ -0,0 +1,67 @@
+
+
+
+
+ {{ template "header.html" . }}
+ {{ .lang.pageTitle }}
+
+
+
+
+
{{ .lang.successHeader }}
+
{{ .successMessage }}
+
{{ .lang.successContinueButton }}
+
+
+
+
+
+
+ {{ .lang.createAccountHeader }}
+ {{ .helpMessage }}
+
+
+
+
+
+ {{ template "form-base" . }}
+
+
+
diff --git a/html/header.html b/html/header.html
new file mode 100644
index 0000000..4a83009
--- /dev/null
+++ b/html/header.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/html/invalidCode.html b/html/invalidCode.html
new file mode 100644
index 0000000..c14ad1a
--- /dev/null
+++ b/html/invalidCode.html
@@ -0,0 +1,17 @@
+
+
+
+
+ {{ template "header.html" . }}
+ Invalid Code - jfa-go
+
+
+
+
Invalid invite code.
+
The code above was either incorrect, or has expired.
+
+ {{ .contactMessage }}
+
+
+
+
diff --git a/data/templates/setup.html b/html/setup.html
similarity index 99%
rename from data/templates/setup.html
rename to html/setup.html
index 526d11f..ecffe4e 100644
--- a/data/templates/setup.html
+++ b/html/setup.html
@@ -3,7 +3,7 @@
-
+
@@ -369,6 +369,6 @@
-
+