mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-29 12:30:11 +00:00
Compare commits
3 Commits
0cb1e7b24b
...
1fc8873c09
Author | SHA1 | Date | |
---|---|---|---|
1fc8873c09 | |||
a4e44f5a8b | |||
c9e7e34fbf |
45
api.go
45
api.go
@ -1085,17 +1085,25 @@ func (app *appContext) GetConfig(gc *gin.Context) {
|
|||||||
app.info.Println("Config requested")
|
app.info.Println("Config requested")
|
||||||
resp := app.configBase
|
resp := app.configBase
|
||||||
// Load language options
|
// Load language options
|
||||||
langOptions := make([]string, len(app.storage.lang.Form))
|
loadLangs := func(langs *map[string]map[string]interface{}, settingsKey string) (string, []string) {
|
||||||
chosenLang := app.config.Section("ui").Key("language").MustString("en-us")
|
langOptions := make([]string, len(*langs))
|
||||||
chosenLangName := app.storage.lang.Form[chosenLang]["meta"].(map[string]interface{})["name"].(string)
|
chosenLang := app.config.Section("ui").Key("language-" + settingsKey).MustString("en-us")
|
||||||
|
chosenLangName := (*langs)[chosenLang]["meta"].(map[string]interface{})["name"].(string)
|
||||||
i := 0
|
i := 0
|
||||||
for _, lang := range app.storage.lang.Form {
|
for _, lang := range *langs {
|
||||||
langOptions[i] = lang["meta"].(map[string]interface{})["name"].(string)
|
langOptions[i] = lang["meta"].(map[string]interface{})["name"].(string)
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
l := resp.Sections["ui"].Settings["language"]
|
return chosenLangName, langOptions
|
||||||
l.Options = langOptions
|
}
|
||||||
l.Value = chosenLangName
|
formChosen, formOptions := loadLangs(&app.storage.lang.Form, "form")
|
||||||
|
fl := resp.Sections["ui"].Settings["language-form"]
|
||||||
|
fl.Options = formOptions
|
||||||
|
fl.Value = formChosen
|
||||||
|
adminChosen, adminOptions := loadLangs(&app.storage.lang.Admin, "admin")
|
||||||
|
al := resp.Sections["ui"].Settings["language-admin"]
|
||||||
|
al.Options = adminOptions
|
||||||
|
al.Value = adminChosen
|
||||||
for sectName, section := range resp.Sections {
|
for sectName, section := range resp.Sections {
|
||||||
for settingName, setting := range section.Settings {
|
for settingName, setting := range section.Settings {
|
||||||
val := app.config.Section(sectName).Key(settingName)
|
val := app.config.Section(sectName).Key(settingName)
|
||||||
@ -1111,11 +1119,12 @@ func (app *appContext) GetConfig(gc *gin.Context) {
|
|||||||
resp.Sections[sectName].Settings[settingName] = s
|
resp.Sections[sectName].Settings[settingName] = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp.Sections["ui"].Settings["language"] = l
|
resp.Sections["ui"].Settings["language-form"] = fl
|
||||||
|
resp.Sections["ui"].Settings["language-admin"] = al
|
||||||
|
|
||||||
t := resp.Sections["jellyfin"].Settings["type"]
|
t := resp.Sections["jellyfin"].Settings["type"]
|
||||||
opts := make([]string, len(serverTypes))
|
opts := make([]string, len(serverTypes))
|
||||||
i = 0
|
i := 0
|
||||||
for _, v := range serverTypes {
|
for _, v := range serverTypes {
|
||||||
opts[i] = v
|
opts[i] = v
|
||||||
i++
|
i++
|
||||||
@ -1146,10 +1155,17 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
|
|||||||
tempConfig.NewSection(section)
|
tempConfig.NewSection(section)
|
||||||
}
|
}
|
||||||
for setting, value := range settings.(map[string]interface{}) {
|
for setting, value := range settings.(map[string]interface{}) {
|
||||||
if section == "ui" && setting == "language" {
|
if section == "ui" && setting == "language-form" {
|
||||||
for key, lang := range app.storage.lang.Form {
|
for key, lang := range app.storage.lang.Form {
|
||||||
if lang["meta"].(map[string]interface{})["name"].(string) == value.(string) {
|
if lang["meta"].(map[string]interface{})["name"].(string) == value.(string) {
|
||||||
tempConfig.Section("ui").Key("language").SetValue(key)
|
tempConfig.Section("ui").Key("language-form").SetValue(key)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if section == "ui" && setting == "language-admin" {
|
||||||
|
for key, lang := range app.storage.lang.Admin {
|
||||||
|
if lang["meta"].(map[string]interface{})["name"].(string) == value.(string) {
|
||||||
|
tempConfig.Section("ui").Key("language-admin").SetValue(key)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1221,10 +1237,17 @@ func (app *appContext) Logout(gc *gin.Context) {
|
|||||||
// @Router /lang [get]
|
// @Router /lang [get]
|
||||||
// @tags Other
|
// @tags Other
|
||||||
func (app *appContext) GetLanguages(gc *gin.Context) {
|
func (app *appContext) GetLanguages(gc *gin.Context) {
|
||||||
|
page := gc.Param("page")
|
||||||
resp := langDTO{}
|
resp := langDTO{}
|
||||||
|
if page == "form" {
|
||||||
for key, lang := range app.storage.lang.Form {
|
for key, lang := range app.storage.lang.Form {
|
||||||
resp[key] = lang["meta"].(map[string]interface{})["name"].(string)
|
resp[key] = lang["meta"].(map[string]interface{})["name"].(string)
|
||||||
}
|
}
|
||||||
|
} else if page == "admin" {
|
||||||
|
for key, lang := range app.storage.lang.Admin {
|
||||||
|
resp[key] = lang["meta"].(map[string]interface{})["name"].(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(resp) == 0 {
|
if len(resp) == 0 {
|
||||||
respond(500, "Couldn't get languages", gc)
|
respond(500, "Couldn't get languages", gc)
|
||||||
return
|
return
|
||||||
|
11
config.go
11
config.go
@ -84,8 +84,15 @@ func (app *appContext) loadConfig() error {
|
|||||||
|
|
||||||
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
|
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
|
||||||
|
|
||||||
app.storage.lang.chosenFormLang = app.config.Section("ui").Key("language").MustString("en-us")
|
oldFormLang := app.config.Section("ui").Key("language").MustString("")
|
||||||
app.storage.lang.chosenFormLang = app.config.Section("ui").Key("language").MustString("en-us")
|
if oldFormLang != "" {
|
||||||
|
app.storage.lang.chosenFormLang = oldFormLang
|
||||||
|
}
|
||||||
|
newFormLang := app.config.Section("ui").Key("language-form").MustString("")
|
||||||
|
if newFormLang != "" {
|
||||||
|
app.storage.lang.chosenFormLang = newFormLang
|
||||||
|
}
|
||||||
|
app.storage.lang.chosenAdminLang = app.config.Section("ui").Key("language-admin").MustString("en-us")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@
|
|||||||
"description": "Settings related to the UI and program functionality."
|
"description": "Settings related to the UI and program functionality."
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"language": {
|
"language-form": {
|
||||||
"name": "Default Form Language",
|
"name": "Default Form Language",
|
||||||
"required": false,
|
"required": false,
|
||||||
"requires_restart": true,
|
"requires_restart": true,
|
||||||
@ -93,7 +93,18 @@
|
|||||||
"en-us"
|
"en-us"
|
||||||
],
|
],
|
||||||
"value": "en-US",
|
"value": "en-US",
|
||||||
"description": "Default UI Language. Currently only implemented for account creation form. Submit a PR on github if you'd like to translate."
|
"description": "Default Account Form Language. Submit a PR on github if you'd like to translate."
|
||||||
|
},
|
||||||
|
"language-admin": {
|
||||||
|
"name": "Default Admin Language",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "select",
|
||||||
|
"options": [
|
||||||
|
"en-us"
|
||||||
|
],
|
||||||
|
"value": "en-US",
|
||||||
|
"description": "Default Admin page Language. Settings has not been translated. Submit a PR on github if you'd like to translate."
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"name": "Default Look",
|
"name": "Default Look",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
window.emailEnabled = {{ .email_enabled }};
|
window.emailEnabled = {{ .email_enabled }};
|
||||||
window.ombiEnabled = {{ .ombiEnabled }};
|
window.ombiEnabled = {{ .ombiEnabled }};
|
||||||
window.usernamesEnabled = {{ .username }};
|
window.usernamesEnabled = {{ .username }};
|
||||||
|
window.langFile = JSON.parse({{ .language }});
|
||||||
</script>
|
</script>
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
<title>Admin - jfa-go</title>
|
<title>Admin - jfa-go</title>
|
||||||
@ -166,6 +167,16 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="notification-box"></div>
|
<div id="notification-box"></div>
|
||||||
|
<span class="dropdown" tabindex="0" id="lang-dropdown">
|
||||||
|
<span class="button ~urge dropdown-button">
|
||||||
|
<i class="ri-global-line"></i>
|
||||||
|
<span class="ml-1 chev"></span>
|
||||||
|
</span>
|
||||||
|
<div class="dropdown-display">
|
||||||
|
<div class="card ~neutral !low" id="lang-list">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
<header class="flex flex-wrap items-center justify-between">
|
<header class="flex flex-wrap items-center justify-between">
|
||||||
@ -264,7 +275,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="tab-settings" class="unfocused">
|
<div id="tab-settings" class="unfocused">
|
||||||
<div class="card ~neutral !low settings overflow">
|
<div class="card ~neutral !low settings overflow">
|
||||||
<span class="heading">{{ .string.settings }}</span>
|
<span class="heading">{{ .strings.settings }}</span>
|
||||||
<div class="fr">
|
<div class="fr">
|
||||||
<span class="button ~neutral !normal unfocused" id="settings-save">{{ .strings.settingsSave }}</span>
|
<span class="button ~neutral !normal unfocused" id="settings-save">{{ .strings.settingsSave }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
##### admin page translation
|
|
||||||
|
|
||||||
* [x] static page content
|
|
||||||
* [ ] Typescript:
|
|
||||||
* [x] accounts.ts
|
|
@ -21,6 +21,7 @@
|
|||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
"date": "Date",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"emailAddress": "Email Address",
|
"emailAddress": "Email Address",
|
||||||
@ -32,6 +33,9 @@
|
|||||||
"commitNoun": "Commit",
|
"commitNoun": "Commit",
|
||||||
"newUser": "New User",
|
"newUser": "New User",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
|
"success": "Success",
|
||||||
|
"error": "Error",
|
||||||
|
"unknown": "Unknown",
|
||||||
"modifySettings": "Modify Settings",
|
"modifySettings": "Modify Settings",
|
||||||
"modifySettingsDescription": "Apply settings from an existing profile, or source them directly from a user.",
|
"modifySettingsDescription": "Apply settings from an existing profile, or source them directly from a user.",
|
||||||
"applyHomescreenLayout": "Apply homescreen layout",
|
"applyHomescreenLayout": "Apply homescreen layout",
|
||||||
@ -43,7 +47,7 @@
|
|||||||
"settingsApplyRestartNow": "Apply & restart",
|
"settingsApplyRestartNow": "Apply & restart",
|
||||||
"settingsApplied": "Settings applied.",
|
"settingsApplied": "Settings applied.",
|
||||||
"settingsRefreshPage": "Refresh the page in a few seconds",
|
"settingsRefreshPage": "Refresh the page in a few seconds",
|
||||||
"settingsRequiredOrRestartMessage": "Note: {*} indicates a required field, {R} indicates changes require a restart.",
|
"settingsRequiredOrRestartMessage": "Note: {n} indicates a required field, {n} indicates changes require a restart.",
|
||||||
"settingsSave": "Save",
|
"settingsSave": "Save",
|
||||||
"ombiUserDefaults": "Ombi user defaults",
|
"ombiUserDefaults": "Ombi user defaults",
|
||||||
"ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go",
|
"ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go",
|
||||||
@ -54,11 +58,49 @@
|
|||||||
"addProfile": "Add Profile",
|
"addProfile": "Add Profile",
|
||||||
"addProfileDescription": "Create a Jellyfin user and configure it, then select it below. When this profile is applied to an invite, new users will be created with the settings.",
|
"addProfileDescription": "Create a Jellyfin user and configure it, then select it below. When this profile is applied to an invite, new users will be created with the settings.",
|
||||||
"addProfileNameOf": "Profile Name",
|
"addProfileNameOf": "Profile Name",
|
||||||
"addProfileStoreHomescreenLayout": "Store homescreen layout"
|
"addProfileStoreHomescreenLayout": "Store homescreen layout",
|
||||||
|
|
||||||
|
"inviteNoUsersCreated": "None yet!",
|
||||||
|
"inviteUsersCreated": "Created users",
|
||||||
|
"inviteNoProfile": "No Profile",
|
||||||
|
"copy": "Copy",
|
||||||
|
"inviteDateCreated": "Created",
|
||||||
|
"inviteRemainingUses": "Remaining uses",
|
||||||
|
"inviteNoInvites": "None",
|
||||||
|
"inviteExpiresInTime": "Expires in {n}",
|
||||||
|
|
||||||
|
"notifyEvent": "Notify on:",
|
||||||
|
"notifyInviteExpiry": "On expiry",
|
||||||
|
"notifyUserCreation": "On user creation"
|
||||||
},
|
},
|
||||||
"variableStrings": {
|
"notifications": {
|
||||||
"settingsRequiredOrRestartMessage": "Note: {*} indicates a required field, {R} indicates changes require a restart."
|
"changedEmailAddress": "Changed email address of {n}.",
|
||||||
|
"userCreated": "User {n} created.",
|
||||||
|
"createProfile": "Created profile {n}.",
|
||||||
|
"saveSettings": "Settings were saved",
|
||||||
|
"setOmbiDefaults": "Stored ombi defaults.",
|
||||||
|
"errorConnection": "Couldn't connect to jfa-go.",
|
||||||
|
"error401Unauthorized": "Unauthorized. Try refreshing the page.",
|
||||||
|
"errorSettingsAppliedNoHomescreenLayout": "Settings were applied, but applying homescreen layout may have failed.",
|
||||||
|
"errorHomescreenAppliedNoSettings": "Homescreen layout was applied, but applying settings may have failed.",
|
||||||
|
"errorSettingsFailed": "Application failed.",
|
||||||
|
"errorLoginBlank": "The username and/or password were left blank.",
|
||||||
|
"errorUnknown": "Unknown error.",
|
||||||
|
"errorBlankFields": "Fields were left blank",
|
||||||
|
"errorDeleteProfile": "Failed to delete profile {n}",
|
||||||
|
"errorLoadProfiles": "Failed to load profiles.",
|
||||||
|
"errorCreateProfile": "Failed to create profile {n}",
|
||||||
|
"errorSetDefaultProfile": "Failed to set default profile.",
|
||||||
|
"errorLoadUsers": "Failed to load users.",
|
||||||
|
"errorSaveSettings": "Couldn't save settings.",
|
||||||
|
"errorLoadSettings": "Failed to load settings.",
|
||||||
|
"errorSetOmbiDefaults": "Failed to store ombi defaults.",
|
||||||
|
"errorLoadOmbiUsers": "Failed to load ombi users.",
|
||||||
|
"errorChangedEmailAddress": "Couldn't change email address of {n}.",
|
||||||
|
"errorFailureCheckLogs": "Failed (check console/logs)",
|
||||||
|
"errorPartialFailureCheckLogs": "Partial failure (check console/logs)"
|
||||||
},
|
},
|
||||||
|
|
||||||
"quantityStrings": {
|
"quantityStrings": {
|
||||||
"modifySettingsFor": {
|
"modifySettingsFor": {
|
||||||
"singular": "Modify Settings for {n} user",
|
"singular": "Modify Settings for {n} user",
|
||||||
@ -75,6 +117,14 @@
|
|||||||
"deleteUser": {
|
"deleteUser": {
|
||||||
"singular": "Delete User",
|
"singular": "Delete User",
|
||||||
"plural": "Delete Users"
|
"plural": "Delete Users"
|
||||||
|
},
|
||||||
|
"deletedUser": {
|
||||||
|
"singular": "Deleted {n} user.",
|
||||||
|
"plural": "Deleted {n} users."
|
||||||
|
},
|
||||||
|
"appliedSettings": {
|
||||||
|
"singular": "Applied settings to {n} user.",
|
||||||
|
"plural": "Applied settings to {n} users."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
main.go
2
main.go
@ -577,7 +577,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
router.GET("/accounts", app.AdminPage)
|
router.GET("/accounts", app.AdminPage)
|
||||||
router.GET("/settings", app.AdminPage)
|
router.GET("/settings", app.AdminPage)
|
||||||
|
|
||||||
router.GET("/lang", app.GetLanguages)
|
router.GET("/lang/:page", app.GetLanguages)
|
||||||
router.GET("/token/login", app.getTokenLogin)
|
router.GET("/token/login", app.getTokenLogin)
|
||||||
router.GET("/token/refresh", app.getTokenRefresh)
|
router.GET("/token/refresh", app.getTokenRefresh)
|
||||||
router.POST("/newUser", app.NewUser)
|
router.POST("/newUser", app.NewUser)
|
||||||
|
29
storage.go
29
storage.go
@ -25,6 +25,7 @@ type Lang struct {
|
|||||||
chosenAdminLang string
|
chosenAdminLang string
|
||||||
AdminPath string
|
AdminPath string
|
||||||
Admin map[string]map[string]interface{}
|
Admin map[string]map[string]interface{}
|
||||||
|
AdminJSON map[string]string
|
||||||
FormPath string
|
FormPath string
|
||||||
Form map[string]map[string]interface{}
|
Form map[string]map[string]interface{}
|
||||||
}
|
}
|
||||||
@ -63,16 +64,16 @@ func (st *Storage) storeInvites() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (st *Storage) loadLang() error {
|
func (st *Storage) loadLang() error {
|
||||||
loadData := func(path string) (map[string]map[string]interface{}, error) {
|
loadData := func(path string, stringJson bool) (map[string]string, map[string]map[string]interface{}, error) {
|
||||||
files, err := ioutil.ReadDir(path)
|
files, err := ioutil.ReadDir(path)
|
||||||
|
outString := map[string]string{}
|
||||||
out := map[string]map[string]interface{}{}
|
out := map[string]map[string]interface{}{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
index := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
|
index := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
|
||||||
var data map[string]interface{}
|
var data map[string]interface{}
|
||||||
if substituteStrings != "" {
|
|
||||||
var file []byte
|
var file []byte
|
||||||
var err error
|
var err error
|
||||||
file, err = ioutil.ReadFile(filepath.Join(path, f.Name()))
|
file, err = ioutil.ReadFile(filepath.Join(path, f.Name()))
|
||||||
@ -80,23 +81,28 @@ func (st *Storage) loadLang() error {
|
|||||||
file = []byte("{}")
|
file = []byte("{}")
|
||||||
}
|
}
|
||||||
// Replace Jellyfin with emby on form
|
// Replace Jellyfin with emby on form
|
||||||
file = []byte(strings.ReplaceAll(string(file), "Jellyfin", substituteStrings))
|
if substituteStrings != "" {
|
||||||
|
fileString := strings.ReplaceAll(string(file), "Jellyfin", substituteStrings)
|
||||||
|
file = []byte(fileString)
|
||||||
|
}
|
||||||
err = json.Unmarshal(file, &data)
|
err = json.Unmarshal(file, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("ERROR: Failed to read \"%s\": %s", path, err)
|
log.Printf("ERROR: Failed to read \"%s\": %s", path, err)
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
} else {
|
if stringJson {
|
||||||
err := loadJSON(filepath.Join(path, f.Name()), &data)
|
stringJSON, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
outString[index] = string(stringJSON)
|
||||||
}
|
}
|
||||||
out[index] = data
|
out[index] = data
|
||||||
|
|
||||||
}
|
}
|
||||||
return out, nil
|
return outString, out, nil
|
||||||
}
|
}
|
||||||
form, err := loadData(st.lang.FormPath)
|
_, form, err := loadData(st.lang.FormPath, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -112,8 +118,9 @@ func (st *Storage) loadLang() error {
|
|||||||
form[index] = lang
|
form[index] = lang
|
||||||
}
|
}
|
||||||
st.lang.Form = form
|
st.lang.Form = form
|
||||||
admin, err := loadData(st.lang.AdminPath)
|
adminJSON, admin, err := loadData(st.lang.AdminPath, true)
|
||||||
st.lang.Admin = admin
|
st.lang.Admin = admin
|
||||||
|
st.lang.AdminJSON = adminJSON
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
ts/admin.ts
18
ts/admin.ts
@ -1,15 +1,25 @@
|
|||||||
import { toggleTheme, loadTheme } from "./modules/theme.js";
|
import { toggleTheme, loadTheme } from "./modules/theme.js";
|
||||||
|
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
||||||
import { Modal } from "./modules/modal.js";
|
import { Modal } from "./modules/modal.js";
|
||||||
import { Tabs } from "./modules/tabs.js";
|
import { Tabs } from "./modules/tabs.js";
|
||||||
import { inviteList, createInvite } from "./modules/invites.js";
|
import { inviteList, createInvite } from "./modules/invites.js";
|
||||||
import { accountsList } from "./modules/accounts.js";
|
import { accountsList } from "./modules/accounts.js";
|
||||||
import { settingsList } from "./modules/settings.js";
|
import { settingsList } from "./modules/settings.js";
|
||||||
import { ProfileEditor } from "./modules/profiles.js";
|
import { ProfileEditor } from "./modules/profiles.js";
|
||||||
import { _post, notificationBox, whichAnimationEvent, toggleLoader } from "./modules/common.js";
|
import { _get, _post, notificationBox, whichAnimationEvent, toggleLoader } from "./modules/common.js";
|
||||||
|
|
||||||
loadTheme();
|
loadTheme();
|
||||||
(document.getElementById('button-theme') as HTMLSpanElement).onclick = toggleTheme;
|
(document.getElementById('button-theme') as HTMLSpanElement).onclick = toggleTheme;
|
||||||
|
|
||||||
|
window.lang = new lang(window.langFile as LangFile);
|
||||||
|
loadLangSelector("admin");
|
||||||
|
// _get(`/lang/admin/${window.language}.json`, null, (req: XMLHttpRequest) => {
|
||||||
|
// if (req.readyState == 4 && req.status == 200) {
|
||||||
|
// langLoaded = true;
|
||||||
|
// window.lang = new lang(req.response as LangFile);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
window.animationEvent = whichAnimationEvent();
|
window.animationEvent = whichAnimationEvent();
|
||||||
|
|
||||||
window.token = "";
|
window.token = "";
|
||||||
@ -110,12 +120,12 @@ function login(username: string, password: string, run?: (state?: number) => voi
|
|||||||
req.onreadystatechange = function (): void {
|
req.onreadystatechange = function (): void {
|
||||||
if (this.readyState == 4) {
|
if (this.readyState == 4) {
|
||||||
if (this.status != 200) {
|
if (this.status != 200) {
|
||||||
let errorMsg = "Connection error.";
|
let errorMsg = window.lang.notif("errorConnection");
|
||||||
if (this.response) {
|
if (this.response) {
|
||||||
errorMsg = this.response["error"];
|
errorMsg = this.response["error"];
|
||||||
}
|
}
|
||||||
if (!errorMsg) {
|
if (!errorMsg) {
|
||||||
errorMsg = "Unknown error";
|
errorMsg = window.lang.notif("errorUnknown");
|
||||||
}
|
}
|
||||||
if (!refresh) {
|
if (!refresh) {
|
||||||
window.notifications.customError("loginError", errorMsg);
|
window.notifications.customError("loginError", errorMsg);
|
||||||
@ -153,7 +163,7 @@ function login(username: string, password: string, run?: (state?: number) => voi
|
|||||||
const username = (document.getElementById("login-user") as HTMLInputElement).value;
|
const username = (document.getElementById("login-user") as HTMLInputElement).value;
|
||||||
const password = (document.getElementById("login-password") as HTMLInputElement).value;
|
const password = (document.getElementById("login-password") as HTMLInputElement).value;
|
||||||
if (!username || !password) {
|
if (!username || !password) {
|
||||||
window.notifications.customError("loginError", "The username and/or password were left blank.");
|
window.notifications.customError("loginError", window.lang.notif("errorLoginBlank"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toggleLoader(button);
|
toggleLoader(button);
|
||||||
|
19
ts/form.ts
19
ts/form.ts
@ -1,5 +1,6 @@
|
|||||||
import { Modal } from "./modules/modal.js";
|
import { Modal } from "./modules/modal.js";
|
||||||
import { _get, _post, toggleLoader } from "./modules/common.js";
|
import { _get, _post, toggleLoader } from "./modules/common.js";
|
||||||
|
import { loadLangSelector } from "./modules/lang.js";
|
||||||
|
|
||||||
interface formWindow extends Window {
|
interface formWindow extends Window {
|
||||||
validationStrings: pwValStrings;
|
validationStrings: pwValStrings;
|
||||||
@ -21,23 +22,7 @@ interface pwValStrings {
|
|||||||
[ type: string ]: pwValString;
|
[ type: string ]: pwValString;
|
||||||
}
|
}
|
||||||
|
|
||||||
_get("/lang", null, (req: XMLHttpRequest) => {
|
loadLangSelector("form");
|
||||||
if (req.readyState == 4) {
|
|
||||||
if (req.status != 200) {
|
|
||||||
document.getElementById("lang-dropdown").remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const list = document.getElementById("lang-list") as HTMLDivElement;
|
|
||||||
let innerHTML = '';
|
|
||||||
for (let code in req.response) {
|
|
||||||
innerHTML += `<a href="?lang=${code}" class="button input ~neutral field mb-half">${req.response[code]}</a>`;
|
|
||||||
}
|
|
||||||
list.innerHTML = innerHTML;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.modal = new Modal(document.getElementById("modal-success"));
|
window.modal = new Modal(document.getElementById("modal-success"));
|
||||||
declare var window: formWindow;
|
declare var window: formWindow;
|
||||||
|
@ -100,10 +100,10 @@ class user implements User {
|
|||||||
_post("/users/emails", send, (req: XMLHttpRequest) => {
|
_post("/users/emails", send, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
if (req.status == 200) {
|
if (req.status == 200) {
|
||||||
window.notifications.customPositive("emailChanged", "Success:", `Changed email address of "${this.name}".`);
|
window.notifications.customSuccess("emailChanged", window.lang.var("notifications", "changedEmailAddress", `"${this.name}"`));
|
||||||
} else {
|
} else {
|
||||||
this.email = oldEmail;
|
this.email = oldEmail;
|
||||||
window.notifications.customError("emailChanged", `Couldn't change email address of "${this.name}".`);
|
window.notifications.customError("emailChanged", window.lang.var("notifications", "errorChangedEmailAddress", `"${this.name}"`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -184,12 +184,10 @@ export class accountsList {
|
|||||||
}
|
}
|
||||||
this._modifySettings.classList.remove("unfocused");
|
this._modifySettings.classList.remove("unfocused");
|
||||||
this._deleteUser.classList.remove("unfocused");
|
this._deleteUser.classList.remove("unfocused");
|
||||||
(this._checkCount == 1) ? this._deleteUser.textContent = "Delete User" : this._deleteUser.textContent = "Delete Users";
|
this._deleteUser.textContent = window.lang.quantity("deleteUser", this._checkCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _genCountString = (): string => { return `${this._checkCount} user${(this._checkCount > 1) ? "s" : ""}`; }
|
|
||||||
|
|
||||||
private _collectUsers = (): string[] => {
|
private _collectUsers = (): string[] => {
|
||||||
let list: string[] = [];
|
let list: string[] = [];
|
||||||
for (let id in this._users) {
|
for (let id in this._users) {
|
||||||
@ -208,7 +206,7 @@ export class accountsList {
|
|||||||
};
|
};
|
||||||
for (let field in send) {
|
for (let field in send) {
|
||||||
if (!send[field]) {
|
if (!send[field]) {
|
||||||
window.notifications.customError("addUserBlankField", "Fields were left blank.");
|
window.notifications.customError("addUserBlankField", window.lang.notif("errorBlankFields"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,7 +215,7 @@ export class accountsList {
|
|||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
toggleLoader(button);
|
toggleLoader(button);
|
||||||
if (req.status == 200) {
|
if (req.status == 200) {
|
||||||
window.notifications.customPositive("addUser", "Success:", `user "${send['username']}" created.`);
|
window.notifications.customSuccess("addUser", window.lang.var("notifications", "userCreated", `"${send['username']}"`));
|
||||||
}
|
}
|
||||||
this.reload();
|
this.reload();
|
||||||
window.modals.addUser.close();
|
window.modals.addUser.close();
|
||||||
@ -227,7 +225,7 @@ export class accountsList {
|
|||||||
|
|
||||||
deleteUsers = () => {
|
deleteUsers = () => {
|
||||||
const modalHeader = document.getElementById("header-delete-user");
|
const modalHeader = document.getElementById("header-delete-user");
|
||||||
modalHeader.textContent = this._genCountString();
|
modalHeader.textContent = window.lang.quantity("deleteNUsers", this._checkCount);
|
||||||
let list = this._collectUsers();
|
let list = this._collectUsers();
|
||||||
const form = document.getElementById("form-delete-user") as HTMLFormElement;
|
const form = document.getElementById("form-delete-user") as HTMLFormElement;
|
||||||
const button = form.querySelector("span.submit") as HTMLSpanElement;
|
const button = form.querySelector("span.submit") as HTMLSpanElement;
|
||||||
@ -247,13 +245,13 @@ export class accountsList {
|
|||||||
toggleLoader(button);
|
toggleLoader(button);
|
||||||
window.modals.deleteUser.close();
|
window.modals.deleteUser.close();
|
||||||
if (req.status != 200 && req.status != 204) {
|
if (req.status != 200 && req.status != 204) {
|
||||||
let errorMsg = "Failed (check console/logs).";
|
let errorMsg = window.lang.notif("errorFailureCheckLogs");
|
||||||
if (!("error" in req.response)) {
|
if (!("error" in req.response)) {
|
||||||
errorMsg = "Partial failure (check console/logs).";
|
errorMsg = window.lang.notif("errorPartialFailureCheckLogs");
|
||||||
}
|
}
|
||||||
window.notifications.customError("deleteUserError", errorMsg);
|
window.notifications.customError("deleteUserError", errorMsg);
|
||||||
} else {
|
} else {
|
||||||
window.notifications.customPositive("deleteUserSuccess", "Success:", `deleted ${this._genCountString()}.`);
|
window.notifications.customSuccess("deleteUserSuccess", window.lang.quantity("deletedUser", this._checkCount));
|
||||||
}
|
}
|
||||||
this.reload();
|
this.reload();
|
||||||
}
|
}
|
||||||
@ -264,7 +262,7 @@ export class accountsList {
|
|||||||
|
|
||||||
modifyUsers = () => {
|
modifyUsers = () => {
|
||||||
const modalHeader = document.getElementById("header-modify-user");
|
const modalHeader = document.getElementById("header-modify-user");
|
||||||
modalHeader.textContent = this._genCountString();
|
modalHeader.textContent = window.lang.quantity("modifySettingsFor", this._checkCount)
|
||||||
let list = this._collectUsers();
|
let list = this._collectUsers();
|
||||||
(() => {
|
(() => {
|
||||||
let innerHTML = "";
|
let innerHTML = "";
|
||||||
@ -310,18 +308,18 @@ export class accountsList {
|
|||||||
const homescreen = Object.keys(response["homescreen"]).length;
|
const homescreen = Object.keys(response["homescreen"]).length;
|
||||||
const policy = Object.keys(response["policy"]).length;
|
const policy = Object.keys(response["policy"]).length;
|
||||||
if (homescreen != 0 && policy == 0) {
|
if (homescreen != 0 && policy == 0) {
|
||||||
errorMsg = "Settings were applied, but applying homescreen layout may have failed.";
|
errorMsg = window.lang.notif("errorSettingsAppliedNoHomescreenLayout");
|
||||||
} else if (policy != 0 && homescreen == 0) {
|
} else if (policy != 0 && homescreen == 0) {
|
||||||
errorMsg = "Homescreen layout was applied, but applying settings may have failed.";
|
errorMsg = window.lang.notif("errorHomescreenAppliedNoSettings");
|
||||||
} else if (policy != 0 && homescreen != 0) {
|
} else if (policy != 0 && homescreen != 0) {
|
||||||
errorMsg = "Application failed.";
|
errorMsg = window.lang.notif("errorSettingsFailed");
|
||||||
}
|
}
|
||||||
} else if ("error" in response) {
|
} else if ("error" in response) {
|
||||||
errorMsg = response["error"];
|
errorMsg = response["error"];
|
||||||
}
|
}
|
||||||
window.notifications.customError("modifySettingsError", errorMsg);
|
window.notifications.customError("modifySettingsError", errorMsg);
|
||||||
} else if (req.status == 200 || req.status == 204) {
|
} else if (req.status == 200 || req.status == 204) {
|
||||||
window.notifications.customPositive("modifySettingsSuccess", "Success:", `applied settings to ${this._genCountString()}.`);
|
window.notifications.customSuccess("modifySettingsSuccess", window.lang.quantity("appliedSettings", this._checkCount));
|
||||||
}
|
}
|
||||||
this.reload();
|
this.reload();
|
||||||
window.modals.modifyUser.close();
|
window.modals.modifyUser.close();
|
||||||
@ -331,8 +329,6 @@ export class accountsList {
|
|||||||
window.modals.modifyUser.show();
|
window.modals.modifyUser.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._users = {};
|
this._users = {};
|
||||||
this._selectAll.checked = false;
|
this._selectAll.checked = false;
|
||||||
|
@ -60,7 +60,7 @@ export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHtt
|
|||||||
window.notifications.connectionError();
|
window.notifications.connectionError();
|
||||||
return;
|
return;
|
||||||
} else if (req.status == 401) {
|
} else if (req.status == 401) {
|
||||||
window.notifications.customError("401Error", "Unauthorized. Try logging back in.");
|
window.notifications.customError("401Error", window.lang.notif("error401Unauthorized"));
|
||||||
}
|
}
|
||||||
onreadystatechange(req);
|
onreadystatechange(req);
|
||||||
};
|
};
|
||||||
@ -80,7 +80,7 @@ export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHt
|
|||||||
window.notifications.connectionError();
|
window.notifications.connectionError();
|
||||||
return;
|
return;
|
||||||
} else if (req.status == 401) {
|
} else if (req.status == 401) {
|
||||||
window.notifications.customError("401Error", "Unauthorized. Try logging back in.");
|
window.notifications.customError("401Error", window.lang.notif("error401Unauthorized"));
|
||||||
}
|
}
|
||||||
onreadystatechange(req);
|
onreadystatechange(req);
|
||||||
};
|
};
|
||||||
@ -97,7 +97,7 @@ export function _delete(url: string, data: Object, onreadystatechange: (req: XML
|
|||||||
window.notifications.connectionError();
|
window.notifications.connectionError();
|
||||||
return;
|
return;
|
||||||
} else if (req.status == 401) {
|
} else if (req.status == 401) {
|
||||||
window.notifications.customError("401Error", "Unauthorized. Try logging back in.");
|
window.notifications.customError("401Error", window.lang.notif("error401Unauthorized"));
|
||||||
}
|
}
|
||||||
onreadystatechange(req);
|
onreadystatechange(req);
|
||||||
};
|
};
|
||||||
@ -131,7 +131,7 @@ export class notificationBox implements NotificationBox {
|
|||||||
private _error = (message: string): HTMLElement => {
|
private _error = (message: string): HTMLElement => {
|
||||||
const noti = document.createElement('aside');
|
const noti = document.createElement('aside');
|
||||||
noti.classList.add("aside", "~critical", "!normal", "mt-half", "notification-error");
|
noti.classList.add("aside", "~critical", "!normal", "mt-half", "notification-error");
|
||||||
noti.innerHTML = `<strong>Error:</strong> ${message}`;
|
noti.innerHTML = `<strong>${window.lang.strings("error")}:</strong> ${message}`;
|
||||||
const closeButton = document.createElement('span') as HTMLSpanElement;
|
const closeButton = document.createElement('span') as HTMLSpanElement;
|
||||||
closeButton.classList.add("button", "~critical", "!low", "ml-1");
|
closeButton.classList.add("button", "~critical", "!low", "ml-1");
|
||||||
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;
|
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;
|
||||||
@ -152,7 +152,7 @@ export class notificationBox implements NotificationBox {
|
|||||||
return noti;
|
return noti;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionError = () => { this.customError("connectionError", "Couldn't connect to jfa-go."); }
|
connectionError = () => { this.customError("connectionError", window.lang.notif("errorConnection")); }
|
||||||
|
|
||||||
customError = (type: string, message: string) => {
|
customError = (type: string, message: string) => {
|
||||||
this._errorTypes[type] = this._errorTypes[type] || false;
|
this._errorTypes[type] = this._errorTypes[type] || false;
|
||||||
@ -179,6 +179,8 @@ export class notificationBox implements NotificationBox {
|
|||||||
this._positiveTypes[type] = true;
|
this._positiveTypes[type] = true;
|
||||||
setTimeout(() => { if (this._box.contains(noti)) { this._box.removeChild(noti); this._positiveTypes[type] = false; } }, this.timeout*1000);
|
setTimeout(() => { if (this._box.contains(noti)) { this._box.removeChild(noti); this._positiveTypes[type] = false; } }, this.timeout*1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
customSuccess = (type: string, message: string) => this.customPositive(type, window.lang.strings("success") + ":", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const whichAnimationEvent = () => {
|
export const whichAnimationEvent = () => {
|
||||||
|
@ -92,7 +92,7 @@ export class DOMInvite implements Invite {
|
|||||||
this._usedBy = uB;
|
this._usedBy = uB;
|
||||||
if (uB.length == 0) {
|
if (uB.length == 0) {
|
||||||
this._right.classList.add("empty");
|
this._right.classList.add("empty");
|
||||||
this._userTable.innerHTML = `<p class="content">None yet!</p>`;
|
this._userTable.innerHTML = `<p class="content">${window.lang.strings("inviteNoUsersCreated")}</p>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._right.classList.remove("empty");
|
this._right.classList.remove("empty");
|
||||||
@ -100,8 +100,8 @@ export class DOMInvite implements Invite {
|
|||||||
<table class="table inv-table">
|
<table class="table inv-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>${window.lang.strings("name")}</th>
|
||||||
<th>Date</th>
|
<th>${window.lang.strings("date")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -153,7 +153,7 @@ export class DOMInvite implements Invite {
|
|||||||
} else {
|
} else {
|
||||||
selected = selected || select.value;
|
selected = selected || select.value;
|
||||||
}
|
}
|
||||||
let innerHTML = `<option value="noProfile" ${noProfile ? "selected" : ""}>No Profile</option>`;
|
let innerHTML = `<option value="noProfile" ${noProfile ? "selected" : ""}>${window.lang.strings("inviteNoProfile")}</option>`;
|
||||||
for (let profile of window.availableProfiles) {
|
for (let profile of window.availableProfiles) {
|
||||||
innerHTML += `<option value="${profile}" ${((profile == selected) && !noProfile) ? "selected" : ""}>${profile}</option>`;
|
innerHTML += `<option value="${profile}" ${((profile == selected) && !noProfile) ? "selected" : ""}>${profile}</option>`;
|
||||||
}
|
}
|
||||||
@ -221,7 +221,7 @@ export class DOMInvite implements Invite {
|
|||||||
this._codeArea.classList.add("inv-codearea");
|
this._codeArea.classList.add("inv-codearea");
|
||||||
this._codeArea.innerHTML = `
|
this._codeArea.innerHTML = `
|
||||||
<a class="invite-link code monospace mr-1" href=""></a>
|
<a class="invite-link code monospace mr-1" href=""></a>
|
||||||
<span class="button ~info !normal" title="Copy invite link"><i class="ri-file-copy-line"></i></span>
|
<span class="button ~info !normal" title="${window.lang.strings("copy")}"><i class="ri-file-copy-line"></i></span>
|
||||||
`;
|
`;
|
||||||
const copyButton = this._codeArea.querySelector("span.button") as HTMLSpanElement;
|
const copyButton = this._codeArea.querySelector("span.button") as HTMLSpanElement;
|
||||||
copyButton.onclick = () => {
|
copyButton.onclick = () => {
|
||||||
@ -248,7 +248,7 @@ export class DOMInvite implements Invite {
|
|||||||
<span class="content sm"></span>
|
<span class="content sm"></span>
|
||||||
</div>
|
</div>
|
||||||
<span class="inv-expiry mr-1"></span>
|
<span class="inv-expiry mr-1"></span>
|
||||||
<span class="button ~critical !normal inv-delete">Delete</span>
|
<span class="button ~critical !normal inv-delete">${window.lang.strings("delete")}</span>
|
||||||
<label>
|
<label>
|
||||||
<i class="icon clickable ri-arrow-down-s-line not-rotated"></i>
|
<i class="icon clickable ri-arrow-down-s-line not-rotated"></i>
|
||||||
<input class="inv-toggle-details unfocused" type="checkbox">
|
<input class="inv-toggle-details unfocused" type="checkbox">
|
||||||
@ -271,23 +271,23 @@ export class DOMInvite implements Invite {
|
|||||||
detailsInner.appendChild(this._left);
|
detailsInner.appendChild(this._left);
|
||||||
this._left.classList.add("inv-profilearea");
|
this._left.classList.add("inv-profilearea");
|
||||||
let innerHTML = `
|
let innerHTML = `
|
||||||
<p class="supra mb-1 top">Profile</p>
|
<p class="supra mb-1 top">${window.lang.strings("profile")}</p>
|
||||||
<div class="select ~neutral !normal inv-profileselect inline-block">
|
<div class="select ~neutral !normal inv-profileselect inline-block">
|
||||||
<select>
|
<select>
|
||||||
<option value="noProfile" selected>No Profile</option>
|
<option value="noProfile" selected>${window.lang.strings("inviteNoProfile")}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
if (window.notificationsEnabled) {
|
if (window.notificationsEnabled) {
|
||||||
innerHTML += `
|
innerHTML += `
|
||||||
<p class="label supra">Notify on:</p>
|
<p class="label supra">${window.lang.strings("notifyEvent")}</p>
|
||||||
<label class="switch block">
|
<label class="switch block">
|
||||||
<input class="inv-notify-expiry" type="checkbox">
|
<input class="inv-notify-expiry" type="checkbox">
|
||||||
<span>On expiry</span>
|
<span>${window.lang.strings("notifyInviteExpiry")}</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="switch block">
|
<label class="switch block">
|
||||||
<input class="inv-notify-creation" type="checkbox">
|
<input class="inv-notify-creation" type="checkbox">
|
||||||
<span>On user creation</span>
|
<span>${window.lang.strings("notifyUserCreation")}</span>
|
||||||
</label>
|
</label>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -306,14 +306,14 @@ export class DOMInvite implements Invite {
|
|||||||
detailsInner.appendChild(this._middle);
|
detailsInner.appendChild(this._middle);
|
||||||
this._middle.classList.add("block");
|
this._middle.classList.add("block");
|
||||||
this._middle.innerHTML = `
|
this._middle.innerHTML = `
|
||||||
<p class="supra mb-1 top">Created <strong class="inv-created"></strong></p>
|
<p class="supra mb-1 top">${window.lang.strings("inviteDateCreated")} <strong class="inv-created"></strong></p>
|
||||||
<p class="supra mb-1">Remaining uses <strong class="inv-remaining"></strong></p>
|
<p class="supra mb-1">${window.lang.strings("inviteRemainingUses")} <strong class="inv-remaining"></strong></p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
this._right = document.createElement('div') as HTMLDivElement;
|
this._right = document.createElement('div') as HTMLDivElement;
|
||||||
detailsInner.appendChild(this._right);
|
detailsInner.appendChild(this._right);
|
||||||
this._right.classList.add("card", "~neutral", "!low", "inv-created-users");
|
this._right.classList.add("card", "~neutral", "!low", "inv-created-users");
|
||||||
this._right.innerHTML = `<strong class="supra table-header">Created users</strong>`;
|
this._right.innerHTML = `<strong class="supra table-header">${window.lang.strings("inviteUsersCreated")}</strong>`;
|
||||||
this._userTable = document.createElement('div') as HTMLDivElement;
|
this._userTable = document.createElement('div') as HTMLDivElement;
|
||||||
this._right.appendChild(this._userTable);
|
this._right.appendChild(this._userTable);
|
||||||
|
|
||||||
@ -376,7 +376,7 @@ export class inviteList implements inviteList {
|
|||||||
<div class="inv inv-empty">
|
<div class="inv inv-empty">
|
||||||
<div class="card ~neutral !normal inv-header flex-expand mt-half">
|
<div class="card ~neutral !normal inv-header flex-expand mt-half">
|
||||||
<div class="inv-codearea">
|
<div class="inv-codearea">
|
||||||
<span class="code monospace">None</span>
|
<span class="code monospace">${window.lang.strings("inviteNoInvites")}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -441,10 +441,10 @@ function parseInvite(invite: { [f: string]: string | number | string[][] | boole
|
|||||||
time += `${invite[fields[i]]}${fields[i][0]} `;
|
time += `${invite[fields[i]]}${fields[i][0]} `;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parsed.expiresIn = `Expires in ${time.slice(0, -1)}`;
|
parsed.expiresIn = window.lang.var("strings", "inviteExpiresInTime", time.slice(0, -1));
|
||||||
parsed.remainingUses = invite["no-limit"] ? "∞" : String(invite["remaining-uses"])
|
parsed.remainingUses = invite["no-limit"] ? "∞" : String(invite["remaining-uses"])
|
||||||
parsed.usedBy = invite["used-by"] as string[][] || [];
|
parsed.usedBy = invite["used-by"] as string[][] || [];
|
||||||
parsed.created = invite["created"] as string || "Unknown";
|
parsed.created = invite["created"] as string || window.lang.strings("unknown");
|
||||||
parsed.profile = invite["profile"] as string || "";
|
parsed.profile = invite["profile"] as string || "";
|
||||||
parsed.notifyExpiry = invite["notify-expiry"] as boolean || false;
|
parsed.notifyExpiry = invite["notify-expiry"] as boolean || false;
|
||||||
parsed.notifyCreation = invite["notify-creation"] as boolean || false;
|
parsed.notifyCreation = invite["notify-creation"] as boolean || false;
|
||||||
@ -566,7 +566,7 @@ export class createInvite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadProfiles = () => {
|
loadProfiles = () => {
|
||||||
let innerHTML = `<option value="noProfile">No Profile</option>`;
|
let innerHTML = `<option value="noProfile">${window.lang.strings("inviteNoProfile")}</option>`;
|
||||||
for (let profile of window.availableProfiles) {
|
for (let profile of window.availableProfiles) {
|
||||||
innerHTML += `<option value="${profile}">${profile}</option>`;
|
innerHTML += `<option value="${profile}">${profile}</option>`;
|
||||||
}
|
}
|
||||||
|
67
ts/modules/lang.ts
Normal file
67
ts/modules/lang.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { _get } from "../modules/common.js";
|
||||||
|
|
||||||
|
interface Meta {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface quantityString {
|
||||||
|
singular: string;
|
||||||
|
plural: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LangFile {
|
||||||
|
meta: Meta;
|
||||||
|
strings: { [key: string]: string };
|
||||||
|
notifications: { [key: string]: string };
|
||||||
|
quantityStrings: { [key: string]: quantityString };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class lang implements Lang {
|
||||||
|
private _lang: LangFile;
|
||||||
|
constructor(lang: LangFile) {
|
||||||
|
this._lang = lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
get = (sect: string, key: string): string => {
|
||||||
|
if (sect == "quantityStrings" || sect == "meta") { return ""; }
|
||||||
|
return this._lang[sect][key];
|
||||||
|
}
|
||||||
|
|
||||||
|
strings = (key: string): string => this.get("strings", key)
|
||||||
|
notif = (key: string): string => this.get("notifications", key)
|
||||||
|
|
||||||
|
var = (sect: string, key: string, ...subs: string[]): string => {
|
||||||
|
if (sect == "quantityStrings" || sect == "meta") { return ""; }
|
||||||
|
let str = this._lang[sect][key];
|
||||||
|
for (let sub of subs) {
|
||||||
|
str = str.replace("{n}", sub);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
quantity = (key: string, number: number): string => {
|
||||||
|
if (number == 1) {
|
||||||
|
return this._lang.quantityStrings[key].singular.replace("{n}", ""+number)
|
||||||
|
}
|
||||||
|
return this._lang.quantityStrings[key].plural.replace("{n}", ""+number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loadLangSelector = (page: string) => _get("/lang/" + page, null, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
if (req.status != 200) {
|
||||||
|
document.getElementById("lang-dropdown").remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const list = document.getElementById("lang-list") as HTMLDivElement;
|
||||||
|
let innerHTML = '';
|
||||||
|
for (let code in req.response) {
|
||||||
|
innerHTML += `<a href="?lang=${code}" class="button input ~neutral field mb-half">${req.response[code]}</a>`;
|
||||||
|
}
|
||||||
|
list.innerHTML = innerHTML;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ class profile implements Profile {
|
|||||||
<td><input type="radio" name="profile-default"></td>
|
<td><input type="radio" name="profile-default"></td>
|
||||||
<td class="profile-from ellipsis"></td>
|
<td class="profile-from ellipsis"></td>
|
||||||
<td class="profile-libraries"></td>
|
<td class="profile-libraries"></td>
|
||||||
<td><span class="button ~critical !normal">Delete</span></td>
|
<td><span class="button ~critical !normal">${window.lang.strings("delete")}</span></td>
|
||||||
`;
|
`;
|
||||||
this._name = this._row.querySelector("b.profile-name");
|
this._name = this._row.querySelector("b.profile-name");
|
||||||
this._adminChip = this._row.querySelector("span.profile-admin") as HTMLSpanElement;
|
this._adminChip = this._row.querySelector("span.profile-admin") as HTMLSpanElement;
|
||||||
@ -71,7 +71,7 @@ class profile implements Profile {
|
|||||||
if (req.status == 200 || req.status == 204) {
|
if (req.status == 200 || req.status == 204) {
|
||||||
this.remove();
|
this.remove();
|
||||||
} else {
|
} else {
|
||||||
window.notifications.customError("profileDelete", `Failed to delete profile "${this.name}"`);
|
window.notifications.customError("profileDelete", window.lang.var("notifications", "errorDeleteProfile", `"${this.name}"`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -98,7 +98,7 @@ export class ProfileEditor {
|
|||||||
get empty(): boolean { return (Object.keys(this._table.children).length == 0) }
|
get empty(): boolean { return (Object.keys(this._table.children).length == 0) }
|
||||||
set empty(state: boolean) {
|
set empty(state: boolean) {
|
||||||
if (state) {
|
if (state) {
|
||||||
this._table.innerHTML = `<tr><td class="empty">None</td></tr>`
|
this._table.innerHTML = `<tr><td class="empty">${window.lang.strings("inviteNoInvites")}</td></tr>`
|
||||||
} else if (this._table.querySelector("td.empty")) {
|
} else if (this._table.querySelector("td.empty")) {
|
||||||
this._table.textContent = ``;
|
this._table.textContent = ``;
|
||||||
}
|
}
|
||||||
@ -133,7 +133,7 @@ export class ProfileEditor {
|
|||||||
this.default = resp.default_profile;
|
this.default = resp.default_profile;
|
||||||
window.modals.profiles.show();
|
window.modals.profiles.show();
|
||||||
} else {
|
} else {
|
||||||
window.notifications.customError("profileEditor", "Failed to load profiles.");
|
window.notifications.customError("profileEditor", window.lang.notif("errorLoadProfiles"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -149,7 +149,7 @@ export class ProfileEditor {
|
|||||||
this.default = newDefault;
|
this.default = newDefault;
|
||||||
} else {
|
} else {
|
||||||
this.default = prevDefault;
|
this.default = prevDefault;
|
||||||
window.notifications.customError("profileDefault", "Failed to set default profile.");
|
window.notifications.customError("profileDefault", window.lang.notif("errorSetDefaultProfile"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -171,7 +171,7 @@ export class ProfileEditor {
|
|||||||
window.modals.profiles.close();
|
window.modals.profiles.close();
|
||||||
window.modals.addProfile.show();
|
window.modals.addProfile.show();
|
||||||
} else {
|
} else {
|
||||||
window.notifications.customError("loadUsers", "Failed to load users.");
|
window.notifications.customError("loadUsers", window.lang.notif("errorLoadUsers"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -191,9 +191,9 @@ export class ProfileEditor {
|
|||||||
window.modals.addProfile.close();
|
window.modals.addProfile.close();
|
||||||
if (req.status == 200 || req.status == 204) {
|
if (req.status == 200 || req.status == 204) {
|
||||||
this.load();
|
this.load();
|
||||||
window.notifications.customPositive("createProfile", "Success:", `created profile "${send['name']}"`);
|
window.notifications.customSuccess("createProfile", window.lang.var("notifications", "createProfile", `"${send['name']}"`));
|
||||||
} else {
|
} else {
|
||||||
window.notifications.customError("createProfile", `Failed to create profile "${send['name']}"`);
|
window.notifications.customError("createProfile", window.lang.var("notifications", "errorCreateProfile", `"${send['name']}"`));
|
||||||
}
|
}
|
||||||
window.modals.profiles.show();
|
window.modals.profiles.show();
|
||||||
}
|
}
|
||||||
|
@ -345,6 +345,17 @@ class DOMSelect implements SSelect {
|
|||||||
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
|
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
|
||||||
};
|
};
|
||||||
this._select.onchange = onValueChange;
|
this._select.onchange = onValueChange;
|
||||||
|
|
||||||
|
const message = document.getElementById("settings-message") as HTMLElement;
|
||||||
|
message.innerHTML = window.lang.var("strings",
|
||||||
|
"settingsRequiredOrRestartMessage",
|
||||||
|
`<span class="badge ~critical">*</span>`,
|
||||||
|
`<span class="badge ~info">R</span>`
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.update(setting);
|
this.update(setting);
|
||||||
}
|
}
|
||||||
update = (s: SSelect) => {
|
update = (s: SSelect) => {
|
||||||
@ -501,9 +512,9 @@ export class settingsList {
|
|||||||
private _send = (config: Object, run?: () => void) => _post("/config", config, (req: XMLHttpRequest) => {
|
private _send = (config: Object, run?: () => void) => _post("/config", config, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
if (req.status == 200 || req.status == 204) {
|
if (req.status == 200 || req.status == 204) {
|
||||||
window.notifications.customPositive("settingsSaved", "Success:", "settings were saved.");
|
window.notifications.customSuccess("settingsSaved", window.lang.notif("saveSettings"));
|
||||||
} else {
|
} else {
|
||||||
window.notifications.customError("settingsSaved", "Couldn't save settings.");
|
window.notifications.customError("settingsSaved", window.lang.notif("errorSaveSettings"));
|
||||||
}
|
}
|
||||||
this.reload();
|
this.reload();
|
||||||
if (run) { run(); }
|
if (run) { run(); }
|
||||||
@ -526,7 +537,7 @@ export class settingsList {
|
|||||||
reload = () => _get("/config", null, (req: XMLHttpRequest) => {
|
reload = () => _get("/config", null, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
if (req.status != 200) {
|
if (req.status != 200) {
|
||||||
window.notifications.customError("settingsLoadError", "Failed to load settings.");
|
window.notifications.customError("settingsLoadError", window.lang.notif("errorLoadSettings"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let settings = req.response as Settings;
|
let settings = req.response as Settings;
|
||||||
@ -558,7 +569,7 @@ class ombiDefaults {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this._button = document.createElement("span") as HTMLSpanElement;
|
this._button = document.createElement("span") as HTMLSpanElement;
|
||||||
this._button.classList.add("button", "~neutral", "!low", "settings-section-button", "mb-half");
|
this._button.classList.add("button", "~neutral", "!low", "settings-section-button", "mb-half");
|
||||||
this._button.innerHTML = `<span class="flex">Ombi user defaults <i class="ri-link-unlink-m ml-half"></i></span>`;
|
this._button.innerHTML = `<span class="flex">${window.lang.strings("ombiUserDefaults")} <i class="ri-link-unlink-m ml-half"></i></span>`;
|
||||||
this._button.onclick = this.load;
|
this._button.onclick = this.load;
|
||||||
this._form = document.getElementById("form-ombi-defaults") as HTMLFormElement;
|
this._form = document.getElementById("form-ombi-defaults") as HTMLFormElement;
|
||||||
this._form.onsubmit = this.send;
|
this._form.onsubmit = this.send;
|
||||||
@ -575,9 +586,9 @@ class ombiDefaults {
|
|||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
toggleLoader(button);
|
toggleLoader(button);
|
||||||
if (req.status == 200 || req.status == 204) {
|
if (req.status == 200 || req.status == 204) {
|
||||||
window.notifications.customPositive("ombiDefaults", "Success:", "stored ombi defaults.");
|
window.notifications.customSuccess("ombiDefaults", window.lang.notif("setOmbiDefaults"));
|
||||||
} else {
|
} else {
|
||||||
window.notifications.customError("ombiDefaults", "Failed to store ombi defaults.");
|
window.notifications.customError("ombiDefaults", window.lang.notif("errorSetOmbiDefaults"));
|
||||||
}
|
}
|
||||||
window.modals.ombiDefaults.close();
|
window.modals.ombiDefaults.close();
|
||||||
}
|
}
|
||||||
@ -600,15 +611,9 @@ class ombiDefaults {
|
|||||||
window.modals.ombiDefaults.show();
|
window.modals.ombiDefaults.show();
|
||||||
} else {
|
} else {
|
||||||
toggleLoader(this._button);
|
toggleLoader(this._button);
|
||||||
window.notifications.customError("ombiLoadError", "Failed to load ombi users.")
|
window.notifications.customError("ombiLoadError", window.lang.notif("errorLoadOmbiUsers"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,12 +27,24 @@ declare interface Window {
|
|||||||
tabs: Tabs;
|
tabs: Tabs;
|
||||||
invites: inviteList;
|
invites: inviteList;
|
||||||
notifications: NotificationBox;
|
notifications: NotificationBox;
|
||||||
|
language: string;
|
||||||
|
lang: Lang;
|
||||||
|
langFile: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface Lang {
|
||||||
|
get: (sect: string, key: string) => string;
|
||||||
|
strings: (key: string) => string;
|
||||||
|
notif: (key: string) => string;
|
||||||
|
var: (sect: string, key: string, ...subs: string[]) => string;
|
||||||
|
quantity: (key: string, number: number) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface NotificationBox {
|
declare interface NotificationBox {
|
||||||
connectionError: () => void;
|
connectionError: () => void;
|
||||||
customError: (type: string, message: string) => void;
|
customError: (type: string, message: string) => void;
|
||||||
customPositive: (type: string, bold: string, message: string) => void;
|
customPositive: (type: string, bold: string, message: string) => void;
|
||||||
|
customSuccess: (type: string, message: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface Tabs {
|
declare interface Tabs {
|
||||||
|
5
views.go
5
views.go
@ -15,9 +15,9 @@ func gcHTML(gc *gin.Context, code int, file string, templ gin.H) {
|
|||||||
func (app *appContext) AdminPage(gc *gin.Context) {
|
func (app *appContext) AdminPage(gc *gin.Context) {
|
||||||
lang := gc.Query("lang")
|
lang := gc.Query("lang")
|
||||||
if lang == "" {
|
if lang == "" {
|
||||||
lang = app.storage.lang.chosenFormLang
|
lang = app.storage.lang.chosenAdminLang
|
||||||
} else if _, ok := app.storage.lang.Form[lang]; !ok {
|
} else if _, ok := app.storage.lang.Form[lang]; !ok {
|
||||||
lang = app.storage.lang.chosenFormLang
|
lang = app.storage.lang.chosenAdminLang
|
||||||
}
|
}
|
||||||
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
||||||
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
|
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
|
||||||
@ -34,6 +34,7 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
|||||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||||
"strings": app.storage.lang.Admin[lang]["strings"],
|
"strings": app.storage.lang.Admin[lang]["strings"],
|
||||||
"quantityStrings": app.storage.lang.Admin[lang]["quantityStrings"],
|
"quantityStrings": app.storage.lang.Admin[lang]["quantityStrings"],
|
||||||
|
"language": app.storage.lang.AdminJSON[lang],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user