mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-01-20 01:49:20 +01:00
Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f50ab36fd | ||
|
|
6124b9b3f3 | ||
|
|
a8ce35c13f | ||
|
|
26d9864051 | ||
|
|
a3a22d353c | ||
|
|
dd5eecf9f9 | ||
|
|
7e0e0b0520 | ||
|
|
8bee09cd01 | ||
|
|
deb117fc34 | ||
|
|
a9a0005007 | ||
|
|
4eb7afead6 | ||
|
|
d1b5b74060 | ||
|
|
cf91ee62ed | ||
|
|
277690ef79 | ||
|
|
f7f3530a33 | ||
|
|
2d3a5c739c | ||
|
|
3dbb993d35 | ||
|
|
508168b49e | ||
|
|
0e1cbd7e7b | ||
|
|
e73ecb7a52 | ||
|
|
62be8adc65 | ||
|
|
acc8892f26 | ||
|
|
0f0355fd01 | ||
|
|
a31f174375 | ||
|
|
18ae03554f | ||
|
|
57e6469564 | ||
|
|
cd2c37057d | ||
|
|
a35ca762e3 | ||
|
|
fd10b2600f | ||
|
|
1384091d95 | ||
|
|
ca29ea2d46 | ||
|
|
d8c9ae4ff6 | ||
|
|
4403ea8e18 | ||
|
|
528829ffda | ||
|
|
84429a3399 | ||
|
|
6be5d6cbcb | ||
|
|
c59ea2000c | ||
|
|
30ee554f56 | ||
|
|
c1d984b86d | ||
|
|
fe1570d0bc | ||
|
|
edfd295fb4 | ||
|
|
d57d33b620 | ||
|
|
aedea1bea6 | ||
|
|
535a100314 | ||
|
|
360c25d084 | ||
|
|
d47afe05f4 | ||
|
|
942792cdfa | ||
|
|
685254950e | ||
|
|
e6cc7fce1a | ||
|
|
d8b1f03ac4 | ||
|
|
d81679fbae | ||
|
|
ebb49fce97 | ||
|
|
0fd4f516b1 | ||
|
|
9fff5781f4 | ||
|
|
e19352a69f | ||
|
|
09b96e5983 | ||
|
|
bd9f4258e2 | ||
|
|
a37cdf43f3 | ||
|
|
4821b30634 | ||
|
|
dd8dfcb2b1 | ||
|
|
73c7f22bd1 | ||
|
|
e7ca335d83 | ||
|
|
3730775018 | ||
|
|
4fcba32f74 | ||
|
|
b39ad5c688 | ||
|
|
a41b382dba | ||
|
|
9092f42834 | ||
|
|
af563aa6e5 | ||
|
|
f37edcb751 | ||
|
|
5d33dcf68e | ||
|
|
947da02b3c | ||
|
|
838d108d25 | ||
|
|
4a19af3353 | ||
|
|
4da1c8c2b6 | ||
|
|
c988239fa8 | ||
|
|
94e3c13b3e | ||
|
|
36f3860c4c | ||
|
|
f78fa28822 | ||
|
|
2de7182c55 | ||
|
|
f3e1606440 | ||
|
|
b7236319ec | ||
|
|
556c31d4ea | ||
|
|
0bf8cd65cd | ||
|
|
4d27f7fc7a | ||
|
|
a4f59203b0 | ||
|
|
eeb9b07bce | ||
|
|
9ae16163bb | ||
|
|
c5ce66bd4d | ||
|
|
da8dd7def8 | ||
|
|
a4b5d6dea8 | ||
|
|
77799b2917 | ||
|
|
5ff3839239 | ||
|
|
d560df5b1e | ||
|
|
91c8ce8089 |
@@ -80,12 +80,15 @@ steps:
|
||||
volumes:
|
||||
- name: ssh_key
|
||||
path: /id_rsa
|
||||
- name: ssh_key2
|
||||
path: /id_rsa2
|
||||
commands:
|
||||
- curl -sL https://git.io/goreleaser > goreleaser
|
||||
- chmod +x goreleaser
|
||||
- ./scripts/version.sh ./goreleaser --snapshot --skip-publish --rm-dist
|
||||
- wget https://builds.hrfee.pw/upload.py
|
||||
- pip3 install requests
|
||||
- bash -c 'sftp -i /id_rsa2 -o StrictHostKeyChecking=no root@161.97.102.153:/mnt/redoc <<< $"put docs/swagger.json jfa-go.json"'
|
||||
- bash -c 'sftp -P 2022 -i /id_rsa -o StrictHostKeyChecking=no root@161.97.102.153:/repo/incoming <<< $"put dist/*.deb"'
|
||||
# - bash -c 'ssh -i /id_rsa root@161.97.102.153 -p 2022 "reprepro -Vb /repo remove trusty-unstable jfa-go"'
|
||||
# - bash -c 'ssh -i /id_rsa root@161.97.102.153 -p 2022 "reprepro -Vb /repo remove trusty-unstable jfa-go-tray"'
|
||||
@@ -99,6 +102,9 @@ volumes:
|
||||
- name: ssh_key
|
||||
host:
|
||||
path: /root/.ssh/id_rsa_packaging
|
||||
- name: ssh_key2
|
||||
host:
|
||||
path: /root/.ssh/docker-build
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,7 +7,7 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
#### Read the [FAQ](https://github.com/hrfee/jfa-go/wiki/FAQ) first!
|
||||
#### Read the [FAQ](https://wiki.jfa-go.com/docs/faq/) first!
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,4 +18,5 @@ instructions-debian.txt
|
||||
cl.md
|
||||
./telegram/
|
||||
mautrix/
|
||||
tempts/
|
||||
matacc.txt
|
||||
|
||||
@@ -28,7 +28,7 @@ before:
|
||||
- npx esbuild --bundle ts/crash.ts --outfile=./data/crash.js --minify
|
||||
- cp html/crash.html data/
|
||||
- npx uncss data/crash.html --csspath web/css --output data/bundle.css
|
||||
- npx inline-source --root data data/crash.html data/crash.html
|
||||
- node scripts/inline.js root data data/crash.html data/crash.html
|
||||
- rm data/bundle.css
|
||||
- mv data/crash.html data/html/
|
||||
- go get -u github.com/swaggo/swag/cmd/swag
|
||||
|
||||
39
Makefile
39
Makefile
@@ -52,14 +52,17 @@ ifeq ($(DEBUG), on)
|
||||
SOURCEMAP := --sourcemap
|
||||
TYPECHECK := tsc -noEmit --project ts/tsconfig.json
|
||||
# jank
|
||||
COPYTS := rm -r $(DATA)/web/js/ts; cp -r ts $(DATA)/web/js
|
||||
COPYTS := rm -r $(DATA)/web/js/ts; cp -r tempts $(DATA)/web/js
|
||||
UNCSS := cp $(DATA)/web/css/bundle.css $(DATA)/bundle.css
|
||||
TAILWIND := --content ""
|
||||
else
|
||||
LDFLAGS := -s -w $(LDFLAGS)
|
||||
SOURCEMAP :=
|
||||
COPYTS :=
|
||||
TYPECHECK :=
|
||||
UNCSS := npx uncss $(DATA)/crash.html --csspath web/css --output $(DATA)/bundle.css
|
||||
UNCSS := npx tailwindcss -i $(DATA)/web/css/bundle.css -o $(DATA)/bundle.css --content "html/crash.html"
|
||||
# UNCSS := npx uncss $(DATA)/crash.html --csspath web/css --output $(DATA)/bundle.css
|
||||
TAILWIND :=
|
||||
endif
|
||||
|
||||
RACE ?= off
|
||||
@@ -91,17 +94,22 @@ email:
|
||||
|
||||
typescript:
|
||||
$(TYPECHECK)
|
||||
$(adding dark variants to typescript)
|
||||
-rm -r tempts
|
||||
cp -r ts tempts
|
||||
scripts/dark-variant.sh ts tempts
|
||||
scripts/dark-variant.sh ts tempts/modules
|
||||
$(info compiling typescript)
|
||||
-mkdir -p $(DATA)/web/js
|
||||
-$(ESBUILD) --bundle ts/admin.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/admin.js --minify
|
||||
-$(ESBUILD) --bundle ts/pwr.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/pwr.js --minify
|
||||
-$(ESBUILD) --bundle ts/form.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/form.js --minify
|
||||
-$(ESBUILD) --bundle ts/setup.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/setup.js --minify
|
||||
-$(ESBUILD) --bundle ts/crash.ts --outfile=./$(DATA)/crash.js --minify
|
||||
-$(ESBUILD) --bundle tempts/admin.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/admin.js --minify
|
||||
-$(ESBUILD) --bundle tempts/pwr.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/pwr.js --minify
|
||||
-$(ESBUILD) --bundle tempts/form.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/form.js --minify
|
||||
-$(ESBUILD) --bundle tempts/setup.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/setup.js --minify
|
||||
-$(ESBUILD) --bundle tempts/crash.ts --outfile=./$(DATA)/crash.js --minify
|
||||
$(COPYTS)
|
||||
|
||||
swagger:
|
||||
$(GOBINARY) get github.com/swaggo/swag/cmd/swag
|
||||
$(GOBINARY) install github.com/swaggo/swag/cmd/swag
|
||||
swag init -g main.go
|
||||
|
||||
compile:
|
||||
@@ -118,18 +126,25 @@ bundle-css:
|
||||
-mkdir -p $(DATA)/web/css
|
||||
$(info bundling css)
|
||||
$(ESBUILD) --bundle css/base.css --outfile=$(DATA)/web/css/bundle.css --external:remixicon.css --minify
|
||||
npx tailwindcss -i $(DATA)/web/css/bundle.css -o $(DATA)/web/css/bundle.css $(TAILWIND)
|
||||
# npx postcss -o $(DATA)/web/css/bundle.css $(DATA)/web/css/bundle.css
|
||||
|
||||
inline:
|
||||
cp html/crash.html $(DATA)/crash.html
|
||||
$(UNCSS)
|
||||
npx inline-source --root $(DATA) $(DATA)/crash.html $(DATA)/crash.html
|
||||
node scripts/inline.js root $(DATA) $(DATA)/crash.html $(DATA)/crash.html
|
||||
rm $(DATA)/bundle.css
|
||||
|
||||
variants-html:
|
||||
$(info copying html)
|
||||
cp -r html $(DATA)/
|
||||
$(info adding dark variants to html)
|
||||
node scripts/missing-colors.js html $(DATA)/html
|
||||
|
||||
copy:
|
||||
$(info copying fonts)
|
||||
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/
|
||||
$(info copying html)
|
||||
cp -r html $(DATA)/
|
||||
$(info copying crash page)
|
||||
mv $(DATA)/crash.html $(DATA)/html/
|
||||
$(info copying static data)
|
||||
-mkdir -p $(DATA)/web
|
||||
@@ -159,4 +174,4 @@ clean:
|
||||
-rm docs/docs.go docs/swagger.json docs/swagger.yaml
|
||||
go clean
|
||||
|
||||
all: configuration npm email typescript bundle-css inline swagger copy compile
|
||||
all: configuration npm email typescript variants-html bundle-css inline swagger copy compile
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[](https://hub.docker.com/r/hrfee/jfa-go)
|
||||
[](https://weblate.jfa-go.com/engage/jfa-go/)
|
||||
[](https://wiki.jfa-go.com)
|
||||
[](https://discord.com/invite/MrtvuQmyhP)
|
||||
|
||||
##### Downloads:
|
||||
##### [docker](#docker) | [debian/ubuntu](#debian) | [arch (aur)](#aur) | [other platforms](#other-platforms)
|
||||
@@ -154,3 +155,8 @@ See [CONTRIBUTING.md](https://github.com/hrfee/jfa-go/blob/main/CONTRIBUTING.md)
|
||||
[](https://weblate.jfa-go.com/engage/jfa-go/)
|
||||
|
||||
For translations, use the weblate instance [here](https://weblate.jfa-go.com/engage/jfa-go/). You can login with github.
|
||||
|
||||
#### Sponsors
|
||||
Big thanks to those who sponsor me. You can see them below:
|
||||
|
||||
[<img src="https://sponsors-endpoint.hrfee.pw/sponsor/avatar/0" width="35">](https://sponsors-endpoint.hrfee.pw/sponsor/profile/0)
|
||||
|
||||
328
api.go
328
api.go
@@ -129,10 +129,18 @@ func (app *appContext) checkInvites() {
|
||||
msg, err := app.email.constructExpiry(code, data, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to construct expiry notification: %v", code, err)
|
||||
} else if err := app.email.send(msg, addr); err != nil {
|
||||
app.err.Printf("%s: Failed to send expiry notification: %v", code, err)
|
||||
} else {
|
||||
app.info.Printf("Sent expiry notification to %s", addr)
|
||||
// Check whether notify "address" is an email address of Jellyfin ID
|
||||
if strings.Contains(addr, "@") {
|
||||
err = app.email.send(msg, addr)
|
||||
} else {
|
||||
err = app.sendByID(msg, addr)
|
||||
}
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to send expiry notification: %v", code, err)
|
||||
} else {
|
||||
app.info.Printf("Sent expiry notification to %s", addr)
|
||||
}
|
||||
}
|
||||
}(address)
|
||||
}
|
||||
@@ -171,10 +179,18 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
|
||||
msg, err := app.email.constructExpiry(code, inv, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to construct expiry notification: %v", code, err)
|
||||
} else if err := app.email.send(msg, addr); err != nil {
|
||||
app.err.Printf("%s: Failed to send expiry notification: %v", code, err)
|
||||
} else {
|
||||
app.info.Printf("Sent expiry notification to %s", addr)
|
||||
// Check whether notify "address" is an email address of Jellyfin ID
|
||||
if strings.Contains(addr, "@") {
|
||||
err = app.email.send(msg, addr)
|
||||
} else {
|
||||
err = app.sendByID(msg, addr)
|
||||
}
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to send expiry notification: %v", code, err)
|
||||
} else {
|
||||
app.info.Printf("Sent expiry notification to %s", addr)
|
||||
}
|
||||
}
|
||||
}(address)
|
||||
}
|
||||
@@ -470,10 +486,18 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
|
||||
msg, err := app.email.constructCreated(req.Code, req.Username, req.Email, invite, app, false)
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to construct user creation notification: %v", req.Code, err)
|
||||
} else if err := app.email.send(msg, address); err != nil {
|
||||
app.err.Printf("%s: Failed to send user creation notification: %v", req.Code, err)
|
||||
} else {
|
||||
app.info.Printf("%s: Sent user creation notification to %v", req.Code, address)
|
||||
// Check whether notify "address" is an email address of Jellyfin ID
|
||||
if strings.Contains(address, "@") {
|
||||
err = app.email.send(msg, address)
|
||||
} else {
|
||||
err = app.sendByID(msg, address)
|
||||
}
|
||||
if err != nil {
|
||||
app.err.Printf("%s: Failed to send user creation notification: %v", req.Code, err)
|
||||
} else {
|
||||
app.info.Printf("Sent user creation notification to %s", address)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -503,24 +527,23 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
|
||||
app.err.Printf("%s: Failed to set configuration template (%d): %v", req.Code, status, err)
|
||||
}
|
||||
}
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
if profile.Ombi != nil && len(profile.Ombi) != 0 {
|
||||
errors, code, err := app.ombi.NewUser(req.Username, req.Password, req.Email, profile.Ombi)
|
||||
if err != nil || code != 200 {
|
||||
app.info.Printf("Failed to create Ombi user (%d): %s", code, err)
|
||||
app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", "))
|
||||
} else {
|
||||
app.info.Println("Created Ombi user")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if app.config.Section("password_resets").Key("enabled").MustBool(false) {
|
||||
if req.Email != "" {
|
||||
app.storage.emails[id] = EmailAddress{Addr: req.Email, Contact: true}
|
||||
app.storage.storeEmails()
|
||||
}
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
app.storage.loadOmbiTemplate()
|
||||
if len(app.storage.ombi_template) != 0 {
|
||||
errors, code, err := app.ombi.NewUser(req.Username, req.Password, req.Email, app.storage.ombi_template)
|
||||
if err != nil || code != 200 {
|
||||
app.info.Printf("Failed to create Ombi user (%d): %s", code, err)
|
||||
app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", "))
|
||||
} else {
|
||||
app.info.Println("Created Ombi user")
|
||||
}
|
||||
}
|
||||
}
|
||||
expiry := time.Time{}
|
||||
if invite.UserExpiry {
|
||||
app.storage.usersLock.Lock()
|
||||
@@ -621,6 +644,11 @@ func (app *appContext) NewUser(gc *gin.Context) {
|
||||
gc.JSON(200, validation)
|
||||
return
|
||||
}
|
||||
if emailEnabled && app.config.Section("email").Key("required").MustBool(false) && !strings.Contains(req.Email, "@") {
|
||||
app.info.Printf("%s: New user failed: Email Required", req.Code)
|
||||
respond(400, "errorNoEmail", gc)
|
||||
return
|
||||
}
|
||||
f, success := app.newUser(req, false)
|
||||
if !success {
|
||||
f(gc)
|
||||
@@ -758,7 +786,7 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Extend time before the user(s) expiry.
|
||||
// @Summary Extend time before the user(s) expiry, or create and expiry if it doesn't exist.
|
||||
// @Produce json
|
||||
// @Param extendExpiryDTO body extendExpiryDTO true "Extend expiry object"
|
||||
// @Success 200 {object} boolResponse
|
||||
@@ -780,6 +808,9 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
|
||||
if expiry, ok := app.storage.users[id]; ok {
|
||||
app.storage.users[id] = expiry.AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute)
|
||||
app.debug.Printf("Expiry extended for \"%s\"", id)
|
||||
} else {
|
||||
app.storage.users[id] = time.Now().AddDate(0, req.Months, req.Days).Add(time.Duration(((60 * req.Hours) + req.Minutes)) * time.Minute)
|
||||
app.debug.Printf("Created expiry for \"%s\"", id)
|
||||
}
|
||||
}
|
||||
if err := app.storage.storeUsers(); err != nil {
|
||||
@@ -918,6 +949,74 @@ func (app *appContext) DeleteAnnounceTemplate(gc *gin.Context) {
|
||||
respondBool(200, false, gc)
|
||||
}
|
||||
|
||||
// @Summary Generate password reset links for a list of users, sending the links to them if possible.
|
||||
// @Produce json
|
||||
// @Param AdminPasswordResetDTO body AdminPasswordResetDTO true "List of user IDs"
|
||||
// @Success 204 {object} boolResponse
|
||||
// @Success 200 {object} AdminPasswordResetRespDTO
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Router /users/password-reset [post]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) AdminPasswordReset(gc *gin.Context) {
|
||||
var req AdminPasswordResetDTO
|
||||
gc.BindJSON(&req)
|
||||
if req.Users == nil || len(req.Users) == 0 {
|
||||
app.debug.Println("Ignoring empty request for PWR")
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
linkCount := 0
|
||||
var pwr InternalPWR
|
||||
var err error
|
||||
resp := AdminPasswordResetRespDTO{}
|
||||
for _, id := range req.Users {
|
||||
pwr, err = app.GenInternalReset(id)
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to get user from Jellyfin: %v", err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
if app.internalPWRs == nil {
|
||||
app.internalPWRs = map[string]InternalPWR{}
|
||||
}
|
||||
app.internalPWRs[pwr.PIN] = pwr
|
||||
sendAddress := app.getAddressOrName(id)
|
||||
if sendAddress == "" || len(req.Users) == 1 {
|
||||
resp.Link, err = app.GenResetLink(pwr.PIN)
|
||||
linkCount++
|
||||
if sendAddress == "" {
|
||||
resp.Manual = true
|
||||
}
|
||||
}
|
||||
if sendAddress != "" {
|
||||
msg, err := app.email.constructReset(
|
||||
PasswordReset{
|
||||
Pin: pwr.PIN,
|
||||
Username: pwr.Username,
|
||||
Expiry: pwr.Expiry,
|
||||
Internal: true,
|
||||
}, app, false,
|
||||
)
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to construct password reset message for \"%s\": %v", pwr.Username, err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
} else if err := app.sendByID(msg, id); err != nil {
|
||||
app.err.Printf("Failed to send password reset message to \"%s\": %v", sendAddress, err)
|
||||
} else {
|
||||
app.info.Printf("Sent password reset message to \"%s\"", sendAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
if resp.Link != "" && linkCount == 1 {
|
||||
gc.JSON(200, resp)
|
||||
return
|
||||
}
|
||||
respondBool(204, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Create a new invite.
|
||||
// @Produce json
|
||||
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
|
||||
@@ -1150,6 +1249,7 @@ func (app *appContext) GetProfiles(gc *gin.Context) {
|
||||
Admin: p.Admin,
|
||||
LibraryAccess: p.LibraryAccess,
|
||||
FromUser: p.FromUser,
|
||||
Ombi: p.Ombi != nil,
|
||||
}
|
||||
}
|
||||
gc.JSON(200, out)
|
||||
@@ -1269,15 +1369,16 @@ func (app *appContext) SetNotify(gc *gin.Context) {
|
||||
return
|
||||
}
|
||||
var address string
|
||||
if app.config.Section("ui").Key("jellyfin_login").MustBool(false) {
|
||||
addr, ok := app.storage.emails[gc.GetString("jfId")]
|
||||
if !ok {
|
||||
app.err.Printf("%s: Couldn't find email address. Make sure it's set", code)
|
||||
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(false)
|
||||
if jellyfinLogin {
|
||||
var addressAvailable bool = app.getAddressOrName(gc.GetString("jfId")) != ""
|
||||
if !addressAvailable {
|
||||
app.err.Printf("%s: Couldn't find contact method for admin. Make sure one is set.", code)
|
||||
app.debug.Printf("%s: User ID \"%s\"", code, gc.GetString("jfId"))
|
||||
respond(500, "Missing user email", gc)
|
||||
respond(500, "Missing user contact method", gc)
|
||||
return
|
||||
}
|
||||
address = addr.Addr
|
||||
address = gc.GetString("jfId")
|
||||
} else {
|
||||
address = app.config.Section("ui").Key("email").String()
|
||||
}
|
||||
@@ -1415,26 +1516,65 @@ func (app *appContext) OmbiUsers(gc *gin.Context) {
|
||||
gc.JSON(200, ombiUsersDTO{Users: userlist})
|
||||
}
|
||||
|
||||
// @Summary Set new user defaults for Ombi accounts.
|
||||
// @Summary Store Ombi user template in an existing profile.
|
||||
// @Produce json
|
||||
// @Param ombiUser body ombiUser true "User to source settings from"
|
||||
// @Param profile path string true "Name of profile to store in"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /ombi/defaults [post]
|
||||
// @Router /profiles/ombi/{profile} [post]
|
||||
// @Security Bearer
|
||||
// @tags Ombi
|
||||
func (app *appContext) SetOmbiDefaults(gc *gin.Context) {
|
||||
func (app *appContext) SetOmbiProfile(gc *gin.Context) {
|
||||
var req ombiUser
|
||||
gc.BindJSON(&req)
|
||||
profileName := gc.Param("profile")
|
||||
profile, ok := app.storage.profiles[profileName]
|
||||
if !ok {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
template, code, err := app.ombi.TemplateByID(req.ID)
|
||||
if err != nil || code != 200 || len(template) == 0 {
|
||||
app.err.Printf("Couldn't get user from Ombi (%d): %v", code, err)
|
||||
respond(500, "Couldn't get user", gc)
|
||||
return
|
||||
}
|
||||
app.storage.ombi_template = template
|
||||
app.storage.storeOmbiTemplate()
|
||||
respondBool(200, true, gc)
|
||||
profile.Ombi = template
|
||||
app.storage.profiles[profileName] = profile
|
||||
if err := app.storage.storeProfiles(); err != nil {
|
||||
respond(500, "Failed to store profile", gc)
|
||||
app.err.Printf("Failed to store profiles: %v", err)
|
||||
return
|
||||
}
|
||||
respondBool(204, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Remove ombi user template from a profile.
|
||||
// @Produce json
|
||||
// @Param profile path string true "Name of profile to store in"
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Failure 400 {object} boolResponse
|
||||
// @Failure 500 {object} stringResponse
|
||||
// @Router /profiles/ombi/{profile} [delete]
|
||||
// @Security Bearer
|
||||
// @tags Ombi
|
||||
func (app *appContext) DeleteOmbiProfile(gc *gin.Context) {
|
||||
profileName := gc.Param("profile")
|
||||
profile, ok := app.storage.profiles[profileName]
|
||||
if !ok {
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
profile.Ombi = nil
|
||||
app.storage.profiles[profileName] = profile
|
||||
if err := app.storage.storeProfiles(); err != nil {
|
||||
respond(500, "Failed to store profile", gc)
|
||||
app.err.Printf("Failed to store profiles: %v", err)
|
||||
return
|
||||
}
|
||||
respondBool(204, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Modify user's email addresses.
|
||||
@@ -1505,38 +1645,70 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
||||
gc.JSON(400, validation)
|
||||
return
|
||||
}
|
||||
resp, status, err := app.jf.ResetPassword(req.PIN)
|
||||
if status != 200 || err != nil || !resp.Success {
|
||||
app.err.Printf("Password Reset failed (%d): %v", status, err)
|
||||
respondBool(status, false, gc)
|
||||
return
|
||||
isInternal := false
|
||||
var userID, username string
|
||||
if reset, ok := app.internalPWRs[req.PIN]; ok {
|
||||
isInternal = true
|
||||
if time.Now().After(reset.Expiry) {
|
||||
app.info.Printf("Password reset failed: PIN \"%s\" has expired", reset.PIN)
|
||||
respondBool(401, false, gc)
|
||||
delete(app.internalPWRs, req.PIN)
|
||||
return
|
||||
}
|
||||
userID = reset.ID
|
||||
username = reset.Username
|
||||
status, err := app.jf.ResetPasswordAdmin(userID)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
app.err.Printf("Password Reset failed (%d): %v", status, err)
|
||||
respondBool(status, false, gc)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
resp, status, err := app.jf.ResetPassword(req.PIN)
|
||||
if status != 200 || err != nil || !resp.Success {
|
||||
app.err.Printf("Password Reset failed (%d): %v", status, err)
|
||||
respondBool(status, false, gc)
|
||||
return
|
||||
}
|
||||
if req.Password == "" || len(resp.UsersReset) == 0 {
|
||||
respondBool(200, false, gc)
|
||||
return
|
||||
}
|
||||
username = resp.UsersReset[0]
|
||||
}
|
||||
if req.Password == "" || len(resp.UsersReset) == 0 {
|
||||
respondBool(200, false, gc)
|
||||
return
|
||||
var user mediabrowser.User
|
||||
var status int
|
||||
var err error
|
||||
if isInternal {
|
||||
user, status, err = app.jf.UserByID(userID, false)
|
||||
} else {
|
||||
user, status, err = app.jf.UserByName(username, false)
|
||||
}
|
||||
user, status, err := app.jf.UserByName(resp.UsersReset[0], false)
|
||||
if status != 200 || err != nil {
|
||||
app.err.Printf("Failed to get user \"%s\" (%d): %v", resp.UsersReset[0], status, err)
|
||||
app.err.Printf("Failed to get user \"%s\" (%d): %v", username, status, err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
status, err = app.jf.SetPassword(user.ID, req.PIN, req.Password)
|
||||
prevPassword := req.PIN
|
||||
if isInternal {
|
||||
prevPassword = ""
|
||||
}
|
||||
status, err = app.jf.SetPassword(user.ID, prevPassword, req.Password)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
app.err.Printf("Failed to change password for \"%s\" (%d): %v", resp.UsersReset[0], status, err)
|
||||
app.err.Printf("Failed to change password for \"%s\" (%d): %v", username, status, err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
// Silently fail for changing ombi passwords
|
||||
if status != 200 || err != nil {
|
||||
app.err.Printf("Failed to get user \"%s\" from jellyfin/emby (%d): %v", resp.UsersReset[0], status, err)
|
||||
app.err.Printf("Failed to get user \"%s\" from jellyfin/emby (%d): %v", username, status, err)
|
||||
respondBool(200, true, gc)
|
||||
return
|
||||
}
|
||||
ombiUser, status, err := app.getOmbiUser(user.ID)
|
||||
if status != 200 || err != nil {
|
||||
app.err.Printf("Failed to get user \"%s\" from ombi (%d): %v", resp.UsersReset[0], status, err)
|
||||
app.err.Printf("Failed to get user \"%s\" from ombi (%d): %v", username, status, err)
|
||||
respondBool(200, true, gc)
|
||||
return
|
||||
}
|
||||
@@ -1568,6 +1740,7 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
||||
var policy mediabrowser.Policy
|
||||
var configuration mediabrowser.Configuration
|
||||
var displayprefs map[string]interface{}
|
||||
var ombi map[string]interface{}
|
||||
if req.From == "profile" {
|
||||
app.storage.loadProfiles()
|
||||
// Check profile exists & isn't empty
|
||||
@@ -1586,6 +1759,13 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
||||
displayprefs = app.storage.profiles[req.Profile].Displayprefs
|
||||
}
|
||||
policy = app.storage.profiles[req.Profile].Policy
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
profile := app.storage.profiles[req.Profile]
|
||||
if profile.Ombi != nil && len(profile.Ombi) != 0 {
|
||||
ombi = profile.Ombi
|
||||
}
|
||||
}
|
||||
|
||||
} else if req.From == "user" {
|
||||
applyingFrom = "user"
|
||||
app.jf.CacheExpiry = time.Now()
|
||||
@@ -1611,27 +1791,72 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
||||
errors := errorListDTO{
|
||||
"policy": map[string]string{},
|
||||
"homescreen": map[string]string{},
|
||||
"ombi": map[string]string{},
|
||||
}
|
||||
/* Jellyfin doesn't seem to like too many of these requests sent in succession
|
||||
and can crash and mess up its database. Issue #160 says this occurs when more
|
||||
than 100 users are modified. A delay totalling 500ms between requests is used
|
||||
if so. */
|
||||
var shouldDelay bool = len(req.ApplyTo) >= 100
|
||||
if shouldDelay {
|
||||
app.debug.Println("Adding delay between requests for large batch")
|
||||
}
|
||||
for _, id := range req.ApplyTo {
|
||||
status, err := app.jf.SetPolicy(id, policy)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
errors["policy"][id] = fmt.Sprintf("%d: %s", status, err)
|
||||
}
|
||||
if shouldDelay {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
if req.Homescreen {
|
||||
status, err = app.jf.SetConfiguration(id, configuration)
|
||||
errorString := ""
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
errorString += fmt.Sprintf("Configuration %d: %s ", status, err)
|
||||
errorString += fmt.Sprintf("Configuration %d: %v ", status, err)
|
||||
} else {
|
||||
status, err = app.jf.SetDisplayPreferences(id, displayprefs)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
errorString += fmt.Sprintf("Displayprefs %d: %s ", status, err)
|
||||
errorString += fmt.Sprintf("Displayprefs %d: %v ", status, err)
|
||||
}
|
||||
}
|
||||
if errorString != "" {
|
||||
errors["homescreen"][id] = errorString
|
||||
}
|
||||
}
|
||||
if ombi != nil {
|
||||
errorString := ""
|
||||
user, status, err := app.getOmbiUser(id)
|
||||
if status != 200 || err != nil {
|
||||
errorString += fmt.Sprintf("Ombi GetUser %d: %v ", status, err)
|
||||
} else {
|
||||
// newUser := ombi
|
||||
// newUser["id"] = user["id"]
|
||||
// newUser["userName"] = user["userName"]
|
||||
// newUser["alias"] = user["alias"]
|
||||
// newUser["emailAddress"] = user["emailAddress"]
|
||||
for k, v := range ombi {
|
||||
switch v.(type) {
|
||||
case map[string]interface{}, []interface{}:
|
||||
user[k] = v
|
||||
default:
|
||||
if v != user[k] {
|
||||
user[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
status, err = app.ombi.ModifyUser(user)
|
||||
if status != 200 || err != nil {
|
||||
errorString += fmt.Sprintf("Apply %d: %v ", status, err)
|
||||
}
|
||||
}
|
||||
if errorString != "" {
|
||||
errors["ombi"][id] = errorString
|
||||
}
|
||||
}
|
||||
if shouldDelay {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
code := 200
|
||||
if len(errors["policy"]) == len(req.ApplyTo) || len(errors["homescreen"]) == len(req.ApplyTo) {
|
||||
@@ -1828,6 +2053,8 @@ func (app *appContext) GetCustomEmails(gc *gin.Context) {
|
||||
|
||||
func (app *appContext) getCustomEmail(id string) *customEmail {
|
||||
switch id {
|
||||
case "Announcement":
|
||||
return &customEmail{}
|
||||
case "UserCreated":
|
||||
return &app.storage.customEmails.UserCreated
|
||||
case "InviteExpiry":
|
||||
@@ -1938,6 +2165,7 @@ func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
||||
emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress")
|
||||
email := app.getCustomEmail(id)
|
||||
if email == nil {
|
||||
app.err.Printf("Failed to get custom email with ID \"%s\"", id)
|
||||
respondBool(400, false, gc)
|
||||
return
|
||||
}
|
||||
|
||||
2
auth.go
2
auth.go
@@ -100,7 +100,7 @@ type getTokenDTO struct {
|
||||
}
|
||||
|
||||
// @Summary Grabs an API token using username & password.
|
||||
// @description Click the lock icon next to this, login with your normal jfa-go credentials. Click 'try it out', then 'execute' and an API Key will be returned, copy it (not including quotes). On any of the other routes, click the lock icon and set the API key as "Bearer `your api key`".
|
||||
// @description If viewing docs locally, click the lock icon next to this, login with your normal jfa-go credentials. Click 'try it out', then 'execute' and an API Key will be returned, copy it (not including quotes). On any of the other routes, click the lock icon and set the API key as "Bearer `your api key`".
|
||||
// @Produce json
|
||||
// @Success 200 {object} getTokenDTO
|
||||
// @Failure 401 {object} stringResponse
|
||||
|
||||
15
config.go
15
config.go
@@ -39,6 +39,8 @@ func (app *appContext) loadConfig() error {
|
||||
|
||||
app.MustSetValue("jellyfin", "public_server", app.config.Section("jellyfin").Key("server").String())
|
||||
|
||||
app.MustSetValue("ui", "redirect_url", app.config.Section("jellyfin").Key("public_server").String())
|
||||
|
||||
for _, key := range app.config.Section("files").Keys() {
|
||||
if name := key.Name(); name != "html_templates" && name != "lang_files" {
|
||||
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
|
||||
@@ -71,6 +73,9 @@ func (app *appContext) loadConfig() error {
|
||||
app.MustSetValue("deletion", "email_html", "jfa-go:"+"deleted.html")
|
||||
app.MustSetValue("deletion", "email_text", "jfa-go:"+"deleted.txt")
|
||||
|
||||
app.MustSetValue("smtp", "hello_hostname", "localhost")
|
||||
app.MustSetValue("smtp", "cert_validation", "true")
|
||||
|
||||
jfUrl := app.config.Section("jellyfin").Key("server").String()
|
||||
if !(strings.HasPrefix(jfUrl, "http://") || strings.HasPrefix(jfUrl, "https://")) {
|
||||
app.config.Section("jellyfin").Key("server").SetValue("http://" + jfUrl)
|
||||
@@ -93,6 +98,11 @@ func (app *appContext) loadConfig() error {
|
||||
app.MustSetValue("user_expiry", "email_text", "jfa-go:"+"user-expired.txt")
|
||||
|
||||
app.MustSetValue("matrix", "topic", "Jellyfin notifications")
|
||||
app.MustSetValue("matrix", "show_on_reg", "true")
|
||||
|
||||
app.MustSetValue("discord", "show_on_reg", "true")
|
||||
|
||||
app.MustSetValue("telegram", "show_on_reg", "true")
|
||||
|
||||
app.config.Section("jellyfin").Key("version").SetValue(version)
|
||||
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
|
||||
@@ -149,6 +159,11 @@ func (app *appContext) loadConfig() error {
|
||||
|
||||
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
|
||||
|
||||
if substituteStrings != "" {
|
||||
v := app.config.Section("ui").Key("success_message")
|
||||
v.SetValue(strings.ReplaceAll(v.String(), "Jellyfin", substituteStrings))
|
||||
}
|
||||
|
||||
oldFormLang := app.config.Section("ui").Key("language").MustString("")
|
||||
if oldFormLang != "" {
|
||||
app.storage.lang.chosenFormLang = oldFormLang
|
||||
|
||||
@@ -247,6 +247,15 @@
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "URL base for when running jfa-go with a reverse proxy in a subfolder. include preceding /, e.g \"/accounts\"."
|
||||
},
|
||||
"redirect_url": {
|
||||
"name": "Form success redirect URL",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"advanced": true,
|
||||
"description": "Set a different URL for the sign-up form to redirect the user to when they've signed up. Default to 'Public Server' or 'Server' in the Jellyfin tab."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -458,6 +467,15 @@
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
"description": "Send emails as plain text instead of HTML."
|
||||
},
|
||||
"required": {
|
||||
"name": "Require on sign-up",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"depends_true": "method",
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
"description": "Require an email address on sign-up."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -535,6 +553,15 @@
|
||||
"type": "password",
|
||||
"value": "smtp password"
|
||||
},
|
||||
"hello_hostname": {
|
||||
"name": "HELLO Hostname",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"advanced": true,
|
||||
"type": "text",
|
||||
"value": "localhost",
|
||||
"description": "Hostname sent when sending HELLO to the SMTP server. Some servers don't like the default \"localhost\" value, such as smtp-relay.gmail.com."
|
||||
},
|
||||
"ssl_cert": {
|
||||
"name": "Path to custom SSL certificate",
|
||||
"required": false,
|
||||
@@ -543,6 +570,15 @@
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Use if your SMTP server's SSL Certificate is not trusted by the system."
|
||||
},
|
||||
"cert_validation": {
|
||||
"name": "Verify certificate",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"advanced": true,
|
||||
"type": "bool",
|
||||
"value": true,
|
||||
"description": "Warning, disabling this makes you much more vulnerable to man-in-the-middle attacks"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -561,10 +597,19 @@
|
||||
"value": false,
|
||||
"description": "Enable signup verification through Discord and the sending of notifications through it.\nSee the jfa-go wiki for setting up a bot."
|
||||
},
|
||||
"show_on_reg": {
|
||||
"name": "Show on user registration",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "bool",
|
||||
"depends_true": "enabled",
|
||||
"value": true,
|
||||
"description": "Allow users to link their Discord on the registration page."
|
||||
},
|
||||
"required": {
|
||||
"name": "Require on sign-up",
|
||||
"required": false,
|
||||
"required_restart": true,
|
||||
"requires_restart": true,
|
||||
"depends_true": "enabled",
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
@@ -644,6 +689,15 @@
|
||||
"value": false,
|
||||
"description": "Enable signup verification through Telegram and the sending of notifications through it.\nSee the jfa-go wiki for setting up a bot."
|
||||
},
|
||||
"show_on_reg": {
|
||||
"name": "Show on user registration",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "bool",
|
||||
"depends_true": "enabled",
|
||||
"value": true,
|
||||
"description": "Allow users to link their Telegram on the registration page."
|
||||
},
|
||||
"required": {
|
||||
"name": "Require on sign-up",
|
||||
"required": false,
|
||||
@@ -691,6 +745,15 @@
|
||||
"value": false,
|
||||
"description": "Enable signup verification through Matrix and the sending of notifications through it.\nSee the jfa-go wiki for setting up a bot."
|
||||
},
|
||||
"show_on_reg": {
|
||||
"name": "Show on user registration",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "bool",
|
||||
"depends_true": "enabled",
|
||||
"value": true,
|
||||
"description": "Allow users to link their Matrix on the registration page."
|
||||
},
|
||||
"required": {
|
||||
"name": "Require on sign-up",
|
||||
"required": false,
|
||||
@@ -1002,7 +1065,7 @@
|
||||
"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. To handle password resets for Ombi & Jellyfin, enable \"Use reset link instead of PIN\"."
|
||||
"description": "Connect to Ombi to automatically create both Ombi and Jellyfin accounts for new users. You'll need to add a ombi template to an existing User Profile for accounts to be created, which you can do by refreshing then checking Settings > User Profiles. To handle password resets for Ombi & Jellyfin, enable \"Use reset link instead of PIN\"."
|
||||
},
|
||||
"settings": {
|
||||
"enabled": {
|
||||
|
||||
131
css/base.css
131
css/base.css
@@ -1,4 +1,7 @@
|
||||
@import "../node_modules/a17t/dist/a17t.css";
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@import "remixicon.css";
|
||||
@import "./modal.css";
|
||||
@import "./dark.css";
|
||||
@@ -12,14 +15,39 @@
|
||||
--border-width-8: 8px;
|
||||
}
|
||||
|
||||
.light-theme {
|
||||
.light {
|
||||
--settings-section-button-filter: 90%;
|
||||
}
|
||||
|
||||
.body {
|
||||
.dark {
|
||||
--settings-section-button-filter: 80%;
|
||||
}
|
||||
|
||||
.dark body {
|
||||
background-color: #101010;
|
||||
}
|
||||
|
||||
.dark select, .dark option, .dark input {
|
||||
background: #202020;
|
||||
}
|
||||
|
||||
html:not(.dark) .card.\@low:not(.\~neutral):not(.\~positive):not(.\~urge):not(.\~warning):not(.\~info):not(.\~critical) {
|
||||
--color-fill: va(--color-fill);
|
||||
--color-content: var(--color-content);
|
||||
--color-accent: var(--color-accent);
|
||||
--color-muted: var(--color-muted);
|
||||
background-color: #fff;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.light-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dark-only {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
margin: 5% 20% 5% 20%;
|
||||
}
|
||||
@@ -27,6 +55,7 @@
|
||||
@media (max-width: 1100px) {
|
||||
.page-container {
|
||||
margin: 2%;
|
||||
margin-top: 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +79,8 @@
|
||||
|
||||
.banner.header {
|
||||
margin-bottom: var(--spacing-4,1rem);
|
||||
max-width: calc(100% + 2.2rem); /* no idea why this works */
|
||||
margin-left: -1.1rem;
|
||||
}
|
||||
|
||||
.banner.footer {
|
||||
@@ -70,54 +101,6 @@ div.card:contains(section.banner.footer) {
|
||||
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-half {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.mr-1 {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.p-1 {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.pb-1 {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.pl-1 {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.al {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -203,15 +186,6 @@ span.sm:not(.heading) {
|
||||
}
|
||||
}
|
||||
|
||||
.fr {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.monospace {
|
||||
background-color: inherit; /* so we can use a17t code blocks */
|
||||
font-family: Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
|
||||
}
|
||||
|
||||
sup.\~critical, .text-critical {
|
||||
color: var(--color-critical-normal-content);
|
||||
}
|
||||
@@ -377,10 +351,8 @@ sup.\~critical, .text-critical {
|
||||
}
|
||||
|
||||
.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 {
|
||||
@@ -392,8 +364,6 @@ sup.\~critical, .text-critical {
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
@@ -421,11 +391,21 @@ select, textarea {
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
html.dark textarea {
|
||||
background-color: #202020
|
||||
}
|
||||
|
||||
input {
|
||||
color: inherit;
|
||||
border: 0 solid var(--color-neutral-300);
|
||||
}
|
||||
|
||||
table {
|
||||
color: var(--color-content);
|
||||
}
|
||||
|
||||
|
||||
|
||||
p.top {
|
||||
margin-top: 0px;
|
||||
}
|
||||
@@ -451,12 +431,19 @@ p.top {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown-display.above {
|
||||
top: auto;
|
||||
bottom: 115%;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap; /* css-3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
background-color: var(--color-content-high) !important;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.circle {
|
||||
@@ -537,3 +524,19 @@ div.card:contains(section.banner.footer) {
|
||||
.card.sectioned .section {
|
||||
padding: var(--spacing-4, 1rem);
|
||||
}
|
||||
|
||||
.button.discord.\@low {
|
||||
background-color: rgba(88, 101, 242,60%);
|
||||
}
|
||||
|
||||
.button.discord.\@low:not(.lang-link) {
|
||||
color: rgba(38, 51, 192, 90%);
|
||||
}
|
||||
|
||||
.pb-0i {
|
||||
padding-bottom: 0px !important
|
||||
}
|
||||
|
||||
.text-center-i {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
91
css/dark.css
91
css/dark.css
@@ -1,91 +0,0 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
.dark-theme select option {
|
||||
background: #202020;
|
||||
}
|
||||
|
||||
351
css/dark.js
Normal file
351
css/dark.js
Normal file
@@ -0,0 +1,351 @@
|
||||
var c = {
|
||||
inherit: 'inherit',
|
||||
current: 'currentColor',
|
||||
transparent: 'transparent',
|
||||
black: '#000',
|
||||
white: '#fff',
|
||||
d_neutral: {
|
||||
900: "rgba(255, 255, 255, 0.87)",
|
||||
800: "rgba(255, 255, 255, 0.8)",
|
||||
700: "rgba(255, 255, 255, 0.73)",
|
||||
600: "rgba(255, 255, 255, 0.66)",
|
||||
500: "rgb(153, 153, 153)",
|
||||
400: "#383838",
|
||||
300: "#303030",
|
||||
200: "#292929",
|
||||
100: "#242424",
|
||||
50: "#202020",
|
||||
000: "#101010"
|
||||
},
|
||||
d_critical: {
|
||||
900: "#fef2f2",
|
||||
800: "#fee2e2",
|
||||
700: "#fecaca",
|
||||
600: "#fca5a5",
|
||||
500: "#f87171",
|
||||
400: "#ef4444",
|
||||
300: "#dc2626",
|
||||
200: "#b91c1c",
|
||||
100: "#991b1b",
|
||||
50: "#7f1d1d",
|
||||
000: "#441313"
|
||||
},
|
||||
d_warning: {
|
||||
900: "#fffbeb",
|
||||
800: "#fef3c7",
|
||||
700: "#fde68a",
|
||||
600: "#fcd34d",
|
||||
500: "#fbbf24",
|
||||
400: "#f59e0b",
|
||||
300: "#d97706",
|
||||
200: "#b45309",
|
||||
100: "#92400e",
|
||||
50: "#783900",
|
||||
000: "#411e01"
|
||||
},
|
||||
d_positive: {
|
||||
900: "#f0fdf4",
|
||||
800: "#dcfce7",
|
||||
700: "#bbf7d0",
|
||||
600: "#86efac",
|
||||
500: "#4ade80",
|
||||
400: "#22c55e",
|
||||
300: "#16a34a",
|
||||
200: "#15803d",
|
||||
100: "#166534",
|
||||
50: "#14532d",
|
||||
000: "#0f2e1b"
|
||||
},
|
||||
d_urge: {
|
||||
900: "#e0ffff",
|
||||
800: "#c0fbff",
|
||||
700: "#a0f4ff",
|
||||
600: "#80e9ff",
|
||||
500: "#60dbfb",
|
||||
400: "#40cbf3",
|
||||
300: "#20b9e9",
|
||||
200: "#00a4dc",
|
||||
100: "#0054bc",
|
||||
50: "#00169a",
|
||||
000: "#050076"
|
||||
},
|
||||
d_info: {
|
||||
900: "#f5f3ff",
|
||||
800: "#ede9fe",
|
||||
700: "#ddd6fe",
|
||||
600: "#c4b5fd",
|
||||
500: "#a78bfa",
|
||||
400: "#8b5cf6",
|
||||
300: "#7c3aed",
|
||||
200: "#6d28d9",
|
||||
100: "#5b21b6",
|
||||
50: "#4c1d95",
|
||||
000: "#240e44"
|
||||
},
|
||||
slate: {
|
||||
50: '#f8fafc',
|
||||
100: '#f1f5f9',
|
||||
200: '#e2e8f0',
|
||||
300: '#cbd5e1',
|
||||
400: '#94a3b8',
|
||||
500: '#64748b',
|
||||
600: '#475569',
|
||||
700: '#334155',
|
||||
800: '#1e293b',
|
||||
900: '#0f172a'
|
||||
},
|
||||
gray: {
|
||||
50: '#f9fafb',
|
||||
100: '#f3f4f6',
|
||||
200: '#e5e7eb',
|
||||
300: '#d1d5db',
|
||||
400: '#9ca3af',
|
||||
500: '#6b7280',
|
||||
600: '#4b5563',
|
||||
700: '#374151',
|
||||
800: '#1f2937',
|
||||
900: '#111827'
|
||||
},
|
||||
zinc: {
|
||||
50: '#fafafa',
|
||||
100: '#f4f4f5',
|
||||
200: '#e4e4e7',
|
||||
300: '#d4d4d8',
|
||||
400: '#a1a1aa',
|
||||
500: '#71717a',
|
||||
600: '#52525b',
|
||||
700: '#3f3f46',
|
||||
800: '#27272a',
|
||||
900: '#18181b'
|
||||
},
|
||||
neutral: {
|
||||
50: '#fafafa',
|
||||
100: '#f5f5f5',
|
||||
200: '#e5e5e5',
|
||||
300: '#d4d4d4',
|
||||
400: '#a3a3a3',
|
||||
500: '#737373',
|
||||
600: '#525252',
|
||||
700: '#404040',
|
||||
800: '#262626',
|
||||
900: '#171717'
|
||||
},
|
||||
stone: {
|
||||
50: '#fafaf9',
|
||||
100: '#f5f5f4',
|
||||
200: '#e7e5e4',
|
||||
300: '#d6d3d1',
|
||||
400: '#a8a29e',
|
||||
500: '#78716c',
|
||||
600: '#57534e',
|
||||
700: '#44403c',
|
||||
800: '#292524',
|
||||
900: '#1c1917'
|
||||
},
|
||||
red: {
|
||||
50: '#fef2f2',
|
||||
100: '#fee2e2',
|
||||
200: '#fecaca',
|
||||
300: '#fca5a5',
|
||||
400: '#f87171',
|
||||
500: '#ef4444',
|
||||
600: '#dc2626',
|
||||
700: '#b91c1c',
|
||||
800: '#991b1b',
|
||||
900: '#7f1d1d'
|
||||
},
|
||||
orange: {
|
||||
50: '#fff7ed',
|
||||
100: '#ffedd5',
|
||||
200: '#fed7aa',
|
||||
300: '#fdba74',
|
||||
400: '#fb923c',
|
||||
500: '#f97316',
|
||||
600: '#ea580c',
|
||||
700: '#c2410c',
|
||||
800: '#9a3412',
|
||||
900: '#7c2d12'
|
||||
},
|
||||
amber: {
|
||||
50: '#fffbeb',
|
||||
100: '#fef3c7',
|
||||
200: '#fde68a',
|
||||
300: '#fcd34d',
|
||||
400: '#fbbf24',
|
||||
500: '#f59e0b',
|
||||
600: '#d97706',
|
||||
700: '#b45309',
|
||||
800: '#92400e',
|
||||
900: '#78350f'
|
||||
},
|
||||
yellow: {
|
||||
50: '#fefce8',
|
||||
100: '#fef9c3',
|
||||
200: '#fef08a',
|
||||
300: '#fde047',
|
||||
400: '#facc15',
|
||||
500: '#eab308',
|
||||
600: '#ca8a04',
|
||||
700: '#a16207',
|
||||
800: '#854d0e',
|
||||
900: '#713f12'
|
||||
},
|
||||
lime: {
|
||||
50: '#f7fee7',
|
||||
100: '#ecfccb',
|
||||
200: '#d9f99d',
|
||||
300: '#bef264',
|
||||
400: '#a3e635',
|
||||
500: '#84cc16',
|
||||
600: '#65a30d',
|
||||
700: '#4d7c0f',
|
||||
800: '#3f6212',
|
||||
900: '#365314'
|
||||
},
|
||||
green: {
|
||||
50: '#f0fdf4',
|
||||
100: '#dcfce7',
|
||||
200: '#bbf7d0',
|
||||
300: '#86efac',
|
||||
400: '#4ade80',
|
||||
500: '#22c55e',
|
||||
600: '#16a34a',
|
||||
700: '#15803d',
|
||||
800: '#166534',
|
||||
900: '#14532d'
|
||||
},
|
||||
emerald: {
|
||||
50: '#ecfdf5',
|
||||
100: '#d1fae5',
|
||||
200: '#a7f3d0',
|
||||
300: '#6ee7b7',
|
||||
400: '#34d399',
|
||||
500: '#10b981',
|
||||
600: '#059669',
|
||||
700: '#047857',
|
||||
800: '#065f46',
|
||||
900: '#064e3b'
|
||||
},
|
||||
teal: {
|
||||
50: '#f0fdfa',
|
||||
100: '#ccfbf1',
|
||||
200: '#99f6e4',
|
||||
300: '#5eead4',
|
||||
400: '#2dd4bf',
|
||||
500: '#14b8a6',
|
||||
600: '#0d9488',
|
||||
700: '#0f766e',
|
||||
800: '#115e59',
|
||||
900: '#134e4a'
|
||||
},
|
||||
cyan: {
|
||||
50: '#ecfeff',
|
||||
100: '#cffafe',
|
||||
200: '#a5f3fc',
|
||||
300: '#67e8f9',
|
||||
400: '#22d3ee',
|
||||
500: '#06b6d4',
|
||||
600: '#0891b2',
|
||||
700: '#0e7490',
|
||||
800: '#155e75',
|
||||
900: '#164e63'
|
||||
},
|
||||
sky: {
|
||||
50: '#f0f9ff',
|
||||
100: '#e0f2fe',
|
||||
200: '#bae6fd',
|
||||
300: '#7dd3fc',
|
||||
400: '#38bdf8',
|
||||
500: '#0ea5e9',
|
||||
600: '#0284c7',
|
||||
700: '#0369a1',
|
||||
800: '#075985',
|
||||
900: '#0c4a6e'
|
||||
},
|
||||
blue: {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
200: '#bfdbfe',
|
||||
300: '#93c5fd',
|
||||
400: '#60a5fa',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
800: '#1e40af',
|
||||
900: '#1e3a8a'
|
||||
},
|
||||
indigo: {
|
||||
50: '#eef2ff',
|
||||
100: '#e0e7ff',
|
||||
200: '#c7d2fe',
|
||||
300: '#a5b4fc',
|
||||
400: '#818cf8',
|
||||
500: '#6366f1',
|
||||
600: '#4f46e5',
|
||||
700: '#4338ca',
|
||||
800: '#3730a3',
|
||||
900: '#312e81'
|
||||
},
|
||||
violet: {
|
||||
50: '#f5f3ff',
|
||||
100: '#ede9fe',
|
||||
200: '#ddd6fe',
|
||||
300: '#c4b5fd',
|
||||
400: '#a78bfa',
|
||||
500: '#8b5cf6',
|
||||
600: '#7c3aed',
|
||||
700: '#6d28d9',
|
||||
800: '#5b21b6',
|
||||
900: '#4c1d95'
|
||||
},
|
||||
purple: {
|
||||
50: '#faf5ff',
|
||||
100: '#f3e8ff',
|
||||
200: '#e9d5ff',
|
||||
300: '#d8b4fe',
|
||||
400: '#c084fc',
|
||||
500: '#a855f7',
|
||||
600: '#9333ea',
|
||||
700: '#7e22ce',
|
||||
800: '#6b21a8',
|
||||
900: '#581c87'
|
||||
},
|
||||
fuchsia: {
|
||||
50: '#fdf4ff',
|
||||
100: '#fae8ff',
|
||||
200: '#f5d0fe',
|
||||
300: '#f0abfc',
|
||||
400: '#e879f9',
|
||||
500: '#d946ef',
|
||||
600: '#c026d3',
|
||||
700: '#a21caf',
|
||||
800: '#86198f',
|
||||
900: '#701a75'
|
||||
},
|
||||
pink: {
|
||||
50: '#fdf2f8',
|
||||
100: '#fce7f3',
|
||||
200: '#fbcfe8',
|
||||
300: '#f9a8d4',
|
||||
400: '#f472b6',
|
||||
500: '#ec4899',
|
||||
600: '#db2777',
|
||||
700: '#be185d',
|
||||
800: '#9d174d',
|
||||
900: '#831843'
|
||||
},
|
||||
rose: {
|
||||
50: '#fff1f2',
|
||||
100: '#ffe4e6',
|
||||
200: '#fecdd3',
|
||||
300: '#fda4af',
|
||||
400: '#fb7185',
|
||||
500: '#f43f5e',
|
||||
600: '#e11d48',
|
||||
700: '#be123c',
|
||||
800: '#9f1239',
|
||||
900: '#881337'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = c;
|
||||
@@ -1,6 +1,6 @@
|
||||
.loader {
|
||||
height: auto;
|
||||
color: rgba(0, 0, 0, 0);
|
||||
color: rgba(0, 0, 0, 0) !important;
|
||||
}
|
||||
|
||||
.loader .dot {
|
||||
|
||||
241
email.go
241
email.go
@@ -10,19 +10,17 @@ import (
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
textTemplate "text/template"
|
||||
"time"
|
||||
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/itchyny/timefmt-go"
|
||||
jEmail "github.com/jordan-wright/email"
|
||||
"github.com/mailgun/mailgun-go/v4"
|
||||
sMail "github.com/xhit/go-simple-mail/v2"
|
||||
)
|
||||
|
||||
var renderer = html.NewRenderer(html.RendererOptions{Flags: html.Smartypants})
|
||||
@@ -32,70 +30,6 @@ type EmailClient interface {
|
||||
Send(fromName, fromAddr string, message *Message, address ...string) error
|
||||
}
|
||||
|
||||
type DummyClient struct{}
|
||||
|
||||
func (dc *DummyClient) Send(fromName, fromAddr string, email *Message, address ...string) error {
|
||||
fmt.Printf("FROM: %s <%s>\nTO: %s\nTEXT: %s\n", fromName, fromAddr, strings.Join(address, ", "), email.Text)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mailgun client implements EmailClient.
|
||||
type Mailgun struct {
|
||||
client *mailgun.MailgunImpl
|
||||
}
|
||||
|
||||
func (mg *Mailgun) Send(fromName, fromAddr string, email *Message, address ...string) error {
|
||||
message := mg.client.NewMessage(
|
||||
fmt.Sprintf("%s <%s>", fromName, fromAddr),
|
||||
email.Subject,
|
||||
email.Text,
|
||||
)
|
||||
for _, a := range address {
|
||||
// Adding variable tells mailgun to do a batch send, so users don't see other recipients.
|
||||
message.AddRecipientAndVariables(a, map[string]interface{}{"unique_id": a})
|
||||
}
|
||||
message.SetHtml(email.HTML)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
_, _, err := mg.client.Send(ctx, message)
|
||||
return err
|
||||
}
|
||||
|
||||
// SMTP supports SSL/TLS and STARTTLS; implements EmailClient.
|
||||
type SMTP struct {
|
||||
sslTLS bool
|
||||
server string
|
||||
port int
|
||||
auth smtp.Auth
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
func (sm *SMTP) Send(fromName, fromAddr string, email *Message, address ...string) error {
|
||||
server := fmt.Sprintf("%s:%d", sm.server, sm.port)
|
||||
from := fmt.Sprintf("%s <%s>", fromName, fromAddr)
|
||||
var wg sync.WaitGroup
|
||||
var err error
|
||||
for _, addr := range address {
|
||||
wg.Add(1)
|
||||
go func(addr string) {
|
||||
defer wg.Done()
|
||||
e := jEmail.NewEmail()
|
||||
e.Subject = email.Subject
|
||||
e.From = from
|
||||
e.Text = []byte(email.Text)
|
||||
e.HTML = []byte(email.HTML)
|
||||
e.To = []string{addr}
|
||||
if sm.sslTLS {
|
||||
err = e.SendWithTLS(server, sm.auth, sm.tlsConfig)
|
||||
} else {
|
||||
err = e.SendWithStartTLS(server, sm.auth, sm.tlsConfig)
|
||||
}
|
||||
}(addr)
|
||||
}
|
||||
wg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
// Emailer contains the email sender, translations, and methods to construct messages.
|
||||
type Emailer struct {
|
||||
fromAddr, fromName string
|
||||
@@ -145,13 +79,12 @@ func NewEmailer(app *appContext) *Emailer {
|
||||
if app.config.Section("smtp").Key("encryption").String() == "ssl_tls" {
|
||||
sslTLS = true
|
||||
}
|
||||
username := ""
|
||||
if u := app.config.Section("smtp").Key("username").MustString(""); u != "" {
|
||||
username = u
|
||||
} else {
|
||||
username := app.config.Section("smtp").Key("username").MustString("")
|
||||
password := app.config.Section("smtp").Key("password").String()
|
||||
if username == "" && password != "" {
|
||||
username = emailer.fromAddr
|
||||
}
|
||||
err := emailer.NewSMTP(app.config.Section("smtp").Key("server").String(), app.config.Section("smtp").Key("port").MustInt(465), username, app.config.Section("smtp").Key("password").String(), sslTLS, app.config.Section("smtp").Key("ssl_cert").MustString(""))
|
||||
err := emailer.NewSMTP(app.config.Section("smtp").Key("server").String(), app.config.Section("smtp").Key("port").MustInt(465), username, password, sslTLS, app.config.Section("smtp").Key("ssl_cert").MustString(""), app.config.Section("smtp").Key("hello_hostname").String(), app.config.Section("smtp").Key("cert_validation").MustBool(true))
|
||||
if err != nil {
|
||||
app.err.Printf("Error while initiating SMTP mailer: %v", err)
|
||||
}
|
||||
@@ -163,6 +96,93 @@ func NewEmailer(app *appContext) *Emailer {
|
||||
return emailer
|
||||
}
|
||||
|
||||
// DummyClient just logs the email to the console for debugging purposes. It can be used by settings [email]/method to "dummy".
|
||||
type DummyClient struct{}
|
||||
|
||||
func (dc *DummyClient) Send(fromName, fromAddr string, email *Message, address ...string) error {
|
||||
fmt.Printf("FROM: %s <%s>\nTO: %s\nTEXT: %s\n", fromName, fromAddr, strings.Join(address, ", "), email.Text)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SMTP supports SSL/TLS and STARTTLS; implements EmailClient.
|
||||
type SMTP struct {
|
||||
Client *sMail.SMTPServer
|
||||
}
|
||||
|
||||
// NewSMTP returns an SMTP emailClient.
|
||||
func (emailer *Emailer) NewSMTP(server string, port int, username, password string, sslTLS bool, certPath string, helloHostname string, validateCertificate bool) (err error) {
|
||||
sender := &SMTP{}
|
||||
sender.Client = sMail.NewSMTPClient()
|
||||
if sslTLS {
|
||||
sender.Client.Encryption = sMail.EncryptionSSLTLS
|
||||
} else {
|
||||
sender.Client.Encryption = sMail.EncryptionSTARTTLS
|
||||
}
|
||||
if username != "" || password != "" {
|
||||
sender.Client.Authentication = sMail.AuthLogin
|
||||
sender.Client.Username = username
|
||||
sender.Client.Password = password
|
||||
}
|
||||
sender.Client.Helo = helloHostname
|
||||
sender.Client.ConnectTimeout, sender.Client.SendTimeout = 15*time.Second, 15*time.Second
|
||||
sender.Client.Host = server
|
||||
sender.Client.Port = port
|
||||
sender.Client.KeepAlive = false
|
||||
// x509.SystemCertPool is unavailable on windows
|
||||
if PLATFORM == "windows" {
|
||||
sender.Client.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: !validateCertificate,
|
||||
ServerName: server,
|
||||
}
|
||||
emailer.sender = sender
|
||||
return
|
||||
}
|
||||
rootCAs, err := x509.SystemCertPool()
|
||||
if rootCAs == nil || err != nil {
|
||||
rootCAs = x509.NewCertPool()
|
||||
}
|
||||
if certPath != "" {
|
||||
var cert []byte
|
||||
cert, err = os.ReadFile(certPath)
|
||||
if rootCAs.AppendCertsFromPEM(cert) == false {
|
||||
err = errors.New("Failed to append cert to pool")
|
||||
}
|
||||
}
|
||||
sender.Client.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: !validateCertificate,
|
||||
ServerName: server,
|
||||
RootCAs: rootCAs,
|
||||
}
|
||||
emailer.sender = sender
|
||||
return
|
||||
}
|
||||
|
||||
func (sm *SMTP) Send(fromName, fromAddr string, email *Message, address ...string) error {
|
||||
from := fmt.Sprintf("%s <%s>", fromName, fromAddr)
|
||||
var cli *sMail.SMTPClient
|
||||
var err error
|
||||
cli, err = sm.Client.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
e := sMail.NewMSG()
|
||||
e.SetFrom(from)
|
||||
e.SetSubject(email.Subject)
|
||||
e.AddTo(address...)
|
||||
e.SetBody(sMail.TextPlain, email.Text)
|
||||
if email.HTML != "" {
|
||||
e.AddAlternative(sMail.TextHTML, email.HTML)
|
||||
}
|
||||
err = e.Send(cli)
|
||||
return err
|
||||
}
|
||||
|
||||
// Mailgun client implements EmailClient.
|
||||
type Mailgun struct {
|
||||
client *mailgun.MailgunImpl
|
||||
}
|
||||
|
||||
// NewMailgun returns a Mailgun emailClient.
|
||||
func (emailer *Emailer) NewMailgun(url, key string) {
|
||||
sender := &Mailgun{
|
||||
@@ -177,51 +197,21 @@ func (emailer *Emailer) NewMailgun(url, key string) {
|
||||
emailer.sender = sender
|
||||
}
|
||||
|
||||
// NewSMTP returns an SMTP emailClient.
|
||||
func (emailer *Emailer) NewSMTP(server string, port int, username, password string, sslTLS bool, certPath string) (err error) {
|
||||
// x509.SystemCertPool is unavailable on windows
|
||||
if PLATFORM == "windows" {
|
||||
sender := &SMTP{
|
||||
server: server,
|
||||
port: port,
|
||||
sslTLS: sslTLS,
|
||||
tlsConfig: &tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
ServerName: server,
|
||||
},
|
||||
}
|
||||
if username != "" || password != "" {
|
||||
sender.auth = smtp.PlainAuth("", username, password, server)
|
||||
}
|
||||
emailer.sender = sender
|
||||
return
|
||||
func (mg *Mailgun) Send(fromName, fromAddr string, email *Message, address ...string) error {
|
||||
message := mg.client.NewMessage(
|
||||
fmt.Sprintf("%s <%s>", fromName, fromAddr),
|
||||
email.Subject,
|
||||
email.Text,
|
||||
)
|
||||
for _, a := range address {
|
||||
// Adding variable tells mailgun to do a batch send, so users don't see other recipients.
|
||||
message.AddRecipientAndVariables(a, map[string]interface{}{"unique_id": a})
|
||||
}
|
||||
rootCAs, err := x509.SystemCertPool()
|
||||
if rootCAs == nil || err != nil {
|
||||
rootCAs = x509.NewCertPool()
|
||||
}
|
||||
if certPath != "" {
|
||||
var cert []byte
|
||||
cert, err = os.ReadFile(certPath)
|
||||
if rootCAs.AppendCertsFromPEM(cert) == false {
|
||||
err = errors.New("Failed to append cert to pool")
|
||||
}
|
||||
}
|
||||
sender := &SMTP{
|
||||
server: server,
|
||||
port: port,
|
||||
sslTLS: sslTLS,
|
||||
tlsConfig: &tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
ServerName: server,
|
||||
RootCAs: rootCAs,
|
||||
},
|
||||
}
|
||||
if username != "" || password != "" {
|
||||
sender.auth = smtp.PlainAuth("", username, password, server)
|
||||
}
|
||||
emailer.sender = sender
|
||||
return
|
||||
message.SetHtml(email.HTML)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
_, _, err := mg.client.Send(ctx, message)
|
||||
return err
|
||||
}
|
||||
|
||||
type templ interface {
|
||||
@@ -522,6 +512,18 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
|
||||
return email, nil
|
||||
}
|
||||
|
||||
// GenResetLink generates and returns a password reset link.
|
||||
func (app *appContext) GenResetLink(pin string) (string, error) {
|
||||
url := app.config.Section("password_resets").Key("url_base").String()
|
||||
var pinLink string
|
||||
if url == "" {
|
||||
return pinLink, fmt.Errorf("disabled as no URL Base provided. Set in Settings > Password Resets.")
|
||||
}
|
||||
// Strip /invite from end of this URL, ik it's ugly.
|
||||
pinLink = fmt.Sprintf("%s/reset?pin=%s", url, pin)
|
||||
return pinLink, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bool) map[string]interface{} {
|
||||
d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern)
|
||||
message := app.config.Section("messages").Key("message").String()
|
||||
@@ -552,17 +554,16 @@ func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bo
|
||||
} else {
|
||||
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": pwr.Username})
|
||||
template["codeExpiry"] = emailer.lang.PasswordReset.template("codeExpiry", tmpl{"date": d, "time": t, "expiresInMinutes": expiresIn})
|
||||
url := app.config.Section("password_resets").Key("url_base").String()
|
||||
if linkResetEnabled {
|
||||
if url != "" {
|
||||
pinLink, err := app.GenResetLink(pwr.Pin)
|
||||
if err == nil {
|
||||
// Strip /invite form end of this URL, ik its ugly.
|
||||
template["link_reset"] = true
|
||||
pinLink := fmt.Sprintf("%s/reset?pin=%s", url, pwr.Pin)
|
||||
template["pin"] = pinLink
|
||||
// Only used in html email.
|
||||
template["pin_code"] = pwr.Pin
|
||||
} else {
|
||||
app.info.Println("Password Reset link disabled as no URL Base provided. Set in Settings > Password Resets.")
|
||||
app.info.Println("Couldn't generate PWR link: %v", err)
|
||||
template["pin"] = pwr.Pin
|
||||
}
|
||||
} else {
|
||||
|
||||
61
go.mod
61
go.mod
@@ -14,61 +14,52 @@ replace github.com/hrfee/jfa-go/linecache => ./linecache
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/discordgo v0.23.2
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/fsnotify/fsnotify v1.5.1
|
||||
github.com/getlantern/golog v0.0.0-20210606115803-bce9f9fe5a5f // indirect
|
||||
github.com/getlantern/hidden v0.0.0-20201229170000-e66e7f878730 // indirect
|
||||
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 // indirect
|
||||
github.com/getlantern/systray v1.1.0
|
||||
github.com/gin-contrib/pprof v1.3.0
|
||||
github.com/gin-contrib/static v0.0.1
|
||||
github.com/gin-gonic/gin v1.7.4
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-playground/validator/v10 v10.9.0 // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7
|
||||
github.com/gomarkdown/markdown v0.0.0-20211212230626-5af6ad2f47df
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20210816220108-eb3489b34f9e
|
||||
github.com/hrfee/jfa-go/docs v0.0.0-20210816220108-eb3489b34f9e
|
||||
github.com/hrfee/jfa-go/linecache v0.0.0-20210816220108-eb3489b34f9e
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-20210816220108-eb3489b34f9e
|
||||
github.com/hrfee/jfa-go/ombi v0.0.0-20210816220108-eb3489b34f9e
|
||||
github.com/hrfee/mediabrowser v0.3.5
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20211222231100-d47afe05f49c
|
||||
github.com/hrfee/jfa-go/docs v0.0.0-20211222231100-d47afe05f49c
|
||||
github.com/hrfee/jfa-go/linecache v0.0.0-20211222231100-d47afe05f49c
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-20211222231100-d47afe05f49c
|
||||
github.com/hrfee/jfa-go/ombi v0.0.0-20211222231100-d47afe05f49c
|
||||
github.com/hrfee/mediabrowser v0.3.8
|
||||
github.com/itchyny/timefmt-go v0.1.3
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/lib/pq v1.10.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7
|
||||
github.com/mailgun/mailgun-go/v4 v4.5.2
|
||||
github.com/mailgun/mailgun-go/v4 v4.6.0
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.7 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2
|
||||
github.com/swaggo/gin-swagger v1.3.1
|
||||
github.com/swaggo/swag v1.7.1 // indirect
|
||||
github.com/swaggo/gin-swagger v1.3.3
|
||||
github.com/swaggo/swag v1.7.6 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/tidwall/gjson v1.8.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tidwall/sjson v1.1.7 // indirect
|
||||
github.com/tidwall/sjson v1.2.4 // indirect
|
||||
github.com/ugorji/go v1.2.6 // indirect
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e // indirect
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.5 // indirect
|
||||
github.com/xhit/go-simple-mail/v2 v2.10.0
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/tools v0.1.8 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
maunium.net/go/maulogger/v2 v2.3.0 // indirect
|
||||
maunium.net/go/mautrix v0.9.19
|
||||
gopkg.in/ini.v1 v1.66.2
|
||||
maunium.net/go/mautrix v0.10.7
|
||||
)
|
||||
|
||||
226
go.sum
226
go.sum
@@ -7,7 +7,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
@@ -28,7 +29,6 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:M88ob4TyDnEqNuL3PgsE/p3bDujfspnulR+0dQWNYZs=
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:buzQsO8HHkZX2Q45fdfGH1xejPjuDQaXH8btcYMFzPM=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||
@@ -37,30 +37,25 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||
github.com/getlantern/errors v1.0.1 h1:XukU2whlh7OdpxnkXhNH9VTLVz0EVPGKDV5K0oWhvzw=
|
||||
github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
|
||||
github.com/getlantern/golog v0.0.0-20210606115803-bce9f9fe5a5f h1:wsVt3P/boVKkPFEZkWxgNgRq/+mD7sWHc17+Vw2PgH8=
|
||||
github.com/getlantern/golog v0.0.0-20210606115803-bce9f9fe5a5f/go.mod h1:ZyIjgH/1wTCl+B+7yH1DqrWp6MPJqESmwmEQ89ZfhvA=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||
github.com/getlantern/hidden v0.0.0-20201229170000-e66e7f878730 h1:oKJVQbWZ2CAJ71jYnm6A3+e6h5bkPJ0okIMwkaYB5HI=
|
||||
github.com/getlantern/hidden v0.0.0-20201229170000-e66e7f878730/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 h1:QthAQCekS1YOeYWSvoHI6ZatlG4B+GBDLxV/2ZkBsTA=
|
||||
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
@@ -68,28 +63,24 @@ github.com/getlantern/systray v1.1.0 h1:U0wCEqseLi2ok1fE6b88gJklzriavPJixZysZPkZ
|
||||
github.com/getlantern/systray v1.1.0/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
|
||||
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
|
||||
github.com/gin-contrib/gzip v0.0.3 h1:etUaeesHhEORpZMp18zoOhepboiWnFtXrBZxszWUn4k=
|
||||
github.com/gin-contrib/gzip v0.0.3/go.mod h1:YxxswVZIqOvcHEQpsSn+QF5guQtO1dCfy0shBPy4jFc=
|
||||
github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0=
|
||||
github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0=
|
||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e h1:8bZpGwoPxkaivQPrAbWl+7zjjUcbFUnYp7yQcx2r2N0=
|
||||
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e/go.mod h1:VhW/Ch/3FhimwZb8Oj+qJmdMmoB8r7lmJ5auRjm50oQ=
|
||||
github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
|
||||
github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
|
||||
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
||||
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
|
||||
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
@@ -104,8 +95,9 @@ github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJ
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ=
|
||||
github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
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/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
@@ -114,51 +106,38 @@ github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyr
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
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=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.8.0 h1:1kAa0fCrnpv+QYdkdcRzrRM7AyYs5o8+jZdJCz9xj6k=
|
||||
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
|
||||
github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
|
||||
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd h1:0b8AqsWQb6A0jjx80UXLG/uMTXQkGD0IGuXWqsrNz1M=
|
||||
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
||||
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7 h1:oKYOfNR7Hp6XpZ4JqolL5u642Js5Z0n7psPVl+S5heo=
|
||||
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
||||
github.com/gomarkdown/markdown v0.0.0-20211212230626-5af6ad2f47df h1:M7mdNDTRraBcrHZg2aOYiFP9yTDajb6fquRZRpXnbVA=
|
||||
github.com/gomarkdown/markdown v0.0.0-20211212230626-5af6ad2f47df/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
@@ -166,106 +145,97 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hrfee/mediabrowser v0.3.5 h1:bOJlI2HLvw7v0c7mcRw5XDRMUHReQzk5z0EJYRyYjpo=
|
||||
github.com/hrfee/mediabrowser v0.3.5/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/itchyny/timefmt-go v0.1.2 h1:q0Xa4P5it6K6D7ISsbLAMwx1PnWlixDcJL6/sFs93Hs=
|
||||
github.com/itchyny/timefmt-go v0.1.2/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
||||
github.com/hrfee/mediabrowser v0.3.7 h1:F57Cmwst4fOfhPuOlanKiOuek9zCVcXm78/zP/1WB2s=
|
||||
github.com/hrfee/mediabrowser v0.3.7/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/hrfee/mediabrowser v0.3.8 h1:y0iBCb6jE3QKcsiCJSYva2fFPHRn4UA+sGRzoPuJ/Dk=
|
||||
github.com/hrfee/mediabrowser v0.3.8/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU=
|
||||
github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
|
||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4 h1:uj4xhotfY92Y1Oa6n6HUiFn87CdoEHYUlTy0+IgbLrs=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4/go.mod h1:RviRjexKqIzx/7r1peoAITm6m7gnif/h+0zmolKJjzw=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
|
||||
github.com/mailgun/mailgun-go/v4 v4.5.1 h1:XrQQ/ZgqFvINRKy+eBqowLl7k3pQO6OCLpKphliMOFs=
|
||||
github.com/mailgun/mailgun-go/v4 v4.5.1/go.mod h1:FJlF9rI5cQT+mrwujtJjPMbIVy3Ebor9bKTVsJ0QU40=
|
||||
github.com/mailgun/mailgun-go/v4 v4.5.2 h1:6289rnnSn8pKFR8TS5pwWOqFcY/HdWpqhvnQ+J7IVQc=
|
||||
github.com/mailgun/mailgun-go/v4 v4.5.2/go.mod h1:FJlF9rI5cQT+mrwujtJjPMbIVy3Ebor9bKTVsJ0QU40=
|
||||
github.com/mailgun/mailgun-go/v4 v4.6.0 h1:qSrgT3wP5fU7wF/tNUp4xeYe8wSUy+8V5NJPYnB6Hxo=
|
||||
github.com/mailgun/mailgun-go/v4 v4.6.0/go.mod h1:FJlF9rI5cQT+mrwujtJjPMbIVy3Ebor9bKTVsJ0QU40=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
|
||||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
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 h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
|
||||
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pkg/browser v0.0.0-20210606212950-a7b7a6107d32 h1:K3WnH8Ka32vWygzmjKEhz1zAVqckNoWDqX3azMxuiSA=
|
||||
github.com/pkg/browser v0.0.0-20210606212950-a7b7a6107d32/go.mod h1:yvwcBfzEX4m+eTgxPBbNYytaWFv4PSQzBaeYjxp8Iik=
|
||||
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc=
|
||||
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
@@ -275,47 +245,37 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM=
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
|
||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM=
|
||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
|
||||
github.com/swaggo/gin-swagger v1.3.0 h1:eOmp7r57oUgZPw2dJOjcGNMse9cvXcI4tTqBcnZtPsI=
|
||||
github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0m5SkWx+cS0=
|
||||
github.com/swaggo/gin-swagger v1.3.1 h1:mO9MU8O99WX+RM3jekzOV54g9Fo+Nbkk7rgrN1u9irM=
|
||||
github.com/swaggo/gin-swagger v1.3.1/go.mod h1:Z6NtRBK2PRig0EUmy1Xu75CnCEs6vGYu9QZd/QWRYKU=
|
||||
github.com/swaggo/gin-swagger v1.3.3 h1:XHyYmeNVFG5PbyWHG4jXtxOm2P4kiZapDCWsyDDiQ/I=
|
||||
github.com/swaggo/gin-swagger v1.3.3/go.mod h1:ymsZuGpbbu+S7ZoQ49QPpZoDBj6uqhb8WizgQPVgWl0=
|
||||
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
|
||||
github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc=
|
||||
github.com/swaggo/swag v1.7.1 h1:gY9ZakXlNWg/i/v5bQBic7VMZ4teq4m89lpiao74p/s=
|
||||
github.com/swaggo/swag v1.7.1/go.mod h1:gAiHxNTb9cIpNmA/VEGUP+CyZMCP/EW7mdtc8Bny+p8=
|
||||
github.com/swaggo/swag v1.7.4/go.mod h1:zD8h6h4SPv7t3l+4BKdRquqW1ASWjKZgT6Qv9z3kNqI=
|
||||
github.com/swaggo/swag v1.7.6 h1:UbAqHyXkW2J+cDjs5S43MkuYR7a6stB7Am7SK8NBmRg=
|
||||
github.com/swaggo/swag v1.7.6/go.mod h1:7vLqNYEtYoIsD14wXgy9oDS65MNiDANrPtbk9rnLuj0=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
|
||||
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
|
||||
github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||
github.com/tidwall/gjson v1.8.1 h1:8j5EE9Hrh3l9Od1OIEDAb7IpezNA20UdRngNAj5N0WU=
|
||||
github.com/tidwall/gjson v1.8.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
|
||||
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=
|
||||
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
|
||||
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
|
||||
github.com/tidwall/sjson v1.1.7 h1:sgVPwu/yygHJ2m1pJDLgGM/h+1F5odx5Q9ljG3imRm8=
|
||||
github.com/tidwall/sjson v1.1.7/go.mod h1:w/yG+ezBeTdUxiKs5NcPicO9diP38nk96QBAbIIGeFs=
|
||||
github.com/tidwall/sjson v1.2.3/go.mod h1:5WdjKx3AQMvCJ4RG6/2UYT7dLrGvJUV1x4jdTAyGvZs=
|
||||
github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc=
|
||||
github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.1.13/go.mod h1:jxau1n+/wyTGLQoCkjok9r5zFa/FxT6eI5HiHKQszjc=
|
||||
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
|
||||
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
|
||||
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU=
|
||||
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
|
||||
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
@@ -325,9 +285,10 @@ github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw=
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
|
||||
github.com/xhit/go-simple-mail/v2 v2.10.0 h1:nib6RaJ4qVh5HD9UE9QJqnUZyWp3upv+Z6CFxaMj0V8=
|
||||
github.com/xhit/go-simple-mail/v2 v2.10.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -335,14 +296,13 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ=
|
||||
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -355,15 +315,14 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff h1:j2EK/QoxYNBsXI4R7fQkkRUk8y6wnOBI+6hgPdP/6Ds=
|
||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -376,34 +335,27 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
@@ -414,10 +366,8 @@ golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
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=
|
||||
@@ -429,33 +379,27 @@ google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+Rur
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
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 h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
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=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
maunium.net/go/maulogger/v2 v2.2.4 h1:oV2GDeM4fx1uRysdpDC0FcrPg+thFicSd9XzPcYMbVY=
|
||||
maunium.net/go/maulogger/v2 v2.2.4/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
|
||||
maunium.net/go/maulogger/v2 v2.3.0 h1:TMCcO65fLk6+pJXo7sl38tzjzW0KBFgc6JWJMBJp4GE=
|
||||
maunium.net/go/maulogger/v2 v2.3.0/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
|
||||
maunium.net/go/mautrix v0.9.15 h1:9EJMALSxvy5LkQh5zjaMhbhm/grzVHv3xoA/2pfxpek=
|
||||
maunium.net/go/mautrix v0.9.15/go.mod h1:7IzKfWvpQtN+W2Lzxc0rLvIxFM3ryKX6Ys3S/ZoWbg8=
|
||||
maunium.net/go/mautrix v0.9.19 h1:8ZoDuijJOKxgEOMDoBN2B6at0Ba7EJpsqWA/5jV7ELw=
|
||||
maunium.net/go/mautrix v0.9.19/go.mod h1:7IzKfWvpQtN+W2Lzxc0rLvIxFM3ryKX6Ys3S/ZoWbg8=
|
||||
maunium.net/go/maulogger/v2 v2.3.1 h1:fwBYJne0pHvJrrIPHK+TAPfyxxbBEz46oVGez2x0ODE=
|
||||
maunium.net/go/maulogger/v2 v2.3.1/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
|
||||
maunium.net/go/mautrix v0.10.7 h1:QV5vbCY4g50N7r1ihdG6zEPfaPn/EVYjM5H+qfLy4RM=
|
||||
maunium.net/go/mautrix v0.10.7/go.mod h1:k4Ng5oci83MEbqPL6KOjPdbU7f8v01KlMjR/zTQ+7mA=
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
</head>
|
||||
<body class="section">
|
||||
<div class="page-container">
|
||||
<h1 class="heading">Page not found.</h1>
|
||||
<p class="content">
|
||||
{{ .contactMessage }}
|
||||
</p>
|
||||
<div class="card">
|
||||
<h1 class="heading">Page not found.</h1>
|
||||
<p class="content">
|
||||
{{ .contactMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
395
html/admin.html
395
html/admin.html
@@ -12,6 +12,7 @@
|
||||
window.ombiEnabled = {{ .ombiEnabled }};
|
||||
window.usernameEnabled = {{ .username }};
|
||||
window.langFile = JSON.parse({{ .language }});
|
||||
window.linkResetEnabled = {{ .linkResetEnabled }};
|
||||
window.language = "{{ .langName }}";
|
||||
</script>
|
||||
{{ template "header.html" . }}
|
||||
@@ -21,23 +22,23 @@
|
||||
<div id="modal-login" class="modal">
|
||||
<form class="modal-content card" id="form-login" href="">
|
||||
<span class="heading">{{ .strings.login }}</span>
|
||||
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="login-user">
|
||||
<input type="password" class="field input ~neutral !high mb-1" placeholder="{{ .strings.password }}" id="login-password">
|
||||
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="login-user">
|
||||
<input type="password" class="field input ~neutral @high mb-4" placeholder="{{ .strings.password }}" id="login-password">
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal full-width center supra submit">{{ .strings.login }}</span>
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.login }}</span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-add-user" class="modal">
|
||||
<form class="modal-content card" id="form-add-user" href="">
|
||||
<span class="heading">{{ .strings.newUser }} <span class="modal-close">×</span></span>
|
||||
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="add-user-user">
|
||||
<input type="email" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.emailAddress }}">
|
||||
<input type="password" class="field input ~neutral !high mb-1" placeholder="{{ .strings.password }}" id="add-user-password">
|
||||
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="add-user-user">
|
||||
<input type="email" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.emailAddress }}">
|
||||
<input type="password" class="field input ~neutral @high mb-4" placeholder="{{ .strings.password }}" id="add-user-password">
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal full-width center supra submit">{{ .strings.create }}</span>
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.create }}</span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
@@ -45,68 +46,73 @@
|
||||
<div class="modal-content content card">
|
||||
<img src="{{ .urlBase }}/banner.svg" class="banner header" alt="jfa-go banner">
|
||||
<span class="heading"><span class="modal-close">×</span></span>
|
||||
<p><i class="icon ri-github-fill"></i><a href="https://github.com/hrfee/jfa-go">jfa-go</a></p>
|
||||
<p>{{ .strings.version }} <span class="code monospace">{{ .version }}</span></p>
|
||||
<p>{{ .strings.commitNoun }} <span class="code monospace">{{ .commit }}</span></p>
|
||||
<div class="dropdown" tabindex="0">
|
||||
<span class="button ~info dropdown-button">
|
||||
<i class="ri-hand-heart-line mr-half"></i>
|
||||
{{ .strings.donate }}
|
||||
<span class="ml-1 chev"></span>
|
||||
</span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral !low">
|
||||
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button input ~neutral field mb-half lang-link">GitHub</a>
|
||||
<a href="https://ko-fi.com/hrfee" target="_blank" class="button input ~neutral field mb-half lang-link">Ko-fi</a>
|
||||
<p>{{ .strings.version }} <span class="code font-mono bg-inherit">{{ .version }}</span></p>
|
||||
<p>{{ .strings.commitNoun }} <span class="code font-mono bg-inherit">{{ .commit }}</span></p>
|
||||
<div class="row col flex">
|
||||
<a class="button ~neutral mr-2 mt-4 mb-4 lang-link" href="https://github.com/hrfee/jfa-go"><i class="ri-github-line mr-2"></i>github</a>
|
||||
<a class="button ~urge mt-4 mb-4 mr-2 lang-link" href="https://wiki.jfa-go.com">wiki/docs</a>
|
||||
<a class="button ~positive mt-4 mb-4 mr-2 lang-link" href="https://weblate.jfa-go.com">translation</a>
|
||||
<div class="dropdown mr-2" tabindex="0">
|
||||
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button ~info mt-4 mb-4 dropdown-button lang-link">
|
||||
<i class="ri-hand-heart-line mr-2"></i>
|
||||
donate
|
||||
<span class="ml-2 chev"></span>
|
||||
</a>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral @low">
|
||||
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button input ~neutral field mb-2 lang-link">GitHub</a>
|
||||
<a href="https://ko-fi.com/hrfee" target="_blank" class="button input ~neutral field mb-2 lang-link">Ko-fi</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="button ~urge mt-4 mb-4 @low discord lang-link" href="https://discord.com/invite/MrtvuQmyhP" target="_blank"><i class="ri-discord-line mr-2"></i>discord</a>
|
||||
</div>
|
||||
<p><a href="https://github.com/hrfee/jfa-go/blob/main/LICENSE">Available under the MIT License.</a></p>
|
||||
<pre class="monospace">{{ .license }}</pre>
|
||||
<pre class="font-mono bg-inherit">{{ .license }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-modify-user" class="modal">
|
||||
<form class="modal-content card" id="form-modify-user" href="">
|
||||
<span class="heading"><span id="header-modify-user"></span> <span class="modal-close">×</span></span>
|
||||
<p class="content">{{ .strings.modifySettingsDescription }}</p>
|
||||
<div class="flex-row mb-1">
|
||||
<label class="flex-row-group mr-1">
|
||||
<p class="content my-4">{{ .strings.modifySettingsDescription }}</p>
|
||||
<div class="flex-row mb-4">
|
||||
<label class="flex-row-group mr-2">
|
||||
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-profile" checked>
|
||||
<span class="button ~neutral !high supra full-width center">{{ .strings.profile }}</span>
|
||||
<span class="button ~neutral @high supra full-width center">{{ .strings.profile }}</span>
|
||||
</label>
|
||||
<label class="flex-row-group ml-1">
|
||||
<label class="flex-row-group ml-2">
|
||||
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-user">
|
||||
<span class="button ~neutral !normal supra full-width center">{{ .strings.user }}</span>
|
||||
<span class="button ~neutral @low supra full-width center">{{ .strings.user }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="select ~neutral !normal mb-1">
|
||||
<div class="select ~neutral @low mb-4">
|
||||
<select id="modify-user-profiles"></select>
|
||||
</div>
|
||||
<div class="select ~neutral !normal mb-1 unfocused">
|
||||
<div class="select ~neutral @low mb-4 unfocused">
|
||||
<select id="modify-user-users"></select>
|
||||
</div>
|
||||
<label class="switch mb-1">
|
||||
<label class="switch mb-4">
|
||||
<input type="checkbox" id="modify-user-homescreen" checked>
|
||||
<span>{{ .strings.applyHomescreenLayout }}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal full-width center supra submit">{{ .strings.apply }}</span>
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.apply }}</span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-delete-user" class="modal">
|
||||
<form class="modal-content card" id="form-delete-user" href="">
|
||||
<span class="heading"><span id="header-delete-user"></span> <span class="modal-close">×</span></span>
|
||||
<div class="content mt-half">
|
||||
<label class="switch mb-1">
|
||||
<div class="content mt-8">
|
||||
<label class="switch mb-4">
|
||||
<input type="checkbox" id="delete-user-notify" checked>
|
||||
<span>{{ .strings.sendDeleteNotificationEmail }}</span>
|
||||
</label>
|
||||
<textarea id="textarea-delete-user" class="textarea full-width ~neutral !normal mb-1" placeholder="{{ .strings.sendDeleteNotificationExample }}"></textarea>
|
||||
<textarea id="textarea-delete-user" class="textarea full-width ~neutral @low mb-4" placeholder="{{ .strings.sendDeleteNotificationExample }}"></textarea>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~critical !normal full-width center supra submit">{{ .strings.delete }}</span>
|
||||
<span class="button ~critical @low full-width center supra submit">{{ .strings.delete }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
@@ -114,11 +120,11 @@
|
||||
<div id="modal-extend-expiry" class="modal">
|
||||
<form class="modal-content card" id="form-extend-expiry" href="">
|
||||
<span class="heading"><span id="header-extend-expiry"></span> <span class="modal-close">×</span></span>
|
||||
<div class="content mt-half">
|
||||
<div class="content mt-8">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label supra" for="extend-expiry-months">{{ .strings.inviteMonths }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="extend-expiry-months">
|
||||
<option>0</option>
|
||||
</select>
|
||||
@@ -126,7 +132,7 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label supra" for="extend-expiry-days">{{ .strings.inviteDays }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="extend-expiry-days">
|
||||
<option>0</option>
|
||||
</select>
|
||||
@@ -136,7 +142,7 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label supra" for="extend-expiry-hours">{{ .strings.inviteHours }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="extend-expiry-hours">
|
||||
<option>0</option>
|
||||
</select>
|
||||
@@ -144,16 +150,21 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label supra" for="extend-expiry-minutes">{{ .strings.inviteMinutes }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="extend-expiry-minutes">
|
||||
<option>0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="switch mb-4">
|
||||
<input type="checkbox" id="expiry-extend-enable" checked>
|
||||
<span>{{ .strings.sendDeleteNotificationEmail }}</span>
|
||||
</label>
|
||||
<textarea id="textarea-extend-enable" class="textarea full-width ~neutral @low mb-4" placeholder="{{ .strings.sendDeleteNotificationExample }}"></textarea>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~critical !normal full-width center supra submit">{{ .strings.submit }}</span>
|
||||
<span class="button ~critical @low full-width center supra submit">{{ .strings.submit }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
@@ -162,33 +173,33 @@
|
||||
<form class="modal-content wide card" id="form-announce" href="">
|
||||
<span class="heading"><span id="header-announce"></span> <span class="modal-close">×</span></span>
|
||||
<div class="row">
|
||||
<div class="col flex-col content mt-half">
|
||||
<div class="col card ~neutral @low">
|
||||
<div id="announce-details">
|
||||
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
|
||||
<div id="announce-variables">
|
||||
<span class="button ~urge !normal mb-1 mt-half" id="announce-variables-username" style="margin-left: 0.25rem; margin-right: 0.25rem;"><span class="monospace">{username}</span></span>
|
||||
<span class="button ~urge @low mb-2 mt-4" id="announce-variables-username" style="margin-left: 0.25rem; margin-right: 0.25rem;"><span class="font-mono bg-inherit">{username}</span></span>
|
||||
</div>
|
||||
<label class="label supra" for="announce-subject"> {{ .strings.subject }}</label>
|
||||
<input type="text" id="announce-subject" class="input ~neutral !normal mb-1 mt-half">
|
||||
<input type="text" id="announce-subject" class="input ~neutral @low mb-2 mt-4">
|
||||
<label class="label supra" for="textarea-announce">{{ .strings.message }}</label>
|
||||
<textarea id="textarea-announce" class="textarea full-width ~neutral !normal mt-half monospace"></textarea>
|
||||
<p class="support mt-half mb-1">{{ .strings.markdownSupported }}</p>
|
||||
<textarea id="textarea-announce" class="textarea full-width ~neutral @low mt-4 font-mono"></textarea>
|
||||
<p class="support mt-4 mb-2">{{ .strings.markdownSupported }}</p>
|
||||
</div>
|
||||
<label class="label unfocused" id="announce-name"><p class="supra">{{ .strings.name }}</p>
|
||||
<input type="text" class="input ~neutral !normal mb-1 mt-half">
|
||||
<input type="text" class="input ~neutral @low mb-2 mt-4">
|
||||
<p class="support">{{ .strings.templateEnterName }}</p>
|
||||
</label>
|
||||
<div class="row flex-expand">
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal center supra submit">{{ .strings.send }}</span>
|
||||
<span class="button ~urge @low center supra submit">{{ .strings.send }}</span>
|
||||
</label>
|
||||
<span class="button ~info !normal center supra" id="save-announce">{{ .strings.saveAsTemplate }}</span>
|
||||
<span class="button ~info @low center supra" id="save-announce">{{ .strings.saveAsTemplate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col card ~neutral !low">
|
||||
<div class="col card ~neutral @low">
|
||||
<span class="subheading supra">{{ .strings.preview }}</span>
|
||||
<div class="mt-half" id="announce-preview"></div>
|
||||
<div class="mt-8" id="announce-preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -196,13 +207,13 @@
|
||||
<div id="modal-customize" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading">{{ .strings.customizeMessages }} <span class="modal-close">×</span></span>
|
||||
<p class="content">{{ .strings.customizeMessagesDescription }}</p>
|
||||
<p class="content my-4">{{ .strings.customizeMessagesDescription }}</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ .strings.name }}</th>
|
||||
<th>{{ .strings.reset }}</th>
|
||||
<th class="table-inline justify-center">{{ .strings.reset }}</th>
|
||||
<th>{{ .strings.edit }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -215,70 +226,80 @@
|
||||
<form class="modal-content wide card" id="form-editor" href="">
|
||||
<span class="heading"><span id="header-editor"></span> <span class="modal-close">×</span></span>
|
||||
<div class="row">
|
||||
<div class="col flex-col content mt-half">
|
||||
<div class="col card ~neutral @low">
|
||||
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
|
||||
<div id="editor-variables"></div>
|
||||
<div id="editor-variables" class="mt-4"></div>
|
||||
<span class="label supra" for="editor-conditionals" id="label-editor-conditionals">{{ .strings.conditionals }}</span>
|
||||
<div id="editor-conditionals"></div>
|
||||
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
|
||||
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral !normal mt-half monospace"></textarea>
|
||||
<p class="support mt-half mb-1">{{ .strings.markdownSupported }}</p>
|
||||
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral @low mt-4 font-mono"></textarea>
|
||||
<p class="support mt-4 mb-2">{{ .strings.markdownSupported }}</p>
|
||||
<div class="flex-row">
|
||||
<label class="full-width ml-half">
|
||||
<label class="full-width ml-2">
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal full-width center supra submit">{{ .strings.submit }}</span>
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col card ~neutral !low">
|
||||
<div class="col card ~neutral @low">
|
||||
<span class="subheading supra">{{ .strings.preview }}</span>
|
||||
<div class="mt-half" id="editor-preview"></div>
|
||||
<div class="mt-8" id="editor-preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-restart" class="modal">
|
||||
<div class="modal-content card ~critical !low">
|
||||
<div class="modal-content card ~critical @low">
|
||||
<span class="heading">{{ .strings.settingsRestartRequired }} <span class="modal-close">×</span></span>
|
||||
<p class="content pb-1">{{ .strings.settingsRestartRequiredDescription }}</p>
|
||||
<div class="fr">
|
||||
<span class="button ~info !normal mb-half" id="settings-apply-no-restart">{{ .strings.settingsApplyRestartLater }}</span>
|
||||
<span class="button ~critical !normal" id="settings-apply-restart">{{ .strings.settingsApplyRestartNow }}</span>
|
||||
<p class="content my-4">{{ .strings.settingsRestartRequiredDescription }}</p>
|
||||
<div class="float-right">
|
||||
<span class="button ~info @low mb-2" id="settings-apply-no-restart">{{ .strings.settingsApplyRestartLater }}</span>
|
||||
<span class="button ~critical @low" id="settings-apply-restart">{{ .strings.settingsApplyRestartNow }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-refresh" class="modal">
|
||||
<div class="modal-content card ~neutral !normal">
|
||||
<div class="modal-content card ~neutral @low">
|
||||
<span class="heading">{{ .strings.settingsApplied }}</span>
|
||||
<p class="content">{{ .strings.settingsRefreshPage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-ombi-defaults" class="modal">
|
||||
<div id="modal-send-pwr" class="modal">
|
||||
<div class="modal-content card ~neutral @low">
|
||||
<span class="heading">{{ .strings.sendPWR }}</span>
|
||||
<p class="content my-2" id="send-pwr-note"></p>
|
||||
<span class="button ~urge @low mt-2" id="send-pwr-link">{{ .strings.copy }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-ombi-profile" class="modal">
|
||||
<form class="modal-content card" id="form-ombi-defaults" href="">
|
||||
<span class="heading">{{ .strings.ombiUserDefaults }} <span class="modal-close">×</span></span>
|
||||
<p class="content">{{ .strings.ombiUserDefaultsDescription }}</p>
|
||||
<div class="select ~neutral !normal mb-1">
|
||||
<span class="heading">{{ .strings.ombiProfile }} <span class="modal-close">×</span></span>
|
||||
<p class="content my-4">{{ .strings.ombiUserDefaultsDescription }}</p>
|
||||
<div class="select ~neutral @low mb-4">
|
||||
<select></select>
|
||||
</div>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal full-width center supra submit">{{ .strings.submit }}</span>
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
<div id="modal-user-profiles" class="modal">
|
||||
<div class="modal-content wide card">
|
||||
<span class="heading">{{ .strings.userProfiles }} <span class="modal-close">×</span></span>
|
||||
<p class="support lg">{{ .strings.userProfilesDescription }}</p>
|
||||
<p class="content my-4">{{ .strings.userProfilesDescription }}</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ .strings.name }}</th>
|
||||
<th>{{ .strings.userProfilesIsDefault }}</th>
|
||||
{{ if .ombiEnabled }}
|
||||
<th>Ombi</th>
|
||||
{{ end }}
|
||||
<th>{{ .strings.from }}</th>
|
||||
<th>{{ .strings.userProfilesLibraries }}</th>
|
||||
<th><span class="button ~neutral !high" id="button-profile-create">{{ .strings.create }}</span></th>
|
||||
<th><span class="button ~neutral @high" id="button-profile-create">{{ .strings.create }}</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table-profiles"></tbody>
|
||||
@@ -289,23 +310,23 @@
|
||||
<div id="modal-add-profile" class="modal">
|
||||
<form class="modal-content card" id="form-add-profile" href="">
|
||||
<span class="heading">{{ .strings.addProfile }} <span class="modal-close">×</span></span>
|
||||
<p class="content">{{ .strings.addProfileDescription }}</p>
|
||||
<p class="content my-4">{{ .strings.addProfileDescription }}</p>
|
||||
<label>
|
||||
<span class="supra">{{ .strings.addProfileNameOf }} </span>
|
||||
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.name }}" id="add-profile-name">
|
||||
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.name }}" id="add-profile-name">
|
||||
<label>
|
||||
<span class="supra">{{ .strings.user }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<div class="select ~neutral @low mt-4 mb-2">
|
||||
<select id="add-profile-user"></select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="switch mb-1">
|
||||
<label class="switch mb-4">
|
||||
<input type="checkbox" id="add-profile-homescreen" checked>
|
||||
<span>{{ .strings.addProfileStoreHomescreenLayout }}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal full-width center supra submit">{{ .strings.create }}</span>
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.create }}</span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
@@ -314,41 +335,41 @@
|
||||
<span class="heading">{{ .strings.updates }} <span class="modal-close">×</span></span>
|
||||
<p class="content">
|
||||
<h2>
|
||||
<a id="update-version"></a> (<span class="monospace" id="update-commit"></span>)
|
||||
<a id="update-version"></a> (<span class="font-mono bg-inherit" id="update-commit"></span>)
|
||||
</h2>
|
||||
<p class="content" id="update-description"></p>
|
||||
<p class="support" id="update-date"></p>
|
||||
<div class="content markdown-box" id="update-changelog"></div>
|
||||
</p>
|
||||
<span class="button ~info !normal full-width center" id="update-download">{{ .strings.download }}</span>
|
||||
<span class="button ~urge !normal full-width center" id="update-update">{{ .strings.update }}</span>
|
||||
<span class="button ~info @low full-width center" id="update-download">{{ .strings.download }}</span>
|
||||
<span class="button ~urge @low full-width center" id="update-update">{{ .strings.update }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ if .telegramEnabled }}
|
||||
<div id="modal-telegram" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading mb-1">{{ .strings.linkTelegram }}</span>
|
||||
<p class="content mb-1">{{ .strings.sendPIN }}</p>
|
||||
<span class="heading mb-4">{{ .strings.linkTelegram }}</span>
|
||||
<p class="content mb-4">{{ .strings.sendPIN }}</p>
|
||||
<h1 class="ac" id="telegram-pin"></h1>
|
||||
<a class="subheading link-center" id="telegram-link" target="_blank">
|
||||
<span class="shield ~info mr-1">
|
||||
<span class="shield ~info mr-2">
|
||||
<span class="icon">
|
||||
<i class="ri-telegram-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
@<span id="telegram-username">
|
||||
</a>
|
||||
<span class="button ~info !normal full-width center mt-1" id="telegram-waiting">{{ .strings.success }}</span>
|
||||
<span class="button ~info @low full-width center mt-4" id="telegram-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .discordEnabled }}
|
||||
<div id="modal-discord" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading mb-1"><span id="discord-header"></span><span class="modal-close">×</span></span>
|
||||
<p class="content mb-1" id="discord-description"></p>
|
||||
<span class="heading mb-4"><span id="discord-header"></span><span class="modal-close">×</span></span>
|
||||
<p class="content mb-4" id="discord-description"></p>
|
||||
<div class="row">
|
||||
<input type="search" class="col sm field ~neutral !normal input" id="discord-search" placeholder="user#1234">
|
||||
<input type="search" class="col sm field ~neutral @low input" id="discord-search" placeholder="user#1234">
|
||||
</div>
|
||||
<table class="table"><tbody id="discord-list"></tbody></table>
|
||||
</div>
|
||||
@@ -357,76 +378,78 @@
|
||||
<div id="modal-matrix" class="modal">
|
||||
<form class="modal-content card" id="form-matrix" href="">
|
||||
<span class="heading">{{ .strings.linkMatrix }}</span>
|
||||
<p class="content">{{ .strings.linkMatrixDescription }}</p>
|
||||
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.matrixHomeServer }}" id="matrix-homeserver">
|
||||
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="matrix-user">
|
||||
<input type="password" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="matrix-password">
|
||||
<p class="content my-4">{{ .strings.linkMatrixDescription }}</p>
|
||||
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.matrixHomeServer }}" id="matrix-homeserver">
|
||||
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="matrix-user">
|
||||
<input type="password" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.password }}" id="matrix-password">
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal full-width center supra submit">{{ .strings.submit }}</span>
|
||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span>
|
||||
</label>
|
||||
</form>
|
||||
</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">
|
||||
<label class="switch pb-1">
|
||||
<input type="radio" name="lang-time" id="lang-12h">
|
||||
<span>{{ .strings.time12h }}</span>
|
||||
</label>
|
||||
<label class="switch pb-1">
|
||||
<input type="radio" name="lang-time" id="lang-24h">
|
||||
<span>{{ .strings.time24h }}</span>
|
||||
</label>
|
||||
<div id="lang-list"></div>
|
||||
<div class="top-4 left-4 absolute">
|
||||
<span class="dropdown" tabindex="0" id="lang-dropdown">
|
||||
<span class="button ~urge dropdown-button">
|
||||
<i class="ri-global-line"></i>
|
||||
<span class="ml-2 chev"></span>
|
||||
</span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral @low">
|
||||
<label class="switch pb-4">
|
||||
<input type="radio" name="lang-time" id="lang-12h">
|
||||
<span>{{ .strings.time12h }}</span>
|
||||
</label>
|
||||
<label class="switch pb-4">
|
||||
<input type="radio" name="lang-time" id="lang-24h">
|
||||
<span>{{ .strings.time24h }}</span>
|
||||
</label>
|
||||
<div id="lang-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
|
||||
</div>
|
||||
<div class="page-container">
|
||||
<div class="mb-1">
|
||||
<div class="mb-4">
|
||||
<header class="flex flex-wrap items-center justify-between">
|
||||
<div class="text-neutral-700">
|
||||
<span id="button-tab-invites" class="tab-button portal">{{ .strings.invites }}</span>
|
||||
<span id="button-tab-accounts" class="tab-button portal">{{ .strings.accounts }}</span>
|
||||
<span id="button-tab-settings" class="tab-button portal">{{ .strings.settings }}</span>
|
||||
<div>
|
||||
<span id="button-tab-invites" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 px-5">{{ .strings.invites }}</span>
|
||||
<span id="button-tab-accounts" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 px-5">{{ .strings.accounts }}</span>
|
||||
<span id="button-tab-settings" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 px-5">{{ .strings.settings }}</span>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<div class="text-neutral-700">
|
||||
<span class="button ~critical !normal mb-1 unfocused" id="logout-button">{{ .strings.logout }}</span>
|
||||
<span id="button-theme" class="button ~neutral !normal mb-1">{{ .strings.theme }}</span>
|
||||
<div class="mb-4">
|
||||
<div>
|
||||
<span class="button ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-invites">
|
||||
<div class="card ~neutral !low invites mb-1">
|
||||
<div class="card @low invites dark:~d_neutral mb-4">
|
||||
<span class="heading">{{ .strings.invites }}</span>
|
||||
<div id="invites"></div>
|
||||
</div>
|
||||
<div class="card ~neutral !low">
|
||||
<div class="card @low dark:~d_neutral">
|
||||
<span class="heading">{{ .strings.create }}</span>
|
||||
<div class="row" id="create-inv">
|
||||
<div class="card ~neutral !normal col">
|
||||
<div class="row mb-1">
|
||||
<label class="col mr-1">
|
||||
<div class="card ~neutral @low col">
|
||||
<div class="row mb-2">
|
||||
<label class="col mr-2">
|
||||
<input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked>
|
||||
<span class="button ~neutral !high supra full-width center">{{ .strings.inviteDuration }}</span>
|
||||
<span class="button ~neutral @high supra full-width center">{{ .strings.inviteDuration }}</span>
|
||||
</label>
|
||||
<label class="col ml-1">
|
||||
<label class="col ml-2">
|
||||
<input type="radio" name="duration" class="unfocused" id="radio-user-expiry">
|
||||
<span class="button ~neutral !normal supra full-width center">{{ .strings.userExpiry }}</span>
|
||||
<span class="button ~neutral @low supra full-width center">{{ .strings.userExpiry }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="inv-duration">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label supra" for="create-months">{{ .strings.inviteMonths }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="create-months">
|
||||
<option>0</option>
|
||||
</select>
|
||||
@@ -434,7 +457,7 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label supra" for="create-days">{{ .strings.inviteDays }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="create-days">
|
||||
<option>0</option>
|
||||
</select>
|
||||
@@ -444,7 +467,7 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label supra" for="create-hours">{{ .strings.inviteHours }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="create-hours">
|
||||
<option>0</option>
|
||||
</select>
|
||||
@@ -452,7 +475,7 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label supra" for="create-minutes">{{ .strings.inviteMinutes }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="create-minutes">
|
||||
<option>0</option>
|
||||
</select>
|
||||
@@ -461,17 +484,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="user-expiry" class="unfocused">
|
||||
<p class="support">{{ .strings.userExpiryDescription }}</p>
|
||||
<div class="mb-half">
|
||||
<label for="create-user-expiry-enabled" class="button ~neutral !normal">
|
||||
<p class="support mb-2">{{ .strings.userExpiryDescription }}</p>
|
||||
<div class="mb-2">
|
||||
<label for="create-user-expiry-enabled" class="button ~neutral @low">
|
||||
<input type="checkbox" id="create-user-expiry-enabled" aria-label="User duration enabled">
|
||||
<span class="ml-half">{{ .strings.enabled }} </span>
|
||||
<span class="ml-2">{{ .strings.enabled }} </span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label supra" for="user-months">{{ .strings.inviteMonths }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="user-months">
|
||||
<option>0</option>
|
||||
</select>
|
||||
@@ -479,7 +502,7 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label supra" for="user-days">{{ .strings.inviteDays }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="user-days">
|
||||
<option>0</option>
|
||||
</select>
|
||||
@@ -489,7 +512,7 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label supra" for="user-hours">{{ .strings.inviteHours }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="user-hours">
|
||||
<option>0</option>
|
||||
</select>
|
||||
@@ -497,7 +520,7 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label supra" for="user-minutes">{{ .strings.inviteMinutes }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="user-minutes">
|
||||
<option>0</option>
|
||||
</select>
|
||||
@@ -507,84 +530,92 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label supra" for="create-label"> {{ .strings.label }}</label>
|
||||
<input type="text" id="create-label" class="input ~neutral !normal mb-1 mt-half">
|
||||
<input type="text" id="create-label" class="input ~neutral @low mb-2 mt-4">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card ~neutral !normal col">
|
||||
<div class="card ~neutral @low col">
|
||||
<label class="label supra" for="create-uses">{{ .strings.inviteNumberOfUses }}</label>
|
||||
<div class="flex-expand mb-1 mt-half">
|
||||
<input type="number" min="0" id="create-uses" class="input ~neutral !normal mr-1" value=1>
|
||||
<label for="create-inf-uses" class="button ~neutral !normal" title="Set uses to infinite">
|
||||
<span>∞</span>
|
||||
<div class="flex-expand mb-2 mt-4">
|
||||
<input type="number" min="0" id="create-uses" class="input ~neutral @low mr-2" value=1>
|
||||
<label for="create-inf-uses" class="button ~neutral @low" title="Set uses to infinite">
|
||||
<span>∞</span>
|
||||
<input type="checkbox" class="unfocused" id="create-inf-uses" aria-label="Set uses to infinite">
|
||||
</label>
|
||||
</div>
|
||||
<p class="support unfocused" id="create-inf-uses-warning"><span class="badge ~critical">{{ .strings.warning }}</span> {{ .strings.inviteInfiniteUsesWarning }}</p>
|
||||
<p class="support unfocused my-2" id="create-inf-uses-warning"><span class="badge ~critical">{{ .strings.warning }}</span> {{ .strings.inviteInfiniteUsesWarning }}</p>
|
||||
<label class="label supra">{{ .strings.profile }}</label>
|
||||
<div class="select ~neutral !normal mb-1 mt-half">
|
||||
<div class="select ~neutral @low mb-2 mt-4">
|
||||
<select id="create-profile">
|
||||
</select>
|
||||
</div>
|
||||
<div id="create-send-to-container">
|
||||
<label class="label supra">{{ .strings.inviteSendToEmail }}</label>
|
||||
<div class="flex-expand mb-1 mt-half">
|
||||
<div class="flex-expand mb-2 mt-4">
|
||||
{{ if .discordEnabled }}
|
||||
<input type="text" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com | user#1234">
|
||||
<span id="create-send-to-search" class="button ~neutral !normal mr-1">
|
||||
<input type="text" id="create-send-to" class="input ~neutral @low mr-2" placeholder="example@example.com | user#1234">
|
||||
<span id="create-send-to-search" class="button ~neutral @low mr-2">
|
||||
<i class="icon ri-search-2-line" title="{{ .strings.search }}"></i>
|
||||
</span>
|
||||
{{ else }}
|
||||
<input type="email" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com">
|
||||
<input type="email" id="create-send-to" class="input ~neutral @low mr-2" placeholder="example@example.com">
|
||||
{{ end }}
|
||||
<label for="create-send-to-enabled" class="button ~neutral !normal">
|
||||
<label for="create-send-to-enabled" class="button ~neutral @low">
|
||||
<input type="checkbox" id="create-send-to-enabled" aria-label="Send to address enabled">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<span class="button ~urge !normal supra full-width center lg" id="create-submit">{{ .strings.create }}</span>
|
||||
<span class="button ~urge @low supra full-width center lg" id="create-submit">{{ .strings.create }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-accounts" class="unfocused">
|
||||
<div class="card ~neutral !low accounts mb-1">
|
||||
<div class="card @low dark:~d_neutral accounts mb-4">
|
||||
<div class="flex-expand row">
|
||||
<div class="row">
|
||||
<span class="heading mr-1 col sm">{{ .strings.accounts }}</span>
|
||||
<input type="search" class="col sm field ~neutral !normal input search ml-1 mr-1" id="accounts-search" placeholder="{{ .strings.search }}">
|
||||
<span class="text-3xl font-bold mr-2 col">{{ .strings.accounts }}</span>
|
||||
<input type="search" class="col sm field ~neutral @low input search ml-2 mr-2" id="accounts-search" placeholder="{{ .strings.search }}">
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="col sm button ~neutral !normal center mb-half" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
|
||||
<div id="accounts-announce-dropdown" class="col sm dropdown" tabindex="0">
|
||||
<span class="h-100 sm button ~info !normal center mb-half" id="accounts-announce">{{ .strings.announce }}</span>
|
||||
<span class="col sm button ~neutral @low center mb-2" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
|
||||
<div id="accounts-announce-dropdown" class="col sm dropdown pb-0i" tabindex="0">
|
||||
<span class="h-100 sm button ~info @low center mb-2" id="accounts-announce">{{ .strings.announce }}</span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral !low">
|
||||
<div class="card ~neutral @low">
|
||||
<span class="supra sm">{{ .strings.templates }}</span>
|
||||
<div id="accounts-announce-templates"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="col sm button ~urge !normal center mb-half" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
|
||||
<span class="col sm button ~warning !normal center mb-half" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
|
||||
<span class="col sm button ~positive !normal center mb-half" id="accounts-disable-enable">{{ .strings.disable }}</span>
|
||||
<span class="col sm button ~critical !normal center mb-half" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
|
||||
<span class="col sm button ~urge @low center mb-2" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
|
||||
<span class="col sm button ~warning @low center mb-2" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
|
||||
<div id="accounts-disable-enable-dropdown" class="col sm dropdown manual pb-0i" tabindex="0">
|
||||
<span class="h-100 sm button ~positive @low center mb-2" id="accounts-disable-enable">{{ .strings.disable }}</span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral @low">
|
||||
<span class="button ~urge sm full-width accounts-announce-template-button" id="accounts-enable-expiry">{{ .strings.setExpiry }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="col sm button ~info @low center mb-2 unfocused" id="accounts-send-pwr">{{ .strings.sendPWR }}</span>
|
||||
<span class="col sm button ~critical @low center mb-2" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card ~neutral !normal accounts-header table-responsive mt-half">
|
||||
<table class="table">
|
||||
<div class="card @low accounts-header table-responsive mt-8">
|
||||
<table class="table text-base leading-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" value="" id="accounts-select-all"></th>
|
||||
<th>{{ .strings.username }}</th>
|
||||
<th class="table-inline my-2">{{ .strings.username }}</th>
|
||||
<th>{{ .strings.emailAddress }}</th>
|
||||
{{ if .telegramEnabled }}
|
||||
<th>Telegram</th>
|
||||
<th class="text-center-i">Telegram</th>
|
||||
{{ end }}
|
||||
{{ if .matrixEnabled }}
|
||||
<th>Matrix</th>
|
||||
<th class="text-center-i">Matrix</th>
|
||||
{{ end }}
|
||||
{{ if .discordEnabled }}
|
||||
<th>Discord</th>
|
||||
<th class="text-center-i">Discord</th>
|
||||
{{ end }}
|
||||
<th>{{ .strings.expiry }}</th>
|
||||
<th>{{ .strings.lastActiveTime }}</th>
|
||||
@@ -596,27 +627,27 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-settings" class="unfocused">
|
||||
<div class="card ~neutral !low settings overflow">
|
||||
<div class="card @low dark:~d_neutral settings overflow">
|
||||
<div class="flex-expand">
|
||||
<div class="flex-row">
|
||||
<span class="heading">{{ .strings.settings }}</span>
|
||||
<label for="settings-advanced-enabled" class="button ~neutral !normal ml-1">
|
||||
<label for="settings-advanced-enabled" class="button ~neutral @low ml-2">
|
||||
<input type="checkbox" id="settings-advanced-enabled" aria-label="Advanced settings enabled">
|
||||
<span class="ml-half">{{ .strings.advancedSettings }} </span>
|
||||
<span class="ml-2">{{ .strings.advancedSettings }} </span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<span class="button ~neutral !normal" id="settings-restart">{{ .strings.settingsRestart }}</span>
|
||||
<span class="button ~urge !normal unfocused" id="settings-save">{{ .strings.settingsSave }}</span>
|
||||
<span class="button ~neutral @low" id="settings-restart">{{ .strings.settingsRestart }}</span>
|
||||
<span class="button ~urge @low unfocused" id="settings-save">{{ .strings.settingsSave }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card ~neutral !normal col" id="settings-sidebar">
|
||||
<aside class="aside sm ~info mb-half" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info">R</span> indicates changes require a restart.</aside>
|
||||
<span class="button ~neutral !low settings-section-button mb-half" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-half"></i></span></span>
|
||||
<span class="button ~neutral !low settings-section-button mb-half" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-half"></i></span></span>
|
||||
<div class="card @low dark:~d_neutral col" id="settings-sidebar">
|
||||
<aside class="aside sm ~urge dark:~d_info mb-2 @low" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info dark:~d_warning">R</span> indicates changes require a restart.</aside>
|
||||
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span>
|
||||
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span>
|
||||
</div>
|
||||
<div class="card ~neutral !normal col overflow" id="settings-panel"></div>
|
||||
<div class="card ~neutral @low col overflow" id="settings-panel"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,41 +7,37 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card ~critical sectioned">
|
||||
<section class="section ~critical">
|
||||
<span class="heading">Crash report for jfa-go</span>
|
||||
{{ if .Err }}
|
||||
<div class="monospace pre-line mt-1 mb-1">
|
||||
Error: {{ .Err }}
|
||||
</div>
|
||||
{{ end }}
|
||||
<a class="button ~critical mb-1" target="_blank" href="https://github.com/hrfee/jfa-go/issues/new/choose">Create an Issue</a>
|
||||
</section>
|
||||
<section class="section ~neutral !low">
|
||||
<div class="flex-expand">
|
||||
<span class="subheading">Full Log</span>
|
||||
<span class="button ~urge ml-half" id="copy-log">Copy</span>
|
||||
</div>
|
||||
<div class="row mb-1">
|
||||
<label class="col mr-1">
|
||||
<span class="button ~neutral !high supra full-width center" id="button-log-normal">Normal</span>
|
||||
</label>
|
||||
<label class="col mr-1">
|
||||
<span class="button ~neutral !normal supra full-width center" id="button-log-sanitized">Sanitized</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="log-normal">
|
||||
<pre class="monospace pre-line">{{ .Log }}</pre>
|
||||
</div>
|
||||
<div id="log-sanitized" class="unfocused">
|
||||
<p class="subheading">An attempt has been made to remove sensitive info, but make sure to check yourself.</p>
|
||||
<pre class="monospace pre-line">{{ .SanitizedLog }}</pre>
|
||||
</div>
|
||||
</section>
|
||||
<div class="card ~critical sectioned">
|
||||
<section class="section ~critical">
|
||||
<span class="heading">Crash report for jfa-go</span>
|
||||
{{ if .Err }}
|
||||
<div class="font-mono bg-inherit pre-line mt-4 mb-4">
|
||||
Error: {{ .Err }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<a class="button ~critical mb-4" target="_blank" href="https://github.com/hrfee/jfa-go/issues/new/choose">Create an Issue</a>
|
||||
</section>
|
||||
<section class="section ~neutral @low">
|
||||
<div class="flex-expand">
|
||||
<span class="subheading">Full Log</span>
|
||||
<span class="button ~urge ml-4" id="copy-log">Copy</span>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="col mr-4">
|
||||
<span class="button ~neutral @high supra full-width center" id="button-log-normal">Normal</span>
|
||||
</label>
|
||||
<label class="col mr-4">
|
||||
<span class="button ~neutral @low supra full-width center" id="button-log-sanitized">Sanitized</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="log-normal">
|
||||
<pre class="font-mono bg-inherit pre-line">{{ .Log }}</pre>
|
||||
</div>
|
||||
<div id="log-sanitized" class="unfocused">
|
||||
<p class="subheading">An attempt has been made to remove sensitive info, but make sure to check yourself.</p>
|
||||
<pre class="font-mono bg-inherit pre-line">{{ .SanitizedLog }}</pre>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<script inline src="crash.js"></script>
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="css/bundle.css">
|
||||
{{ template "header.html" . }}
|
||||
<title>{{ .strings.successHeader }} - jfa-go</title>
|
||||
<title>{{ .strings.successHeader }} - jfa-go</title>
|
||||
</head>
|
||||
<body class="section">
|
||||
<div class="page-container">
|
||||
<div class="card ~neutral !normal mb-1">
|
||||
<span class="heading mb-1">{{ .strings.successHeader }}</span>
|
||||
<p class="content mb-1">{{ .successMessage }}</p>
|
||||
<a class="button ~urge !normal full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.successContinueButton }}</a>
|
||||
<div class="card ~neutral @low mb-4">
|
||||
<span class="heading mb-4">{{ .strings.successHeader }}</span>
|
||||
<p class="content my-4">{{ .successMessage }}</p>
|
||||
<a class="button ~urge @high full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.continue }}</a>
|
||||
</div>
|
||||
<i class="content">{{ .contactMessage }}</i>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
window.telegramEnabled = {{ .telegramEnabled }};
|
||||
window.telegramRequired = {{ .telegramRequired }};
|
||||
window.telegramPIN = "{{ .telegramPIN }}";
|
||||
window.emailRequired = {{ .emailRequired }};
|
||||
window.discordEnabled = {{ .discordEnabled }};
|
||||
window.discordRequired = {{ .discordRequired }};
|
||||
window.discordPIN = "{{ .discordPIN }}";
|
||||
|
||||
@@ -14,77 +14,79 @@
|
||||
<body class="max-w-full overflow-x-hidden section">
|
||||
<div id="modal-success" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading mb-1">{{ .strings.successHeader }}</span>
|
||||
<p class="content mb-1">{{ .successMessage }}</p>
|
||||
<a class="button ~urge !normal full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.successContinueButton }}</a>
|
||||
<span class="heading mb-4">{{ if .passwordReset }}{{ .strings.passwordReset }}{{ else }}{{ .strings.successHeader }}{{ end }}</span>
|
||||
<p class="content mb-4">{{ if .passwordReset }}{{ .strings.youCanLoginPassword }}{{ else }}{{ .successMessage }}{{ end }}</p>
|
||||
<a class="button ~urge @low full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.continue }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-confirmation" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading mb-1">{{ .strings.confirmationRequired }}</span>
|
||||
<p class="content mb-1">{{ .strings.confirmationRequiredMessage }}</p>
|
||||
<span class="heading mb-4">{{ .strings.confirmationRequired }}</span>
|
||||
<p class="content mb-4">{{ .strings.confirmationRequiredMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{ if .telegramEnabled }}
|
||||
<div id="modal-telegram" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading mb-1">{{ .strings.linkTelegram }}</span>
|
||||
<p class="content mb-1">{{ .strings.sendPIN }}</p>
|
||||
<h1 class="ac">{{ .telegramPIN }}</h1>
|
||||
<span class="heading mb-4">{{ .strings.linkTelegram }}</span>
|
||||
<p class="content mb-4">{{ .strings.sendPIN }}</p>
|
||||
<p class="text-center text-2xl mb-2">{{ .telegramPIN }}</p>
|
||||
<a class="subheading link-center" href="{{ .telegramURL }}" target="_blank">
|
||||
<span class="shield ~info mr-1">
|
||||
<span class="shield ~info mr-4">
|
||||
<span class="icon">
|
||||
<i class="ri-telegram-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
@{{ .telegramUsername }}
|
||||
</a>
|
||||
<span class="button ~info !normal full-width center mt-1" id="telegram-waiting">{{ .strings.success }}</span>
|
||||
<span class="button ~info @low full-width center mt-4" id="telegram-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .discordEnabled }}
|
||||
<div id="modal-discord" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading mb-1">{{ .strings.linkDiscord }}</span>
|
||||
<p class="content mb-1"> {{ .discordSendPINMessage }}</p>
|
||||
<h1 class="ac">{{ .discordPIN }}</h1>
|
||||
<span class="heading mb-4">{{ .strings.linkDiscord }}</span>
|
||||
<p class="content mb-4"> {{ .discordSendPINMessage }}</p>
|
||||
<h1 class="text-center text-2xl mb-2">{{ .discordPIN }}</h1>
|
||||
<a id="discord-invite"></a>
|
||||
<span class="button ~info !normal full-width center mt-1" id="discord-waiting">{{ .strings.success }}</span>
|
||||
<span class="button ~info @low full-width center mt-4" id="discord-waiting">{{ .strings.success }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .matrixEnabled }}
|
||||
<div id="modal-matrix" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading mb-1">{{ .strings.linkMatrix }}</span>
|
||||
<p class="content mb-1"> {{ .strings.matrixEnterUser }}</p>
|
||||
<input type="text" class="input ~neutral !high" placeholder="@user:riot.im" id="matrix-userid">
|
||||
<div class="subheading link-center mt-1">
|
||||
<span class="shield ~info mr-1">
|
||||
<span class="heading mb-4">{{ .strings.linkMatrix }}</span>
|
||||
<p class="content mb-4"> {{ .strings.matrixEnterUser }}</p>
|
||||
<input type="text" class="input ~neutral @high" placeholder="@user:riot.im" id="matrix-userid">
|
||||
<div class="subheading link-center mt-4">
|
||||
<span class="shield ~info mr-4">
|
||||
<span class="icon">
|
||||
<i class="ri-chat-3-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
{{ .matrixUser }}
|
||||
</div>
|
||||
<span class="button ~info !normal full-width center mt-1" id="matrix-send">{{ .strings.submit }}</span>
|
||||
<span class="button ~info @low full-width center mt-4" id="matrix-send">{{ .strings.submit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<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 class="top-4 left-4 absolute">
|
||||
<span class="dropdown" tabindex="0" id="lang-dropdown">
|
||||
<span class="button ~urge dropdown-button">
|
||||
<i class="ri-global-line"></i>
|
||||
<span class="ml-2 chev"></span>
|
||||
</span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral @low" id="lang-list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div id="notification-box"></div>
|
||||
<div class="page-container">
|
||||
<div class="card ~neutral !low">
|
||||
<div class="card dark:~d_neutral @low">
|
||||
<div class="row baseline">
|
||||
<span class="col heading">
|
||||
{{ if .passwordReset }}
|
||||
@@ -106,41 +108,41 @@
|
||||
{{ if .userExpiry }}
|
||||
<aside class="col aside sm ~warning" id="user-expiry-message"></aside>
|
||||
{{ end }}
|
||||
<form class="card ~neutral !normal" id="form-create" href="">
|
||||
<form class="card dark:~d_neutral @low" id="form-create" href="">
|
||||
{{ if not .passwordReset }}
|
||||
<label class="label supra">
|
||||
{{ .strings.username }}
|
||||
<input type="text" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="create-username" aria-label="{{ .strings.username }}">
|
||||
<input type="text" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.username }}" id="create-username" aria-label="{{ .strings.username }}">
|
||||
</label>
|
||||
|
||||
<label class="label supra" for="create-email">{{ .strings.emailAddress }}</label>
|
||||
<input type="email" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
|
||||
<input type="email" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
|
||||
{{ if .telegramEnabled }}
|
||||
<span class="button ~info !normal full-width center mb-1" id="link-telegram">{{ .strings.linkTelegram }}</span>
|
||||
<span class="button ~info @low full-width center mb-4" id="link-telegram">{{ .strings.linkTelegram }}</span>
|
||||
{{ end }}
|
||||
{{ if .discordEnabled }}
|
||||
<span class="button ~info !normal full-width center mb-1" id="link-discord">{{ .strings.linkDiscord }}</span>
|
||||
<span class="button ~info @low full-width center mb-4" id="link-discord">{{ .strings.linkDiscord }}</span>
|
||||
{{ end }}
|
||||
{{ if .matrixEnabled }}
|
||||
<span class="button ~info !normal full-width center mb-1" id="link-matrix">{{ .strings.linkMatrix }}</span>
|
||||
<span class="button ~info @low full-width center mb-4" id="link-matrix">{{ .strings.linkMatrix }}</span>
|
||||
{{ end }}
|
||||
{{ if or (.telegramEnabled) (or .discordEnabled .matrixEnabled) }}
|
||||
<div id="contact-via" class="unfocused">
|
||||
<label class="row switch pb-1">
|
||||
<label class="row switch pb-4">
|
||||
<input type="radio" name="contact-via" value="email"><span>Contact through Email</span>
|
||||
</label>
|
||||
{{ if .telegramEnabled }}
|
||||
<label class="row switch pb-1">
|
||||
<label class="row switch pb-4">
|
||||
<input type="radio" name="contact-via" value="telegram" id="contact-via-telegram"><span>Contact through Telegram</span>
|
||||
</label>
|
||||
{{ end }}
|
||||
{{ if .discordEnabled }}
|
||||
<label class="row switch pb-1">
|
||||
<label class="row switch pb-4">
|
||||
<input type="radio" name="contact-via" value="discord" id="contact-via-discord"><span>Contact through Discord</span>
|
||||
</label>
|
||||
{{ end }}
|
||||
{{ if .matrixEnabled }}
|
||||
<label class="row switch pb-1">
|
||||
<label class="row switch pb-4">
|
||||
<input type="radio" name="contact-via" value="matrix" id="contact-via-matrix"><span>Contact through Matrix</span>
|
||||
</label>
|
||||
{{ end }}
|
||||
@@ -148,13 +150,13 @@
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<label class="label supra" for="create-password">{{ .strings.password }}</label>
|
||||
<input type="password" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-password" aria-label="{{ .strings.password }}">
|
||||
<input type="password" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.password }}" id="create-password" aria-label="{{ .strings.password }}">
|
||||
|
||||
<label class="label supra" for="create-reenter-password">{{ .strings.reEnterPassword }}</label>
|
||||
<input type="password" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-reenter-password" aria-label="{{ .strings.reEnterPassword }}">
|
||||
<input type="password" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.password }}" id="create-reenter-password" aria-label="{{ .strings.reEnterPassword }}">
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal full-width center supra submit">
|
||||
<span class="button ~urge @low full-width center supra submit">
|
||||
{{ if .passwordReset }}
|
||||
{{ .strings.reset }}
|
||||
{{ else }}
|
||||
@@ -165,7 +167,7 @@
|
||||
</form>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card ~neutral !normal">
|
||||
<div class="card ~neutral @low mb-4">
|
||||
<span class="label supra" for="inv-uses">{{ .strings.passwordRequirementsHeader }}</span>
|
||||
<ul>
|
||||
{{ range $key, $value := .requirements }}
|
||||
@@ -176,7 +178,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
{{ if .contactMessage }}
|
||||
<aside class="col aside sm ~info">{{ .contactMessage }}</aside>
|
||||
<aside class="col aside sm ~info mt-4">{{ .contactMessage }}</aside>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
</head>
|
||||
<body class="section">
|
||||
<div class="page-container">
|
||||
<h1 class="heading">Invalid invite code.</h1>
|
||||
<p class="content">The code above was either incorrect, or has expired.</p>
|
||||
<p class="content">
|
||||
{{ .contactMessage }}
|
||||
</p>
|
||||
<div class="card">
|
||||
<h1 class="text-3xl font-semibold">Invalid invite code.</h1>
|
||||
<p class="content">The code above was either incorrect, or has expired.</p>
|
||||
<p class="content">
|
||||
{{ .contactMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="page-container">
|
||||
<div class="card ~neutral !normal mb-1">
|
||||
<span class="heading mb-1">
|
||||
<div class="card ~neutral @low mb-4">
|
||||
<span class="heading mb-4">
|
||||
{{ if .success }}
|
||||
{{ .strings.passwordReset }}
|
||||
{{ else }}
|
||||
{{ .strings.resetFailed }}
|
||||
{{ end }}
|
||||
</span>
|
||||
<p class="content mb-1">
|
||||
<p class="content mb-4">
|
||||
{{ if .success }}
|
||||
{{ if .ombiEnabled }}
|
||||
{{ .strings.youCanLoginOmbi }}
|
||||
@@ -35,7 +35,7 @@
|
||||
<aside class="aside ~warning">
|
||||
{{ .strings.changeYourPassword }}
|
||||
</aside>
|
||||
<span class="button ~urge !normal full-width center supra p-1 mt-1" id="pin" title="{{ .strings.copy }}">{{ .pin }}</span>
|
||||
<span class="button ~urge @low w-100 text-center text-xl p-1 mt-4" id="pin" title="{{ .strings.copy }}">{{ .pin }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<i class="content">{{ .contactMessage }}</i>
|
||||
|
||||
406
html/setup.html
406
html/setup.html
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="light-theme">
|
||||
<html lang="en" class="light">
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="css/bundle.css">
|
||||
{{ template "header.html" . }}
|
||||
@@ -7,18 +7,20 @@
|
||||
</head>
|
||||
<body class="max-w-full overflow-x-hidden section">
|
||||
<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 class="top-4 left-4 absolute">
|
||||
<span class="dropdown" tabindex="0" id="lang-dropdown">
|
||||
<span class="button ~urge dropdown-button">
|
||||
<i class="ri-global-line"></i>
|
||||
<span class="ml-2 chev"></span>
|
||||
</span>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~neutral @low" id="lang-list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="page-container" id="page-container">
|
||||
<div class="card ~neutral !low mb-1">
|
||||
<div class="card ~neutral @low mb-2">
|
||||
<div class="row">
|
||||
<img class="banner header" src="banner.svg" alt="jfa-go" />
|
||||
</div>
|
||||
@@ -26,70 +28,70 @@
|
||||
<span class="heading welcome">{{ .lang.StartPage.welcome }}</span>
|
||||
</div>
|
||||
<div class="row col flex center">
|
||||
<p class="content">{{ .lang.StartPage.pressStart }}</p>
|
||||
<p class="content my-2">{{ .lang.StartPage.pressStart }}</p>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="support">{{ .lang.StartPage.httpsNotice }}</span>
|
||||
<span class="button ~urge !normal next">{{ .lang.StartPage.start }}</span>
|
||||
<span class="button ~urge @low next">{{ .lang.StartPage.start }}</span>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.Language.title }}</span>
|
||||
<p class="content" id="language-description"></p>
|
||||
<p class="content my-2" id="language-description"></p>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Language.defaultAdminLang }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<span class="mt-4">{{ .lang.Language.defaultAdminLang }}</span>
|
||||
<div class="select ~neutral @low mt-4 mb-2">
|
||||
<select id="ui-language-admin">
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Language.defaultFormLang }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<span class="mt-4">{{ .lang.Language.defaultFormLang }}</span>
|
||||
<div class="select ~neutral @low mt-4 mb-2">
|
||||
<select id="ui-language-form">
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Language.defaultEmailLang }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<span class="mt-4">{{ .lang.Language.defaultEmailLang }}</span>
|
||||
<div class="select ~neutral @low mt-4 mb-2">
|
||||
<select id="email-language">
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.General.title }}</span>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.General.listenAddress }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="ui-host" value="0.0.0.0">
|
||||
<span class="mt-4">{{ .lang.General.listenAddress }}</span>
|
||||
<input type="url" class="input ~neutral @low mt-4 mb-2" id="ui-host" value="0.0.0.0">
|
||||
</label>
|
||||
<label class="row switch">
|
||||
<input type="checkbox" id="advanced-tls"><span>{{ .lang.General.useHTTPS }}</span>
|
||||
<input type="checkbox" class="mr-2" id="advanced-tls"><span>{{ .lang.General.useHTTPS }}</span>
|
||||
</label>
|
||||
<p class="support mb-1">{{ .lang.General.useHTTPSNotice }}</p>
|
||||
<p class="support mb-2 mt-1">{{ .lang.General.useHTTPSNotice }}</p>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.General.pathToCertificate }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="advanced-tls_cert">
|
||||
<span class="mt-4">{{ .lang.General.pathToCertificate }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-tls_cert">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.General.pathToKeyFile }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="advanced-tls_key">
|
||||
<span class="mt-4">{{ .lang.General.pathToKeyFile }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-tls_key">
|
||||
</label>
|
||||
<span class="heading">{{ .lang.Updates.title }}</span>
|
||||
<p class="content" id="updates-description"></p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="updates-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
|
||||
<p class="content my-2" id="updates-description"></p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" class="mr-2" id="updates-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Updates.updateChannel }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<div class="select ~neutral @low mt-4 mb-2">
|
||||
<select id="updates-channel">
|
||||
<option value="stable">{{ .lang.Updates.stable }}</option>
|
||||
<option value="unstable">{{ .lang.Updates.unstable }}</option>
|
||||
@@ -99,21 +101,21 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.port }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="ui-port" value="8056">
|
||||
<span class="mt-4">{{ .lang.Strings.port }}</span>
|
||||
<input type="number" class="input ~neutral @low mt-4 mb-2" id="ui-port" value="8056">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.General.httpsPort }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="advanced-tls_port" value="8057">
|
||||
<span class="mt-4">{{ .lang.General.httpsPort }}</span>
|
||||
<input type="number" class="input ~neutral @low mt-4 mb-2" id="advanced-tls_port" value="8057">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.General.urlBase }} ({{ .lang.Strings.optional }})</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half" id="ui-url_base">
|
||||
<p class="support mb-1">{{ .lang.General.urlBaseNotice }}</p>
|
||||
<span class="mt-4">{{ .lang.General.urlBase }} ({{ .lang.Strings.optional }})</span>
|
||||
<input type="url" class="input ~neutral @low mt-4" id="ui-url_base">
|
||||
<p class="support mb-2 mt-1">{{ .lang.General.urlBaseNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Strings.theme }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<div class="select ~neutral @low mt-4 mb-2">
|
||||
<select id="ui-theme">
|
||||
<option value="Jellyfin (Dark)">{{ .lang.General.darkTheme }}</option>
|
||||
<option value="Default (Light)">{{ .lang.General.lightTheme }}</option>
|
||||
@@ -123,142 +125,142 @@
|
||||
</div>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.Login.title }}</span>
|
||||
<p class="content">{{ .lang.Login.description }}</p>
|
||||
<div class="pl-1">
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="ui-jellyfin_login" value="true" checked><span>{{ .lang.Login.authorizeWithJellyfin }}</span>
|
||||
<p class="content my-2">{{ .lang.Login.description }}</p>
|
||||
<div class="pl-4">
|
||||
<label class="row switch pb-4">
|
||||
<input type="radio" class="mr-2" name="ui-jellyfin_login" value="true" checked><span>{{ .lang.Login.authorizeWithJellyfin }}</span>
|
||||
</label>
|
||||
<label class="row switch pl-1 pb-1">
|
||||
<input type="checkbox" id="ui-admin_only"><span>{{ .lang.Login.adminOnly }}</span>
|
||||
<label class="row switch pl-4 pb-4">
|
||||
<input type="checkbox" class="mr-2" id="ui-admin_only"><span>{{ .lang.Login.adminOnly }}</span>
|
||||
</label>
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="ui-jellyfin_login" value="false"><span>{{ .lang.Login.authorizeManual }}</span>
|
||||
<label class="row switch pb-4">
|
||||
<input type="radio" class="mr-2" name="ui-jellyfin_login" value="false"><span>{{ .lang.Login.authorizeManual }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="login-manual">
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" id="ui-username" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.username }}">
|
||||
<span class="mt-4">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" id="ui-username" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.username }}">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Strings.password }}</span>
|
||||
<input type="password" id="ui-password" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.password }}">
|
||||
<input type="password" id="ui-password" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.password }}">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Strings.emailAddress }} ({{ .lang.Strings.optional }})</span>
|
||||
<input type="email" id="ui-email" class="input ~neutral !normal mt-half" placeholder="email@address">
|
||||
<span class="support mb-1">{{ .lang.Login.emailNotice }}</span>
|
||||
<input type="email" id="ui-email" class="input ~neutral @low mt-4" placeholder="email@address">
|
||||
<span class="support mb-2 mt-1">{{ .lang.Login.emailNotice }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.JellyfinEmby.title }}</span>
|
||||
<p class="content">{{ .lang.JellyfinEmby.description }}</p>
|
||||
<p class="content my-2">{{ .lang.JellyfinEmby.description }}</p>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span>{{ .lang.Strings.serverType }}</span>
|
||||
<div class="select ~neutral !normal mt-half">
|
||||
<div class="select ~neutral @low mt-4">
|
||||
<select id="jellyfin-type">
|
||||
<option value="jellyfin">Jellyfin</option>
|
||||
<option value="emby">Emby</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="support mb-1">{{ .lang.JellyfinEmby.embyNotice }}</p>
|
||||
<p class="support mb-2 mt-1">{{ .lang.JellyfinEmby.embyNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.JellyfinEmby.replaceJellyfin }} ({{ .lang.Strings.optional }})</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="jellyfin-substitute_jellyfin_strings">
|
||||
<p class="support mb-1">{{ .lang.JellyfinEmby.replaceJellyfinNotice }}</p>
|
||||
<span class="mt-4">{{ .lang.JellyfinEmby.replaceJellyfin }} ({{ .lang.Strings.optional }})</span>
|
||||
<input type="text" class="input ~neutral @low mt-4" id="jellyfin-substitute_jellyfin_strings">
|
||||
<p class="support mb-2 mt-1">{{ .lang.JellyfinEmby.replaceJellyfinNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" id="jellyfin-username" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.username }}">
|
||||
<span class="mt-4">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" id="jellyfin-username" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.username }}">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Strings.password }}</span>
|
||||
<input type="password" id="jellyfin-password" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.password }}">
|
||||
<input type="password" id="jellyfin-password" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.password }}">
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.internal }})</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="jellyfin-server" placeholder="http://jellyf.in:80">
|
||||
<span class="mt-4">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.internal }})</span>
|
||||
<input type="url" class="input ~neutral @low mt-4 mb-2" id="jellyfin-server" placeholder="http://jellyf.in:80">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.external }})</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half" id="jellyfin-public_server" placeholder="https://jellyf.in">
|
||||
<p class="support mb-1">{{ .lang.JellyfinEmby.addressExternalNotice }}</p>
|
||||
<span class="mt-4">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.external }})</span>
|
||||
<input type="url" class="input ~neutral @low mt-4" id="jellyfin-public_server" placeholder="https://jellyf.in">
|
||||
<p class="support mb-2 mt-1">{{ .lang.JellyfinEmby.addressExternalNotice }}</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal" id="jellyfin-test-connection">{{ .lang.JellyfinEmby.testConnection }}</span>
|
||||
<span class="button ~urge !normal next" disabled>{{ .lang.Strings.next }}</span>
|
||||
<span class="button ~urge @low" id="jellyfin-test-connection">{{ .lang.JellyfinEmby.testConnection }}</span>
|
||||
<span class="button ~urge @low next" disabled>{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.Ombi.title }}</span>
|
||||
<p class="content">{{ .lang.Ombi.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="ombi-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
<p class="content my-2">{{ .lang.Ombi.description }}</p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" class="mr-2" id="ombi-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.serverAddress }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="ombi-server" placeholder="ombi.jellyf.in">
|
||||
<span class="mt-4">{{ .lang.Strings.serverAddress }}</span>
|
||||
<input type="url" class="input ~neutral @low mt-4 mb-2" id="ombi-server" placeholder="ombi.jellyf.in">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.apiKey }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="ombi-api_key">
|
||||
<p class="support mb-1">{{ .lang.Ombi.apiKeyNotice }}</p>
|
||||
<span class="mt-4">{{ .lang.Strings.apiKey }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4" id="ombi-api_key">
|
||||
<p class="support mb-2 mt-1">{{ .lang.Ombi.apiKeyNotice }}</p>
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.Messages.title }}</span>
|
||||
<p class="content" id="messages-description"></p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="messages-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
|
||||
<p class="content my-2" id="messages-description"></p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" class="mr-2" id="messages-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.dateFormat }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="email-date_format" value="%d/%m/%y">
|
||||
<p class="support mb-1" id="email-dateformat-notice"></p>
|
||||
<span class="mt-4">{{ .lang.Email.dateFormat }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4" id="email-date_format" value="%d/%m/%y">
|
||||
<p class="support mb-2 mt-1" id="email-dateformat-notice"></p>
|
||||
</label>
|
||||
<div>
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span>
|
||||
<label class="row switch pb-4">
|
||||
<input type="radio" class="mr-2" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span>
|
||||
</label>
|
||||
<label class="row switch pb-1">
|
||||
<input type="radio" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span>
|
||||
<label class="row switch pb-4">
|
||||
<input type="radio" class="mr-2" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="email-sect">
|
||||
<span class="heading">{{ .lang.Email.title }}</span>
|
||||
<p class="content" id="email-description"></p>
|
||||
<p class="content my-2" id="email-description"></p>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="label">
|
||||
<span>{{ .lang.Email.method }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<div class="select ~neutral @low mt-4 mb-2">
|
||||
<select id="email-method">
|
||||
<option value="">{{ .lang.Strings.disabled }}</option>
|
||||
<option value="smtp">SMTP</option>
|
||||
@@ -267,24 +269,24 @@
|
||||
</div>
|
||||
</label>
|
||||
<label class="row switch">
|
||||
<input type="checkbox" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span>
|
||||
<p class="support mb-1">{{ .lang.Email.useEmailAsUsernameNotice }}</p>
|
||||
<input type="checkbox" class="mr-2" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span>
|
||||
<p class="support mb-2 mt-1">{{ .lang.Email.useEmailAsUsernameNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.fromAddress }}</span>
|
||||
<input type="email" class="input ~neutral !normal mt-half mb-1" id="email-address" placeholder="mail@jellyf.in">
|
||||
<span class="mt-4">{{ .lang.Email.fromAddress }}</span>
|
||||
<input type="email" class="input ~neutral @low mt-4 mb-2" id="email-address" placeholder="mail@jellyf.in">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.senderName }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="email-from" value="Jellyfin">
|
||||
<span class="mt-4">{{ .lang.Email.senderName }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4 mb-2" id="email-from" value="Jellyfin">
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div id="email-smtp">
|
||||
<p class="subheading">SMTP</p>
|
||||
<p class="text-2xl font-semibold mb-2">SMTP</p>
|
||||
<label class="label">
|
||||
<span>{{ .lang.Email.encryption }}</span>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<div class="select ~neutral @low mt-4 mb-2">
|
||||
<select id="smtp-encryption">
|
||||
<option value="starttls">STARTTLS ({{ .lang.Strings.port }} 587)</option>
|
||||
<option value="ssl_tls">SSL/TLS ({{ .lang.Strings.port }} 465)</option>
|
||||
@@ -292,197 +294,197 @@
|
||||
</div>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.serverAddress }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="smtp-server" placeholder="smtp.jellyf.in">
|
||||
<span class="mt-4">{{ .lang.Strings.serverAddress }}</span>
|
||||
<input type="url" class="input ~neutral @low mt-4 mb-2" id="smtp-server" placeholder="smtp.jellyf.in">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.port }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="smtp-port" placeholder="587">
|
||||
<span class="mt-4">{{ .lang.Strings.port }}</span>
|
||||
<input type="number" class="input ~neutral @low mt-4 mb-2" id="smtp-port" placeholder="587">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="smtp-username">
|
||||
<span class="mt-4">{{ .lang.Strings.username }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4 mb-2" id="smtp-username">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.password }}</span>
|
||||
<input type="password" class="input ~neutral !normal mt-half mb-1" id="smtp-password">
|
||||
<span class="mt-4">{{ .lang.Strings.password }}</span>
|
||||
<input type="password" class="input ~neutral @low mt-4 mb-2" id="smtp-password">
|
||||
</label>
|
||||
</div>
|
||||
<div id="email-mailgun">
|
||||
<p class="subheading">Mailgun</p>
|
||||
<p class="text-2xl font-semibold mb-2">Mailgun</p>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Email.mailgunApiURL }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages">
|
||||
<span class="mt-4">{{ .lang.Email.mailgunApiURL }}</span>
|
||||
<input type="url" class="input ~neutral @low mt-4 mb-2" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.apiKey }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_key">
|
||||
<span class="mt-4">{{ .lang.Strings.apiKey }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4 mb-2" id="mailgun-api_key">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused related-to-email">
|
||||
<div class="card ~neutral @low mb-2 unfocused related-to-email">
|
||||
<span class="heading">{{ .lang.Notifications.title }}</span>
|
||||
<p class="content">{{ .lang.Notifications.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="notifications-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
<p class="content my-2">{{ .lang.Notifications.description }}</p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" class="mr-2" id="notifications-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<span class="heading">{{ .lang.WelcomeEmails.title }}</span>
|
||||
<p class="content">{{ .lang.WelcomeEmails.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="welcome_email-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
<p class="content my-2">{{ .lang.WelcomeEmails.description }}</p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" class="mr-2" id="welcome_email-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.emailSubject }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="welcome_email-subject" placeholder="{{ .emailLang.WelcomeEmail.title }}">
|
||||
<span class="mt-4">{{ .lang.Strings.emailSubject }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4 mb-2" id="welcome_email-subject" placeholder="{{ .emailLang.WelcomeEmail.title }}">
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused related-to-email">
|
||||
<div class="card ~neutral @low mb-2 unfocused related-to-email">
|
||||
<span class="heading">{{ .lang.InviteEmails.title }}</span>
|
||||
<p class="content">{{ .lang.InviteEmails.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="invite_emails-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
<p class="content my-2">{{ .lang.InviteEmails.description }}</p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" class="mr-2" id="invite_emails-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.URL }}</span>
|
||||
<input type="url" class="input ~neutral !normal mt-half mb-1" id="invite_emails-url_base" placeholder="https://accounts.jellyf.in/invite">
|
||||
<span class="mt-4">{{ .lang.Strings.URL }}</span>
|
||||
<input type="url" class="input ~neutral @low mt-4 mb-2" id="invite_emails-url_base" placeholder="https://accounts.jellyf.in/invite">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.Strings.emailSubject }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="invite_emails-subject" placeholder="{{ .emailLang.InviteEmail.title }}">
|
||||
<span class="mt-4">{{ .lang.Strings.emailSubject }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4 mb-2" id="invite_emails-subject" placeholder="{{ .emailLang.InviteEmail.title }}">
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div id="password-resets" class="card ~neutral !low mb-1 unfocused related-to-email">
|
||||
<div id="password-resets" class="card ~neutral @low mb-2 unfocused related-to-email">
|
||||
<span class="heading">{{ .lang.PasswordResets.title }}</span>
|
||||
<p class="content">{{ .lang.PasswordResets.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="password_resets-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
<p class="content my-2">{{ .lang.PasswordResets.description }}</p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" class="mr-2" id="password_resets-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordResets.pathToJellyfin }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="password_resets-watch_directory" placeholder="/config/jellyfin">
|
||||
<p class="support mb-1">{{ .lang.PasswordResets.pathToJellyfinNotice }}</p>
|
||||
<span class="mt-4">{{ .lang.PasswordResets.pathToJellyfin }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4" id="password_resets-watch_directory" placeholder="/config/jellyfin">
|
||||
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.pathToJellyfinNotice }}</p>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="password_resets-link_reset"><span>{{ .lang.PasswordResets.resetLinks }}</span>
|
||||
<p class="support mb-1">{{ .lang.PasswordResets.resetLinksNotice }}</p>
|
||||
<input type="checkbox" class="mr-2" id="password_resets-link_reset"><span>{{ .lang.PasswordResets.resetLinks }}</span>
|
||||
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.resetLinksNotice }}</p>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="password_resets-set_password"><span>{{ .lang.PasswordResets.setPassword }}</span>
|
||||
<p class="support mb-1">{{ .lang.PasswordResets.setPasswordNotice }}</p>
|
||||
<input type="checkbox" class="mr-2" id="password_resets-set_password"><span>{{ .lang.PasswordResets.setPassword }}</span>
|
||||
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.setPasswordNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<p class="mt-half">{{ .lang.PasswordResets.resetLinksLanguage }}</p>
|
||||
<div class="select ~neutral !normal mt-half mb-1">
|
||||
<p class="mt-4">{{ .lang.PasswordResets.resetLinksLanguage }}</p>
|
||||
<div class="select ~neutral @low mt-4 mb-2">
|
||||
<select id="password_resets-language">
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
<label class="row label">
|
||||
<span class="mt-half">{{ .lang.Strings.emailSubject }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half mb-1" id="password_resets-subject" placeholder="{{ .emailLang.PasswordReset.title }}">
|
||||
<span class="mt-4">{{ .lang.Strings.emailSubject }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4 mb-2" id="password_resets-subject" placeholder="{{ .emailLang.PasswordReset.title }}">
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.PasswordValidation.title }}</span>
|
||||
<p class="content">{{ .lang.PasswordValidation.description }}</p>
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" id="password_validation-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
|
||||
<p class="content my-2">{{ .lang.PasswordValidation.description }}</p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" class="mr-2" id="password_validation-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordValidation.length }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-min_length" value="8">
|
||||
<span class="mt-4">{{ .lang.PasswordValidation.length }}</span>
|
||||
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-min_length" value="8">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordValidation.uppercase }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-upper" value="1">
|
||||
<span class="mt-4">{{ .lang.PasswordValidation.uppercase }}</span>
|
||||
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-upper" value="1">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordValidation.lowercase }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-lower" value="0">
|
||||
<span class="mt-4">{{ .lang.PasswordValidation.lowercase }}</span>
|
||||
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-lower" value="0">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordValidation.numbers }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-number" value="0">
|
||||
<span class="mt-4">{{ .lang.PasswordValidation.numbers }}</span>
|
||||
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-number" value="0">
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.PasswordValidation.special }}</span>
|
||||
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-special" value="0">
|
||||
<span class="mt-4">{{ .lang.PasswordValidation.special }}</span>
|
||||
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-special" value="0">
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.HelpMessages.title }}</span>
|
||||
<p class="content">{{ .lang.HelpMessages.description }}</p>
|
||||
<p class="content my-2">{{ .lang.HelpMessages.description }}</p>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.HelpMessages.contactMessage }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="ui-contact_message">
|
||||
<p class="support mb-1">{{ .lang.HelpMessages.contactMessageNotice }}</p>
|
||||
<span class="mt-4">{{ .lang.HelpMessages.contactMessage }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4" id="ui-contact_message">
|
||||
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.contactMessageNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.HelpMessages.helpMessage }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="ui-help_message">
|
||||
<p class="support mb-1">{{ .lang.HelpMessages.helpMessageNotice }}</p>
|
||||
<span class="mt-4">{{ .lang.HelpMessages.helpMessage }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4" id="ui-help_message">
|
||||
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.helpMessageNotice }}</p>
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="mt-half">{{ .lang.HelpMessages.successMessage }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="ui-success_message">
|
||||
<p class="support mb-1">{{ .lang.HelpMessages.successMessageNotice }}</p>
|
||||
<span class="mt-4">{{ .lang.HelpMessages.successMessage }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4" id="ui-success_message">
|
||||
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.successMessageNotice }}</p>
|
||||
</label>
|
||||
<label class="label related-to-email">
|
||||
<span class="mt-half">{{ .lang.HelpMessages.emailMessage }}</span>
|
||||
<input type="text" class="input ~neutral !normal mt-half" id="email-message">
|
||||
<p class="support mb-1">{{ .lang.HelpMessages.emailMessageNotice }}</p>
|
||||
<span class="mt-4">{{ .lang.HelpMessages.emailMessage }}</span>
|
||||
<input type="text" class="input ~neutral @low mt-4" id="email-message">
|
||||
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.emailMessageNotice }}</p>
|
||||
</label>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral !low mb-1 unfocused">
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<div class="row col flex center">
|
||||
<span class="heading">{{ .lang.EndPage.finished }}</span>
|
||||
</div>
|
||||
<div class="row col flex center">
|
||||
<p class="content">{{ .lang.EndPage.restartMessage }}</p>
|
||||
<p class="content my-2">{{ .lang.EndPage.restartMessage }}</p>
|
||||
</div>
|
||||
<div class="row col flex center">
|
||||
<span class="button ~neutral !normal back mr-1">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge !normal" id="restart">{{ .lang.Strings.submit }}</span>
|
||||
<span class="button ~urge !normal unfocused" id="refresh">{{ .lang.EndPage.refreshPage }}</span>
|
||||
<span class="button ~neutral @low back mr-4">{{ .lang.Strings.back }}</span>
|
||||
<span class="button ~urge @low" id="restart">{{ .lang.Strings.submit }}</span>
|
||||
<span class="button ~urge @low unfocused" id="refresh">{{ .lang.EndPage.refreshPage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
BIN
images/thumb-white.png
Normal file
BIN
images/thumb-white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
203
images/thumb-white.svg
Normal file
203
images/thumb-white.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 128 KiB |
@@ -100,7 +100,8 @@
|
||||
"searchDiscordUser": "Gib den Discord-Benutzername ein, um den Benutzer zu finden.",
|
||||
"findDiscordUser": "Suche Discord-Benutzer",
|
||||
"linkMatrixDescription": "Gib den Benutzernamen und das Passwort des Benutzers ein, der als Bot verwendet werden soll. Nach dem Absenden wird die App neu gestartet.",
|
||||
"matrixHomeServer": "Adresse des Homeservers"
|
||||
"matrixHomeServer": "Adresse des Homeservers",
|
||||
"templates": "Vorlagen"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "E-Mail-Adresse von {n} geändert.",
|
||||
@@ -140,7 +141,8 @@
|
||||
"noUpdatesAvailable": "Keinen neuen Aktualisierungen verfügbar.",
|
||||
"updateAppliedRefresh": "Update angewendet, bitte aktualisieren.",
|
||||
"telegramVerified": "Telegram-Konto verifiziert.",
|
||||
"accountConnected": "Konto verbunden."
|
||||
"accountConnected": "Konto verbunden.",
|
||||
"savedAnnouncement": "Ankündigung gespeichert."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"reEnable": "Re-enable",
|
||||
"setExpiry": "Set expiry",
|
||||
"disable": "Disable",
|
||||
"admin": "Admin",
|
||||
"updates": "Updates",
|
||||
@@ -57,8 +58,13 @@
|
||||
"reset": "Reset",
|
||||
"edit": "Edit",
|
||||
"donate": "Donate",
|
||||
"sendPWR": "Send Password Reset",
|
||||
"contactThrough": "Contact through:",
|
||||
"extendExpiry": "Extend expiry",
|
||||
"sendPWRManual": "User {n} has no method of contact, press copy to get a link to send to them.",
|
||||
"sendPWRSuccess": "Password reset link sent.",
|
||||
"sendPWRSuccessManual": "If the user hasn't received it, press copy to get a link to manually send to them.",
|
||||
"sendPWRValidFor": "The link is valid for 30m.",
|
||||
"customizeMessages": "Customize Messages",
|
||||
"customizeMessagesDescription": "If you don't want to use jfa-go's message templates, you can create your own using Markdown.",
|
||||
"markdownSupported": "Markdown is supported.",
|
||||
@@ -77,8 +83,8 @@
|
||||
"settingsRefreshPage": "Refresh the page in a few seconds.",
|
||||
"settingsRequiredOrRestartMessage": "Note: {n} indicates a required field, {n} indicates changes require a restart.",
|
||||
"settingsSave": "Save",
|
||||
"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",
|
||||
"ombiProfile": "Ombi user profile",
|
||||
"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 when this profile is selected.",
|
||||
"userProfiles": "User Profiles",
|
||||
"userProfilesDescription": "Profiles are applied to users when they create an account. A profile include library access rights and homescreen layout.",
|
||||
"userProfilesIsDefault": "Default",
|
||||
@@ -114,7 +120,7 @@
|
||||
"saveEmail": "Email saved.",
|
||||
"sentAnnouncement": "Announcement sent.",
|
||||
"savedAnnouncement": "Announcement saved.",
|
||||
"setOmbiDefaults": "Stored ombi defaults.",
|
||||
"setOmbiProfile": "Stored ombi profile.",
|
||||
"updateApplied": "Update applied, please restart.",
|
||||
"updateAppliedRefresh": "Update applied, please refresh.",
|
||||
"telegramVerified": "Telegram account verified.",
|
||||
@@ -135,7 +141,7 @@
|
||||
"errorLoadUsers": "Failed to load users.",
|
||||
"errorSaveSettings": "Couldn't save settings.",
|
||||
"errorLoadSettings": "Failed to load settings.",
|
||||
"errorSetOmbiDefaults": "Failed to store ombi defaults.",
|
||||
"errorSetOmbiProfile": "Failed to store ombi profile.",
|
||||
"errorLoadOmbiUsers": "Failed to load ombi users.",
|
||||
"errorChangedEmailAddress": "Couldn't change email address of {n}.",
|
||||
"errorFailureCheckLogs": "Failed (check console/logs)",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Español(ES)"
|
||||
"name": "Español (ES)"
|
||||
},
|
||||
"strings": {
|
||||
"invites": "Invitaciones",
|
||||
@@ -13,7 +13,7 @@
|
||||
"inviteNumberOfUses": "Números de usos",
|
||||
"inviteDuration": "Duración de invitación",
|
||||
"warning": "Advertencia",
|
||||
"inviteInfiniteUsesWarning": "Las invitaciones con usos infinitos pueden usarse abusivamente",
|
||||
"inviteInfiniteUsesWarning": "Las invitaciones con usos infinitos se pueden usar de forma abusiva",
|
||||
"inviteSendToEmail": "Enviar a",
|
||||
"login": "Acceso",
|
||||
"logout": "Cerrar sesión",
|
||||
@@ -37,10 +37,10 @@
|
||||
"user": "Usuario",
|
||||
"expiry": "Expiración",
|
||||
"userExpiry": "Caducidad del usuario",
|
||||
"userExpiryDescription": "Una cantidad específica de tiempo después de cada registro, jfa-go eliminará/deshabilitará la cuenta. Puede cambiar este comportamiento en la configuración.",
|
||||
"userExpiryDescription": "Una cantidad de tiempo específica después de cada registro, jfa-go eliminará / deshabilitará la cuenta. Puede cambiar este comportamiento en la configuración.",
|
||||
"aboutProgram": "Acerca de",
|
||||
"version": "Versión",
|
||||
"commitNoun": "Cometer",
|
||||
"commitNoun": "Revisión",
|
||||
"newUser": "Nuevo usuario",
|
||||
"profile": "Perfil",
|
||||
"unknown": "Desconocido",
|
||||
@@ -49,38 +49,38 @@
|
||||
"subject": "Asunto del email",
|
||||
"message": "Mensaje",
|
||||
"variables": "Variables",
|
||||
"preview": "Previsualizar",
|
||||
"preview": "Vista previa",
|
||||
"reset": "Reiniciar",
|
||||
"edit": "Editar",
|
||||
"extendExpiry": "Extender el vencimiento",
|
||||
"customizeMessages": "Personalizar emails",
|
||||
"customizeMessagesDescription": "Si no desea utilizar las plantillas de correo electrónico de jfa-go, puede crear las suyas propias con Markdown.",
|
||||
"customizeMessages": "Personalizar mensajes",
|
||||
"customizeMessagesDescription": "Si no desea utilizar las plantillas de mensajes de jfa-go, puede crear las suyas con Markdown.",
|
||||
"markdownSupported": "Se admite Markdown.",
|
||||
"modifySettings": "Modificar configuración",
|
||||
"modifySettingsDescription": "Aplique la configuración de un perfil existente u obténgalos directamente de un usuario.",
|
||||
"applyHomescreenLayout": "Aplicar el diseño de la pantalla de inicio",
|
||||
"sendDeleteNotificationEmail": "Enviar notificación a correo",
|
||||
"sendDeleteNotificationEmail": "Enviar mensaje de notificación",
|
||||
"sendDeleteNotifiationExample": "Tu cuenta ha sido eliminada.",
|
||||
"settingsRestart": "Reiniciar",
|
||||
"settingsRestarting": "Reiniciando…",
|
||||
"settingsRestartRequired": "Reinicio necesario",
|
||||
"settingsRestartRequiredDescription": "Es necesario reiniciar para aplicar algunas configuraciones que cambió. ¿Reiniciar ahora o más tarde?",
|
||||
"settingsApplyRestartLater": "Aplicar, reiniciar más tarde",
|
||||
"settingsApplyRestartNow": "Aplicar, reiniciar más tarde",
|
||||
"settingsApplyRestartNow": "Aplicar y reiniciar",
|
||||
"settingsApplied": "Se aplicó la configuración.",
|
||||
"settingsRefreshPage": "Actualiza la página en unos segundos.",
|
||||
"settingsRequiredOrRestartMessage": "Nota: {n} indica un campo obligatorio, {n} indica que los cambios requieren un reinicio.",
|
||||
"settingsSave": "Guardar",
|
||||
"ombiUserDefaults": "Valores predeterminados de usuario de Ombi",
|
||||
"ombiUserDefaultsDescription": "Cree un usuario Ombi y configúrelo, luego selecciónelo a continuación. Sus configuraciones / permisos se almacenarán y aplicarán a los nuevos usuarios de Ombi creados por jfa-go",
|
||||
"ombiUserDefaultsDescription": "Cree un usuario Ombi y configúrelo, luego selecciónelo a continuación. Sus configuraciones/permisos se almacenarán y aplicarán a los nuevos usuarios de Ombi creados por jfa-go cuando se seleccione este perfil.",
|
||||
"userProfiles": "Perfiles de usuario",
|
||||
"userProfilesDescription": "Los perfiles se aplican a los usuarios cuando crean una cuenta. Un perfil incluye los derechos de acceso a la biblioteca y el diseño de la pantalla de inicio.",
|
||||
"userProfilesIsDefault": "Defecto",
|
||||
"userProfilesIsDefault": "Por defecto",
|
||||
"userProfilesLibraries": "Bibliotecas",
|
||||
"addProfile": "Agregar perfil",
|
||||
"addProfile": "Agregar Perfil",
|
||||
"addProfileDescription": "Cree un usuario de Jellyfin y configúrelo, luego selecciónelo a continuación. Cuando este perfil se aplica a una invitación, se crearán nuevos usuarios con la configuración.",
|
||||
"addProfileNameOf": "Nombre de perfil",
|
||||
"addProfileStoreHomescreenLayout": "Diseño de la pantalla de inicio de la tienda",
|
||||
"addProfileStoreHomescreenLayout": "Guardar el diseño de la pantalla de inicio",
|
||||
"inviteNoUsersCreated": "¡Ninguno todavía!",
|
||||
"inviteUsersCreated": "Usuarios creados",
|
||||
"inviteNoProfile": "Sin perfil",
|
||||
@@ -93,18 +93,25 @@
|
||||
"notifyUserCreation": "Sobre la creación de usuarios",
|
||||
"conditionals": "Condicionales",
|
||||
"donate": "Donar",
|
||||
"add": "Añadir",
|
||||
"add": "Agregar",
|
||||
"templates": "Plantillas",
|
||||
"contactThrough": "Contactar a través de:",
|
||||
"select": "Seleccionar",
|
||||
"sendPIN": "Pídale al usuario que envíe el PIN a continuación al bot.",
|
||||
"searchDiscordUser": "Comienza a escribir el nombre de usuario de Discord para encontrar a la usuario.",
|
||||
"searchDiscordUser": "Comienza a escribir el nombre de usuario de Discord para encontrar al usuario.",
|
||||
"findDiscordUser": "Encontrar usuario de Discord",
|
||||
"linkMatrixDescription": "Ingrese el nombre de usuario y la contraseña para usar como bot. Una vez enviada, la aplicación se reiniciará",
|
||||
"linkMatrixDescription": "Ingrese el nombre de usuario y la contraseña del usuario para usar como bot. Una vez enviada, la aplicación se reiniciará.",
|
||||
"matrixHomeServer": "Dirección del servidor de inicio",
|
||||
"saveAsTemplate": "Guardar plantilla",
|
||||
"saveAsTemplate": "Guardar como plantilla",
|
||||
"deleteTemplate": "Eliminar plantilla",
|
||||
"templateEnterName": "Ingrese un nombre para guardar esta plantilla."
|
||||
"templateEnterName": "Ingrese un nombre para guardar esta plantilla.",
|
||||
"setExpiry": "Establecer vencimiento",
|
||||
"sendPWR": "Enviar restablecimiento de contraseña",
|
||||
"sendPWRSuccess": "Se envió el enlace para restablecer la contraseña.",
|
||||
"sendPWRSuccessManual": "Si el usuario no lo ha recibido, presione copiar para generar el enlace y enviárselo manualmente.",
|
||||
"sendPWRValidFor": "El enlace es válido por 30m.",
|
||||
"sendPWRManual": "El usuario {n} no tiene ningún método de contacto, presione copiar para generar el enlace para enviarle.",
|
||||
"ombiProfile": "Perfil de usuario de Ombi"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Se cambió la dirección de correo electrónico de {n}.",
|
||||
@@ -118,7 +125,7 @@
|
||||
"errorConnection": "No se pudo conectar a jfa-go.",
|
||||
"error401Unauthorized": "No autorizado. Intente actualizar la página.",
|
||||
"errorSettingsAppliedNoHomescreenLayout": "Se aplicó la configuración, pero es posible que no se haya aplicado el diseño de la pantalla de inicio.",
|
||||
"errorHomescreenAppliedNoSettings": "Se aplicó el diseño de la pantalla de inicio, pero es posible que no se haya aplicado la configuración.",
|
||||
"errorHomescreenAppliedNoSettings": "Se aplicó el diseño de la pantalla de inicio, pero es posible que la aplicación de la configuración haya fallado.",
|
||||
"errorSettingsFailed": "La aplicación falló.",
|
||||
"errorLoginBlank": "El nombre de usuario y/o la contraseña se dejaron en blanco.",
|
||||
"errorUnknown": "Error desconocido.",
|
||||
@@ -132,20 +139,22 @@
|
||||
"errorSaveSettings": "No se pudo guardar la configuración.",
|
||||
"errorLoadSettings": "No se pudo cargar la configuración.",
|
||||
"errorSetOmbiDefaults": "No se pudieron almacenar los valores predeterminados de ombi.",
|
||||
"errorLoadOmbiUsers": "No se pudieron cargar los usuarios de ombi.",
|
||||
"errorLoadOmbiUsers": "No se pudieron cargar los usuarios de Ombi.",
|
||||
"errorChangedEmailAddress": "No se pudo cambiar la dirección de correo electrónico de {n}.",
|
||||
"errorFailureCheckLogs": "Fallido (ver consola / registros)",
|
||||
"errorPartialFailureCheckLogs": "Fallo parcial (ver consola / registros)",
|
||||
"errorFailureCheckLogs": "Fallido (ver consola/registros)",
|
||||
"errorPartialFailureCheckLogs": "Fallo parcial (ver consola/registros)",
|
||||
"errorUserCreated": "No se pudo crear el usuario {n}.",
|
||||
"errorSendWelcomeEmail": "No se pudo enviar el mensaje de bienvenida (verifique la consola / registros)",
|
||||
"errorSendWelcomeEmail": "No se pudo enviar el mensaje de bienvenida (verifique la consola/registros)",
|
||||
"errorApplyUpdate": "No se pudo aplicar la actualización, intente manualmente.",
|
||||
"errorCheckUpdate": "No se pudo comprobar la actualización.",
|
||||
"updateAvailable": "Hay una nueva actualización disponible, verifique la configuración.",
|
||||
"noUpdatesAvailable": "No hay nuevas actualizaciones disponibles.",
|
||||
"updateAppliedRefresh": "Actualización aplicada, actualice.",
|
||||
"accountConnected": "Cuenta conectada.",
|
||||
"updateAppliedRefresh": "Actualización aplicada, por favor actualice.",
|
||||
"accountConnected": "Cuenta vinculada.",
|
||||
"savedAnnouncement": "Anuncio guardado.",
|
||||
"telegramVerified": "Cuenta de Telegram verificada."
|
||||
"telegramVerified": "Cuenta de Telegram verificada.",
|
||||
"setOmbiProfile": "Perfil de Ombi guardado.",
|
||||
"errorSetOmbiProfile": "No se pudo guardar el perfil de Ombi."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
@@ -157,8 +166,8 @@
|
||||
"plural": "Eliminar {n} usuarios"
|
||||
},
|
||||
"disableUsers": {
|
||||
"singular": "Deshabilitar {n} usuario",
|
||||
"plural": "Inhabilitar {n} usuarios"
|
||||
"singular": "Desactivar {n} usuario",
|
||||
"plural": "Desactivar {n} usuarios"
|
||||
},
|
||||
"reEnableUsers": {
|
||||
"singular": "Reactivar {n} usuario",
|
||||
@@ -173,32 +182,32 @@
|
||||
"plural": "Borrar usuarios"
|
||||
},
|
||||
"deletedUser": {
|
||||
"singular": "Usuario eliminado {n}.",
|
||||
"plural": "Usuarios eliminados {n}."
|
||||
"singular": "{n} usuario eliminado.",
|
||||
"plural": "{n} usuarios eliminados."
|
||||
},
|
||||
"disabledUser": {
|
||||
"singular": "Usuario deshabilitado {n}.",
|
||||
"plural": "Usuarios deshabilitados {n}."
|
||||
"singular": "{n} usuario desactivado.",
|
||||
"plural": "{n} usuarios desactivados."
|
||||
},
|
||||
"enabledUser": {
|
||||
"singular": "Usuario {n} habilitado.",
|
||||
"plural": "Usuarios {n} habilitados."
|
||||
"singular": "{n} usuario activado.",
|
||||
"plural": "{n} usuarios activados."
|
||||
},
|
||||
"announceTo": {
|
||||
"singular": "Anunciar al usuario {n}",
|
||||
"plural": "Anunciar a los usuarios {n}"
|
||||
"singular": "Anunciar a {n} usuario",
|
||||
"plural": "Anunciar a {n} usuarios"
|
||||
},
|
||||
"appliedSettings": {
|
||||
"singular": "Se aplicó la configuración al usuario {n}.",
|
||||
"plural": "Se aplicó la configuración a los usuarios {n}."
|
||||
"singular": "Se aplicó la configuración a {n} usuario.",
|
||||
"plural": "Se aplicó la configuración a {n} usuarios."
|
||||
},
|
||||
"extendExpiry": {
|
||||
"singular": "Extender la expiración para el usuario {n}",
|
||||
"plural": "Extender la expiración para los usuarios {n}"
|
||||
"singular": "Extender la expiración para {n} usuario",
|
||||
"plural": "Extender la expiración para {n} usuarios"
|
||||
},
|
||||
"extendedExpiry": {
|
||||
"singular": "Caducidad extendida para el usuario {n}.",
|
||||
"plural": "Caducidad extendida para los usuarios {n}."
|
||||
"singular": "Caducidad extendida para {n} usuario.",
|
||||
"plural": "Caducidad extendida para {n} usuarios."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"settingsRequiredOrRestartMessage": "Remarque : {n} indique un champ obligatoire, {n} indique que les modifications nécessitent un redémarrage.",
|
||||
"settingsSave": "Sauver",
|
||||
"ombiUserDefaults": "Paramètres par défaut de l'utilisateur Ombi",
|
||||
"ombiUserDefaultsDescription": "Créez un utilisateur Ombi et configurez-le, puis sélectionnez-le ci-dessous. Ses paramètres/autorisations seront stockés et appliqués aux nouveaux utilisateurs Ombi créés par jfa-go",
|
||||
"ombiUserDefaultsDescription": "Créez un utilisateur Ombi et configurez-le, puis sélectionnez-le ci-dessous. Ses paramètres/autorisations seront stockés et appliqués aux nouveaux utilisateurs Ombi créés par jfa-go lorsque ce profil est sélectionné.",
|
||||
"userProfiles": "Profils d'utilisateurs",
|
||||
"userProfilesDescription": "Les profils sont appliqués aux utilisateurs lorsqu'ils créent un compte. Un profil inclut les droits d'accès à la bibliothèque et la disposition de l'écran d'accueil.",
|
||||
"userProfilesIsDefault": "Défaut",
|
||||
@@ -101,7 +101,18 @@
|
||||
"findDiscordUser": "Trouver l'utilisateur Discord",
|
||||
"linkMatrixDescription": "Entrez le nom d'utilisateur et le mot de passe de l'utilisateur pour l’utilisateur comme bot. Une fois soumis, l'application va redémarrer.",
|
||||
"searchDiscordUser": "Commencez à taper le nom d'utilisateur Discord pour trouver l'utilisateur.",
|
||||
"matrixHomeServer": "Adresse du serveur"
|
||||
"matrixHomeServer": "Adresse du serveur domestique",
|
||||
"saveAsTemplate": "Sauvegarder comme modèle",
|
||||
"templateEnterName": "Entrez un nom pour sauvegarder ce modèle.",
|
||||
"deleteTemplate": "Supprimer le modèle",
|
||||
"templates": "Modèles",
|
||||
"setExpiry": "Paramétrer l'expiration",
|
||||
"sendPWRSuccess": "Réinitialisation du mot de passe envoyée.",
|
||||
"sendPWR": "Envoyer une réinitialisation du mot de passe",
|
||||
"sendPWRValidFor": "Ce lien est valable 30min.",
|
||||
"sendPWRManual": "L'utilisateur {n} n'a pas indiqué de méthode de contact, appuyez sur copier pour recevoir un lien à lui envoyer.",
|
||||
"sendPWRSuccessManual": "Si l'utilisateur ne l'a pas reçu, appuyez sur copier pour recevoir un lien à lui envoyer manuellement.",
|
||||
"ombiProfile": "Profil d'utilisateur Ombi"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Adresse e-mail modifiée de {n}.",
|
||||
@@ -141,7 +152,10 @@
|
||||
"noUpdatesAvailable": "Aucune nouvelle mise à jour disponible.",
|
||||
"telegramVerified": "Compte Telegram vérifié.",
|
||||
"updateAppliedRefresh": "Mise à jour appliquée, veuillez actualiser.",
|
||||
"accountConnected": "Compte connecté."
|
||||
"accountConnected": "Compte connecté.",
|
||||
"savedAnnouncement": "Annonce enregistrée.",
|
||||
"setOmbiProfile": "Profil ombi enregistré.",
|
||||
"errorSetOmbiProfile": "Echec de la sauvegarde du profil ombi."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"settingsRequiredOrRestartMessage": "Opmerking: {n} is een verplicht veld, {n} geeft aan dat na wijzigen een herstart nodig is.",
|
||||
"settingsSave": "Opslaan",
|
||||
"ombiUserDefaults": "Ombi gebruiker standaardinstellingen",
|
||||
"ombiUserDefaultsDescription": "Maak een Ombi gebruiker aan met de gewenste instellingen, en selecteer deze hieronder. Deze instellingen/rechten worden opgeslagen en toegepast voor nieuwe Ombi gebruikers die jfa-go aanmaakt",
|
||||
"ombiUserDefaultsDescription": "Maak een Ombi gebruiker aan met de gewenste instellingen, en selecteer deze hieronder. Deze instellingen/rechten worden opgeslagen en toegepast voor nieuwe Ombi gebruikers die jfa-go aanmaakt als dit profiel is geselecteerd.",
|
||||
"userProfiles": "Gebruikersprofielen",
|
||||
"userProfilesDescription": "Profielen worden toegepast op gebruikers wanneer ze een account aanmaken. Een profiel bevat rechten voor bibliotheken en indeling van de startpagina.",
|
||||
"userProfilesIsDefault": "Standaard",
|
||||
@@ -104,7 +104,14 @@
|
||||
"templates": "Sjablonen",
|
||||
"templateEnterName": "Voer een naam in om dit sjabloon op te slaan.",
|
||||
"saveAsTemplate": "Sla op als sjabloon",
|
||||
"deleteTemplate": "Verwijder sjabloon"
|
||||
"deleteTemplate": "Verwijder sjabloon",
|
||||
"setExpiry": "Stel verloop in",
|
||||
"sendPWRManual": "Gebruiker {n} heeft geen contactmogelijkheden, druk op kopiëren om een link te krijgen die je kunt sturen.",
|
||||
"sendPWRSuccessManual": "Als de gebruiker hem niet heeft ontvangen, druk dan op kopiëren om een link te krijgen die je handmatig kunt sturen.",
|
||||
"sendPWR": "Verstuur wachtwoordreset",
|
||||
"sendPWRSuccess": "Wachtwoordreset-link verstuurd.",
|
||||
"sendPWRValidFor": "De link is 30m geldig.",
|
||||
"ombiProfile": "Ombi gebruikersprofiel"
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "E-mailadres van {n} gewijzigd.",
|
||||
@@ -145,7 +152,9 @@
|
||||
"telegramVerified": "Telegram-account goedgekeurd.",
|
||||
"updateAppliedRefresh": "Update toegepast, ververs alsjeblieft.",
|
||||
"accountConnected": "Account gekoppeld.",
|
||||
"savedAnnouncement": "Aankondiging opgeslagen."
|
||||
"savedAnnouncement": "Aankondiging opgeslagen.",
|
||||
"setOmbiProfile": "Opgeslagen ombi-profiel.",
|
||||
"errorSetOmbiProfile": "Opslaan van ombi-profiel mislukt."
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
|
||||
210
lang/admin/vi-vn.json
Normal file
210
lang/admin/vi-vn.json
Normal file
@@ -0,0 +1,210 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Vietnamese (VN)"
|
||||
},
|
||||
"strings": {
|
||||
"invites": "Lời mời",
|
||||
"accounts": "Tài khoản",
|
||||
"settings": "Cài đặt",
|
||||
"inviteMonths": "Tháng",
|
||||
"inviteDays": "Ngày",
|
||||
"inviteHours": "Giờ",
|
||||
"inviteMinutes": "Phút",
|
||||
"inviteNumberOfUses": "Số lần sử dụng",
|
||||
"inviteDuration": "Thời hạn hiệu lực",
|
||||
"warning": "Cảnh báo",
|
||||
"inviteInfiniteUsesWarning": "các lời mời không giới hạn số lần sử dụng có thể bị lạm dụng",
|
||||
"inviteSendToEmail": "Gửi tới",
|
||||
"login": "Đăng nhập",
|
||||
"logout": "Đăng xuất",
|
||||
"create": "Tạo mới",
|
||||
"apply": "Áp dụng",
|
||||
"delete": "Xóa",
|
||||
"add": "Thêm",
|
||||
"select": "Chọn",
|
||||
"name": "Tên",
|
||||
"date": "Ngày",
|
||||
"enabled": "Mở",
|
||||
"disabled": "Tắt",
|
||||
"reEnable": "Mở lại",
|
||||
"setExpiry": "Đặt hết hạn",
|
||||
"disable": "Tắt",
|
||||
"admin": "Admin",
|
||||
"updates": "Cập nhật",
|
||||
"update": "Cập nhật",
|
||||
"download": "Tải về",
|
||||
"search": "Tìm kiếm",
|
||||
"advancedSettings": "Cài đặt Cấp cao",
|
||||
"lastActiveTime": "Lần cuối Hoạt động",
|
||||
"from": "Từ",
|
||||
"user": "Người dùng",
|
||||
"expiry": "Hết hạn",
|
||||
"userExpiry": "Hết hạn Người dùng",
|
||||
"userExpiryDescription": "Sau một khoảng thời gian nhất định sau khi mỗi đăng ký, jfa-go sẽ xóa/vô hiệu hóa tài khoản. Bạn có thể chỉnh sửa chế độ này trong cài đặt.",
|
||||
"aboutProgram": "Thông tin",
|
||||
"version": "Phiên bản",
|
||||
"commitNoun": "Gửi",
|
||||
"newUser": "Người dùng mới",
|
||||
"profile": "Hồ sơ",
|
||||
"unknown": "Không xác định",
|
||||
"label": "Nhãn",
|
||||
"announce": "Thông báo",
|
||||
"templates": "Mẫu",
|
||||
"subject": "Chủ đề",
|
||||
"message": "Tin nhắn",
|
||||
"variables": "Biến",
|
||||
"conditionals": "Điều kiện",
|
||||
"preview": "Xem trước",
|
||||
"reset": "Đặt lại",
|
||||
"edit": "Chỉnh sửa",
|
||||
"donate": "Đóng góp",
|
||||
"sendPWR": "Gửi Đặt lại Mật khẩu",
|
||||
"contactThrough": "Liên lạc qua:",
|
||||
"extendExpiry": "Gia hạn",
|
||||
"sendPWRManual": "Người dùng {n} không có phương thức liên lạc, nhấn chép để lấy đường link để gửi cho họ.",
|
||||
"sendPWRSuccess": "Link đặt lại mật khẩu đã được gửi đi.",
|
||||
"sendPWRSuccessManual": "Nếu người dùng chưa nhận được, nhấn chép để lấy đường link có thể gửi đến họ.",
|
||||
"sendPWRValidFor": "Link có hiệu lực trong vòng 30 phút.",
|
||||
"customizeMessages": "Tùy chỉnh Tin nhắn",
|
||||
"customizeMessagesDescription": "Nếu bạn không muốn sử dụng mẫu tin nhắn của jfa-go, bạn có thể tự tạo mẫu của mình bằng Markdown.",
|
||||
"markdownSupported": "Có hỗ trợ Markdown.",
|
||||
"modifySettings": "Chỉnh sửa Cài đặt",
|
||||
"modifySettingsDescription": "Áp dụng các cài đặt từ một mẫu có sẵn, hoặc lấy trực tiếp từ một người dùng.",
|
||||
"applyHomescreenLayout": "Áp dụng bố cục trang chủ",
|
||||
"sendDeleteNotificationEmail": "Gửi tin nhắn thông báo",
|
||||
"sendDeleteNotifiationExample": "Tài khoản của bạn đã bị xóa.",
|
||||
"settingsRestart": "Khởi động lại",
|
||||
"settingsRestarting": "Đang khởi động lại…",
|
||||
"settingsRestartRequired": "Cần khởi động lại",
|
||||
"settingsRestartRequiredDescription": "Một số cài đặt bạn đã thay đổi cần phải khởi động lại để có hiệu lực. Khởi động lại ngay hay để sau?",
|
||||
"settingsApplyRestartLater": "Áp dụng, khởi động lại sau",
|
||||
"settingsApplyRestartNow": "Áp dụng & khởi động lại",
|
||||
"settingsApplied": "Cài đặt đã được áp dụng.",
|
||||
"settingsRefreshPage": "Làm mới trang trong một vài giây nữa.",
|
||||
"settingsRequiredOrRestartMessage": "Lưu ý: {n} là những cài đặt cần thiết, {n} là những cài đặt cần khởi động lại nếu chúng được thay đổi.",
|
||||
"settingsSave": "Lưu",
|
||||
"ombiProfile": "Tài khoản người dùng Ombi",
|
||||
"ombiUserDefaultsDescription": "Tạo và cài đặt một tài khoản người dùng Ombi, sau đó chọn nó bên dưới. Các cài đặt/quyền sẽ được lưu lại và được áp dụng cho các tài khoản Ombi được tạo bởi jfa-go khi tài khoản mẫy này được chọn.",
|
||||
"userProfiles": "Thông tin Người dùng",
|
||||
"userProfilesDescription": "Mẫu tài khoản được áp dụng cho người dùng khi họ tạo tài khoản. Mẫu tài khoản bao gồm các quyền truy cập thư viện và bốc cục trang chủ.",
|
||||
"userProfilesIsDefault": "Mặc định",
|
||||
"userProfilesLibraries": "Thư viện",
|
||||
"addProfile": "Thêm Tài khoản Mẫu",
|
||||
"addProfileDescription": "Tạo một tài khoản Jellyfin và cấu hình nó, rồi chọn nó bên dưới. Khi tài khoản mẫu này được áp dụng trong lời mời, các người dùng mới sẽ được tạo với các cài đặt tương tự.",
|
||||
"addProfileNameOf": "Tên Tài khoản mẫu",
|
||||
"addProfileStoreHomescreenLayout": "Lưu bố cục trang chủ",
|
||||
"inviteNoUsersCreated": "Chưa có!",
|
||||
"inviteUsersCreated": "Người dùng đã tạo",
|
||||
"inviteNoProfile": "Không có Tài khoản mẫu",
|
||||
"inviteDateCreated": "Tạo",
|
||||
"inviteRemainingUses": "Số lần sử dụng còn lại",
|
||||
"inviteNoInvites": "Không có",
|
||||
"inviteExpiresInTime": "Hết hạn trong {n}",
|
||||
"notifyEvent": "Thông báo khi:",
|
||||
"notifyInviteExpiry": "Khi hết hạn",
|
||||
"notifyUserCreation": "Khi có người dùng được tạo",
|
||||
"sendPIN": "Yêu cầu người dùng gửi mã PIN bên dưới cho bot.",
|
||||
"searchDiscordUser": "Gõ tên người dùng Discord để tìm người dùng.",
|
||||
"findDiscordUser": "Tìm người dùng Discord",
|
||||
"linkMatrixDescription": "Nhập tên đăng nhập và mật khẩu của người dùng được sử dụng làm bot. Khi hoàn thành, ứng dụng sẽ khởi động lại.",
|
||||
"matrixHomeServer": "Địa chỉ máy chủ",
|
||||
"saveAsTemplate": "Lưu thành mẫu",
|
||||
"deleteTemplate": "Xóa mẫu",
|
||||
"templateEnterName": "Nhập tên mẫu để lưu mẫu này."
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Đã đổi địa chỉ email của {n}.",
|
||||
"userCreated": "Người dùng {n} đã được tạo.",
|
||||
"createProfile": "Đã tạo tài khoản mẫu {n}.",
|
||||
"saveSettings": "Cài đặt đã được lưu",
|
||||
"saveEmail": "Email đã được lưu.",
|
||||
"sentAnnouncement": "Thông báo đã được gửi.",
|
||||
"savedAnnouncement": "Thông báo đã được lưu.",
|
||||
"setOmbiProfile": "Mẫu tài khoản Ombi đã được lưu trữ.",
|
||||
"updateApplied": "Cập nhật mới đã được áp dụng, vui lòng khởi động lại.",
|
||||
"updateAppliedRefresh": "Cập nhật mới đã được áp dụng, vui lòng làm mới lại trang.",
|
||||
"telegramVerified": "Tài khoản Telegram đã được xác thực.",
|
||||
"accountConnected": "Tài khoản đã được kết nối.",
|
||||
"errorConnection": "Không thể kết nối với jfa-go.",
|
||||
"error401Unauthorized": "Không được phép. Hãy thử làm mới trang.",
|
||||
"errorSettingsAppliedNoHomescreenLayout": "Cài đặt đã được áp dụng, nhưng việc áp dụng bố cục màn hình chính có thể không thành công.",
|
||||
"errorHomescreenAppliedNoSettings": "",
|
||||
"errorSettingsFailed": "",
|
||||
"errorLoginBlank": "",
|
||||
"errorUnknown": "",
|
||||
"errorSaveEmail": "",
|
||||
"errorBlankFields": "",
|
||||
"errorDeleteProfile": "",
|
||||
"errorLoadProfiles": "",
|
||||
"errorCreateProfile": "",
|
||||
"errorSetDefaultProfile": "",
|
||||
"errorLoadUsers": "",
|
||||
"errorSaveSettings": "",
|
||||
"errorLoadSettings": "",
|
||||
"errorSetOmbiProfile": "",
|
||||
"errorLoadOmbiUsers": "",
|
||||
"errorChangedEmailAddress": "",
|
||||
"errorFailureCheckLogs": "",
|
||||
"errorPartialFailureCheckLogs": "",
|
||||
"errorUserCreated": "",
|
||||
"errorSendWelcomeEmail": "",
|
||||
"errorApplyUpdate": "",
|
||||
"errorCheckUpdate": "",
|
||||
"updateAvailable": "",
|
||||
"noUpdatesAvailable": ""
|
||||
},
|
||||
"quantityStrings": {
|
||||
"modifySettingsFor": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"deleteNUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"disableUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"reEnableUsers": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"addUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"deleteUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"deletedUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"disabledUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"enabledUser": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"announceTo": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"appliedSettings": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"extendExpiry": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
},
|
||||
"extendedExpiry": {
|
||||
"singular": "",
|
||||
"plural": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
"submit": "Indsend",
|
||||
"send": "Send",
|
||||
"success": "Succes",
|
||||
"continue": "Fortsæt",
|
||||
"error": "Fejl",
|
||||
"copy": "Kopiér",
|
||||
"copied": "Kopiret",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"emailAddress": "E-Mail-Adresse",
|
||||
"submit": "Absenden",
|
||||
"success": "Erfolgreich",
|
||||
"continue": "Weiter",
|
||||
"error": "Fehler",
|
||||
"copy": "Kopieren",
|
||||
"theme": "Thema",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"name": "Όνομα",
|
||||
"submit": "Καταχώρηση",
|
||||
"success": "Επιτυχία",
|
||||
"continue": "Συνέχεια",
|
||||
"error": "Σφάλμα",
|
||||
"copy": "Αντιγραφή",
|
||||
"theme": "Θέμα",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"submit": "Submit",
|
||||
"send": "Send",
|
||||
"success": "Success",
|
||||
"continue": "Continue",
|
||||
"error": "Error",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Español(ES)"
|
||||
"name": "Español (ES)"
|
||||
},
|
||||
"strings": {
|
||||
"username": "Nombre de usuario",
|
||||
@@ -9,15 +9,19 @@
|
||||
"name": "Nombre",
|
||||
"submit": "Enviar",
|
||||
"success": "Éxito",
|
||||
"continue": "Continuar",
|
||||
"error": "Error",
|
||||
"copy": "Copiar",
|
||||
"copied": "Copiado",
|
||||
"time24h": "24 horas",
|
||||
"time12h": "12 horas",
|
||||
"time24h": "Formato de 24 horas",
|
||||
"time12h": "Formato de 12 horas",
|
||||
"theme": "Tema",
|
||||
"send": "Enviar",
|
||||
"contactDiscord": "Contacta a través de Discord",
|
||||
"contactEmail": "Contacta a través de email",
|
||||
"contactTelegram": "Contacta a través de Telegram"
|
||||
"contactDiscord": "Contactar por Discord",
|
||||
"contactEmail": "Contactar por correo electrónico",
|
||||
"contactTelegram": "Contactar por Telegram",
|
||||
"linkMatrix": "Enlace Matrix",
|
||||
"linkDiscord": "Enlace Discord",
|
||||
"linkTelegram": "Enlace Telegram"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"submit": "تایید",
|
||||
"send": "ارسال",
|
||||
"success": "موفقیت",
|
||||
"continue": "ادامه دادن",
|
||||
"error": "خطا",
|
||||
"copy": "کپی",
|
||||
"copied": "کپی شد",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"emailAddress": "Addresse Email",
|
||||
"submit": "Soumettre",
|
||||
"success": "Succès",
|
||||
"continue": "Continuer",
|
||||
"error": "Erreur",
|
||||
"copy": "Copier",
|
||||
"time24h": "Temps 24h",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"name": "Nama",
|
||||
"submit": "Submit",
|
||||
"success": "Sukses",
|
||||
"continue": "Lanjut",
|
||||
"error": "Error",
|
||||
"copy": "Salin",
|
||||
"time24h": "Waktu 24 jam",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"emailAddress": "E-mailadres",
|
||||
"submit": "Verstuur",
|
||||
"success": "Succes",
|
||||
"continue": "Doorgaan",
|
||||
"error": "Fout",
|
||||
"copy": "Kopiëer",
|
||||
"theme": "Thema",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"emailAddress": "Endereço de Email",
|
||||
"submit": "Enviar",
|
||||
"success": "Sucesso",
|
||||
"continue": "Continuar",
|
||||
"error": "Erro",
|
||||
"copy": "Copiar",
|
||||
"theme": "Tema",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"name": "Namn",
|
||||
"submit": "Skicka",
|
||||
"success": "Lyckades",
|
||||
"continue": "Fortsätt",
|
||||
"error": "Fel",
|
||||
"copy": "Kopiera",
|
||||
"time24h": "24 timmarsklocka",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"submit": "提交",
|
||||
"send": "发送",
|
||||
"success": "成功",
|
||||
"continue": "继续",
|
||||
"error": "错误",
|
||||
"copy": "复制",
|
||||
"copied": "已复制",
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
"title": "Notice: User created",
|
||||
"aUserWasCreated": "A user was created using code {code}.",
|
||||
"time": "Time",
|
||||
"notificationNotice": "Note: Notification emails can be toggled on the admin dashboard."
|
||||
"notificationNotice": "Note: Notification messages can be toggled on the admin dashboard."
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"name": "Invite expiry",
|
||||
"title": "Notice: Invite expired",
|
||||
"inviteExpired": "Invite expired.",
|
||||
"expiredAt": "Code {code} expired at {time}.",
|
||||
"notificationNotice": "Note: Notification emails can be toggled on the admin dashboard."
|
||||
"notificationNotice": "Note: Notification messages can be toggled on the admin dashboard."
|
||||
},
|
||||
"passwordReset": {
|
||||
"name": "Password reset",
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Español(ES)"
|
||||
"name": "Español (ES)"
|
||||
},
|
||||
"strings": {
|
||||
"ifItWasNotYou": "Si no fue usted, ignore este correo electrónico.",
|
||||
"ifItWasNotYou": "Si no fue usted, ignórelo.",
|
||||
"helloUser": "Hola {username},",
|
||||
"reason": "Razón"
|
||||
},
|
||||
"userCreated": {
|
||||
"name": "Creación de usuarios",
|
||||
"title": "Noticia: Usuario creado",
|
||||
"title": "Aviso: Usuario creado",
|
||||
"aUserWasCreated": "Se creó un usuario con el código {code}.",
|
||||
"time": "Hora",
|
||||
"notificationNotice": "Nota: los correos electrónicos de notificación se pueden alternar en el panel de administración."
|
||||
"notificationNotice": "Nota: Los mensajes de notificación se pueden alternar en el panel de administración."
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"name": "Vencimiento de la invitación",
|
||||
"title": "Aviso: Invitación caducada",
|
||||
"inviteExpired": "Invitación caducada.",
|
||||
"expiredAt": "El código {code} venció a las {time}.",
|
||||
"notificationNotice": "Nota: Los correos electrónicos de notificación se pueden alternar en el panel de administración."
|
||||
"notificationNotice": "Nota: Los mensajes de notificación se pueden alternar en el panel de administración."
|
||||
},
|
||||
"passwordReset": {
|
||||
"name": "Restablecimiento de contraseña",
|
||||
"title": "Solicitud de restablecimiento de contraseña - Jellyfin",
|
||||
"someoneHasRequestedReset": "Alguien ha solicitado recientemente un restablecimiento de contraseña en Jellyfin.",
|
||||
"ifItWasYou": "Si era usted, ingrese el pin a continuación en el mensaje.",
|
||||
"ifItWasYou": "Si era usted, ingrese el PIN a continuación en el mensaje.",
|
||||
"ifItWasYouLink": "Si fue usted, haga clic en el enlace de abajo.",
|
||||
"codeExpiry": "El código vencerá el {date}, a las {time} UTC, que está en {expiresInMinutes}.",
|
||||
"codeExpiry": "El código vencerá el {date}, a las {time} UTC, que es en {expiresInMinutes}.",
|
||||
"pin": "PIN"
|
||||
},
|
||||
"userDeleted": {
|
||||
@@ -36,26 +36,26 @@
|
||||
"yourAccountWasDeleted": "Su cuenta de Jellyfin fue eliminada."
|
||||
},
|
||||
"userDisabled": {
|
||||
"name": "Usuario deshabilitado",
|
||||
"title": "Su cuenta ha sido deshabilitada - Jellyfin",
|
||||
"yourAccountWasDisabled": "Su cuenta fue inhabilitada."
|
||||
"name": "Usuario desactivado",
|
||||
"title": "Su cuenta ha sido desactivada - Jellyfin",
|
||||
"yourAccountWasDisabled": "Su cuenta fue desactivada."
|
||||
},
|
||||
"userEnabled": {
|
||||
"name": "Usuario habilitado",
|
||||
"name": "Usuario activado",
|
||||
"title": "Su cuenta ha sido reactivada - Jellyfin",
|
||||
"yourAccountWasEnabled": "Su cuenta se volvió a habilitar."
|
||||
"yourAccountWasEnabled": "Su cuenta fue reactivada."
|
||||
},
|
||||
"inviteEmail": {
|
||||
"name": "Correo electrónico",
|
||||
"title": "Invitar - Jellyfin",
|
||||
"name": "Invitar por correo electrónico",
|
||||
"title": "Invitación - Jellyfin",
|
||||
"hello": "Hola",
|
||||
"youHaveBeenInvited": "Has sido invitado a Jellyfin.",
|
||||
"toJoin": "Para unirse, siga el enlace a continuación.",
|
||||
"inviteExpiry": "Esta invitación vencerá el {date} a las {time}, que está en {expiresInMinutes}, así que regístrese cuanto antes.",
|
||||
"linkButton": "Configurar tu cuenta"
|
||||
"inviteExpiry": "Esta invitación vencerá el {date} a las {time}, que es en {expiresInMinutes}, así que regístrese cuanto antes.",
|
||||
"linkButton": "Configura tu cuenta"
|
||||
},
|
||||
"welcomeEmail": {
|
||||
"name": "Correo de bienvenida",
|
||||
"name": "Bienvenido",
|
||||
"title": "Bienvenido a Jellyfin",
|
||||
"welcome": "¡Bienvenido a Jellyfin!",
|
||||
"youCanLoginWith": "Puede iniciar sesión con los detalles a continuación",
|
||||
@@ -66,12 +66,12 @@
|
||||
"name": "Email de confirmación",
|
||||
"title": "Confirma tu correo electrónico - Jellyfin",
|
||||
"clickBelow": "Haga clic en el enlace de abajo para confirmar su dirección de correo electrónico y comenzar a usar Jellyfin.",
|
||||
"confirmEmail": "Confirmar correo electrónico"
|
||||
"confirmEmail": "Confirma el correo electrónico"
|
||||
},
|
||||
"userExpired": {
|
||||
"name": "Caducidad del usuario",
|
||||
"title": "Tu cuenta ha caducado - Jellyfin",
|
||||
"yourAccountHasExpired": "Tu cuenta ha expirado.",
|
||||
"contactTheAdmin": "Comuníquese con el administrador para obtener más información."
|
||||
"contactTheAdmin": "Póngase en contacto con el administrador para obtener más información."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
"title": "Notification : Utilisateur créé",
|
||||
"aUserWasCreated": "Un utilisateur a été créé avec ce code {code}.",
|
||||
"time": "Date",
|
||||
"notificationNotice": "Note : Les emails de notification peuvent être activés sur le tableau de bord administrateur.",
|
||||
"notificationNotice": "Note : Les messages de notification peuvent être activés sur le tableau de bord administrateur.",
|
||||
"name": "Création d'utilisateur"
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"title": "Notification : Invitation expirée",
|
||||
"inviteExpired": "Invitation expirée.",
|
||||
"expiredAt": "Le code {code} a expiré à {time}.",
|
||||
"notificationNotice": "Note : Les emails de notification peuvent être activés sur le tableau de bord administrateur.",
|
||||
"notificationNotice": "Note : Les messages de notification peuvent être activés sur le tableau de bord administrateur.",
|
||||
"name": "Expiration de l'invitation"
|
||||
},
|
||||
"passwordReset": {
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
"title": "Melding: Gebruiker aangemaakt",
|
||||
"aUserWasCreated": "Er is een gebruiker aangemaakt door gebruik te maken van code {code}.",
|
||||
"time": "Tijdstip",
|
||||
"notificationNotice": "Opmerking: Meldingse-mails kunnen worden aan- of uitgezet via het beheerdersdashboard.",
|
||||
"notificationNotice": "Opmerking: Meldingen kunnen worden aan- of uitgezet via het beheerders-dashboard.",
|
||||
"name": "Gebruiker aangemaakt"
|
||||
},
|
||||
"inviteExpiry": {
|
||||
"title": "Melding: Uitnodiging verlopen",
|
||||
"inviteExpired": "Uitnodiging verlopen.",
|
||||
"expiredAt": "Code {code} is verlopen op {time}.",
|
||||
"notificationNotice": "Opmerking: Meldingse-mails kunnen worden aan- of uitgezet via het beheerdersdashboard.",
|
||||
"notificationNotice": "Opmerking: Meldingen kunnen worden aan- of uitgezet via het beheerders-dashboard.",
|
||||
"name": "Uitnodiging verlopen"
|
||||
},
|
||||
"passwordReset": {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"createAccountButton": "Opret Konto",
|
||||
"passwordRequirementsHeader": "Adgangskodekrav",
|
||||
"successHeader": "Succes!",
|
||||
"successContinueButton": "Fortsæt",
|
||||
"confirmationRequired": "E-mail bekræftelse er påkrævet",
|
||||
"confirmationRequiredMessage": "Tjek venligst din e-mail indbakke for at verificere din adresse.",
|
||||
"yourAccountIsValidUntil": "Din konto er gyldig indtil {date}.",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"createAccountButton": "Konto erstellen",
|
||||
"passwordRequirementsHeader": "Passwortanforderungen",
|
||||
"successHeader": "Erfolgreich!",
|
||||
"successContinueButton": "Weiter",
|
||||
"confirmationRequired": "E-Mail-Bestätigung erforderlich",
|
||||
"confirmationRequiredMessage": "Bitte überprüfe dein Posteingang und bestätige deine E-Mail-Adresse.",
|
||||
"yourAccountIsValidUntil": "Dein Konto wird bis zum {date} gültig sein.",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"createAccountButton": "Δημιουργία Λογαρισμού",
|
||||
"passwordRequirementsHeader": "Απαιτήσεις Κωδικού",
|
||||
"successHeader": "Επιτυχία!",
|
||||
"successContinueButton": "Συνέχεια",
|
||||
"confirmationRequired": "Απαιτείται επιβεβαίωση Email",
|
||||
"confirmationRequiredMessage": "Παρακαλώ ελέγξτε το email σας για να επιβεβαιώσετε την διεύθυνση σας .",
|
||||
"yourAccountIsValidUntil": "Ο λογαριασμός σου θα ισχύει μέχρι {date}."
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"createAccountButton": "Create Account",
|
||||
"passwordRequirementsHeader": "Password Requirements",
|
||||
"successHeader": "Success!",
|
||||
"successContinueButton": "Continue",
|
||||
"confirmationRequired": "Email confirmation required",
|
||||
"confirmationRequiredMessage": "Please check your email inbox to verify your address.",
|
||||
"yourAccountIsValidUntil": "Your account will be valid until {date}.",
|
||||
@@ -30,6 +29,7 @@
|
||||
"errorMatrixVerification": "Matrix verification required.",
|
||||
"errorInvalidPIN": "PIN is invalid.",
|
||||
"errorUnknown": "Unknown error.",
|
||||
"errorNoEmail": "Email required.",
|
||||
"verified": "Account verified."
|
||||
},
|
||||
"validationStrings": {
|
||||
|
||||
@@ -9,19 +9,28 @@
|
||||
"emailAddress": "Correo electrónico",
|
||||
"username": "Nombre de usuario",
|
||||
"password": "Contraseña",
|
||||
"reEnterPassword": "Rescriba su contraseña",
|
||||
"reEnterPasswordInvalid": "Las contraseñas no son coincidentes.",
|
||||
"reEnterPassword": "Confirme la contraseña",
|
||||
"reEnterPasswordInvalid": "Las contraseñas no son similares.",
|
||||
"createAccountButton": "Crear una cuenta",
|
||||
"passwordRequirementsHeader": "Requisitos de contraseña",
|
||||
"successHeader": "¡Éxito!",
|
||||
"successContinueButton": "Continuar",
|
||||
"confirmationRequired": "Se requiere confirmación por correo electrónico",
|
||||
"confirmationRequiredMessage": "Revise la bandeja de entrada de su correo electrónico para verificar su dirección.",
|
||||
"yourAccountIsValidUntil": "Su cuenta será válida hasta el {date}."
|
||||
"confirmationRequiredMessage": "Por favor, revise la bandeja de entrada de su correo electrónico para verificar su dirección.",
|
||||
"yourAccountIsValidUntil": "Su cuenta será válida hasta el {date}.",
|
||||
"sendPINDiscord": "Escribe {command} en {server_channel} en Discord, luego envía el PIN a continuación a través de DM al bot.",
|
||||
"sendPIN": "Envíe el PIN a continuación al bot, luego regrese aquí para vincular su cuenta.",
|
||||
"matrixEnterUser": "Ingrese su ID de usuario, presione enviar y se le enviará un PIN. Ingrese aquí para continuar."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "El usuario ya existe.",
|
||||
"errorInvalidCode": "Código de invitación no es válido."
|
||||
"errorInvalidCode": "Código de invitación no válido.",
|
||||
"errorDiscordVerification": "Se requiere verificación de Discord.",
|
||||
"errorUnknown": "Error desconocido.",
|
||||
"verified": "Cuenta verificada.",
|
||||
"errorTelegramVerification": "Se requiere verificación de Telegram.",
|
||||
"errorInvalidPIN": "PIN inválido.",
|
||||
"errorMatrixVerification": "Se requiere verificación de Matrix.",
|
||||
"errorNoEmail": "Correo electrónico requerido."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
@@ -29,11 +38,11 @@
|
||||
"plural": "Debe tener al menos {n} caracteres"
|
||||
},
|
||||
"uppercase": {
|
||||
"singular": "Debe tener al menos {n} caracteres en mayúscula",
|
||||
"singular": "Debe tener al menos {n} carácter en mayúscula",
|
||||
"plural": "Debe tener al menos {n} caracteres en mayúscula"
|
||||
},
|
||||
"lowercase": {
|
||||
"singular": "Debe tener al menos {n} caracteres en minúscula",
|
||||
"singular": "Debe tener al menos {n} carácter en minúscula",
|
||||
"plural": "Debe tener al menos {n} caracteres en minúscula"
|
||||
},
|
||||
"number": {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"createAccountButton": "ساخت حساب کاربری",
|
||||
"passwordRequirementsHeader": "کلمه عبور لازم است",
|
||||
"successHeader": "موفقیت!",
|
||||
"successContinueButton": "ادامه دادن",
|
||||
"confirmationRequired": "تایید ایمیل لازم است",
|
||||
"confirmationRequiredMessage": "لطفاً برای تأیید آدرس خود ، صندوق پستی ایمیل خود را بررسی کنید.",
|
||||
"yourAccountIsValidUntil": "حساب شما تا {date} معتبر خواهد بود.",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"pageTitle": "Créer un compte Jellyfin",
|
||||
"createAccountHeader": "Création du compte",
|
||||
"accountDetails": "Détails",
|
||||
"emailAddress": "Email",
|
||||
"emailAddress": "E-mail",
|
||||
"username": "Nom d'utilisateur",
|
||||
"password": "Mot de passe",
|
||||
"reEnterPassword": "Confirmez mot de passe",
|
||||
@@ -15,7 +15,6 @@
|
||||
"createAccountButton": "Créer le compte",
|
||||
"passwordRequirementsHeader": "Mot de passe requis",
|
||||
"successHeader": "Succès !",
|
||||
"successContinueButton": "Continuer",
|
||||
"confirmationRequired": "Confirmation de l'adresse e-mail requise",
|
||||
"confirmationRequiredMessage": "Veuillez vérifier votre boite de réception pour confirmer votre adresse e-mail.",
|
||||
"yourAccountIsValidUntil": "Votre compte sera valide jusqu'au {date}.",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"createAccountButton": "Buat Akun",
|
||||
"passwordRequirementsHeader": "Persyaratan Kata Sandi",
|
||||
"successHeader": "Sukses!",
|
||||
"successContinueButton": "Lanjut",
|
||||
"confirmationRequired": "Konfirmasi email diperlukan",
|
||||
"confirmationRequiredMessage": "Silakan periksa kotak masuk email Anda untuk memverifikasi alamat Anda."
|
||||
},
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"createAccountButton": "Crea Un Account",
|
||||
"passwordRequirementsHeader": "Requisiti Password",
|
||||
"successHeader": "Successo!",
|
||||
"successContinueButton": "Continua",
|
||||
"confirmationRequired": "Richiesta la conferma Email",
|
||||
"confirmationRequiredMessage": "Controlla la tua casella email per verificare il tuo indirizzo."
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Nederlands (NL)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Maak Jellyfin account aan",
|
||||
"pageTitle": "Maak Jellyfin-account aan",
|
||||
"createAccountHeader": "Account aanmaken",
|
||||
"accountDetails": "Details",
|
||||
"emailAddress": "E-mail",
|
||||
@@ -14,7 +14,6 @@
|
||||
"createAccountButton": "Maak account aan",
|
||||
"passwordRequirementsHeader": "Wachtwoordvereisten",
|
||||
"successHeader": "Succes!",
|
||||
"successContinueButton": "Doorgaan",
|
||||
"confirmationRequired": "Bevestiging van e-mailadres verplicht",
|
||||
"confirmationRequiredMessage": "Controleer je e-mail inbox om je adres te bevestigen.",
|
||||
"yourAccountIsValidUntil": "Je account zal geldig zijn tot {date}.",
|
||||
@@ -53,6 +52,7 @@
|
||||
"errorDiscordVerification": "Discord-verificatie vereist.",
|
||||
"errorUnknown": "Onbekende fout.",
|
||||
"errorMatrixVerification": "Matrix-verificatie vereist.",
|
||||
"verified": "Account geverifieerd."
|
||||
"verified": "Account geverifieerd.",
|
||||
"errorNoEmail": "E-mail vereist."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"createAccountButton": "Criar Conta",
|
||||
"passwordRequirementsHeader": "Requisitos da Senha",
|
||||
"successHeader": "Concluído!",
|
||||
"successContinueButton": "Continuar",
|
||||
"confirmationRequired": "Confirmação por e-mail",
|
||||
"confirmationRequiredMessage": "Verifique sua caixa de email para finalizar o cadastro.",
|
||||
"yourAccountIsValidUntil": "Sua conta é válida até {date}.",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"createAccountButton": "Skapa konto",
|
||||
"passwordRequirementsHeader": "Lösenordskrav",
|
||||
"successHeader": "Lyckades!",
|
||||
"successContinueButton": "Fortsätt",
|
||||
"confirmationRequired": "E-postbekräftelse krävs",
|
||||
"confirmationRequiredMessage": "Kontrollera din e-postkorg för att verifiera din adress.",
|
||||
"yourAccountIsValidUntil": "Ditt konto är giltigt fram tills {date}.",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"createAccountButton": "创建账户",
|
||||
"passwordRequirementsHeader": "密码格式要求",
|
||||
"successHeader": "成功!",
|
||||
"successContinueButton": "继续",
|
||||
"confirmationRequired": "需要邮件确认",
|
||||
"confirmationRequiredMessage": "请登录您的邮箱收件箱来验证您的地址。",
|
||||
"yourAccountIsValidUntil": "您的账户将在 {date} 之前有效。",
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": ""
|
||||
},
|
||||
"strings": {
|
||||
"passwordReset": "",
|
||||
"resetFailed": "",
|
||||
"tryAgain": "",
|
||||
"youCanLogin": "",
|
||||
"youCanLoginOmbi": "",
|
||||
"changeYourPassword": ""
|
||||
"name": "English (GB)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"tryAgain": "Please try again.",
|
||||
"youCanLogin": "You can now log in with the below code as your password.",
|
||||
"youCanLoginOmbi": "You can now log in to Jellyfin & Ombi with the below code as your password.",
|
||||
"youCanLoginPassword": "You can now login with your new password. Press below to continue to Jellyfin.",
|
||||
"changeYourPassword": "Make sure to change your password after you log in.",
|
||||
"enterYourPassword": "Enter your new password below."
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
"name": "Español (ES)"
|
||||
},
|
||||
"strings": {
|
||||
"passwordReset": "Cambiar contraseña",
|
||||
"resetFailed": "Error al cambiar contraseña",
|
||||
"tryAgain": "Por favor intente nuevamente.",
|
||||
"youCanLogin": "Ahora puedes logearte con el codigo como contraseña.",
|
||||
"changeYourPassword": "Recuerda cambiar tu contraseña luego de iniciar sesión.",
|
||||
"passwordReset": "Restablecimiento de contraseña",
|
||||
"resetFailed": "Error al restablecer la contraseña",
|
||||
"tryAgain": "Por favor, intente de nuevo.",
|
||||
"youCanLogin": "Ahora puede iniciar sesión con el siguiente código como contraseña.",
|
||||
"changeYourPassword": "Asegúrese de cambiar su contraseña después de iniciar sesión.",
|
||||
"youCanLoginOmbi": "Ahora puede iniciar sesión en Jellyfin & Ombi con el siguiente código como contraseña.",
|
||||
"reset": "Reiniciar"
|
||||
"reset": "Reiniciar",
|
||||
"enterYourPassword": "Ingrese su nueva contraseña abajo.",
|
||||
"youCanLoginPassword": "Ahora puede iniciar sesión con su nueva contraseña. Presione a continuación para continuar con Jellyfin."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"youCanLogin": "Vous pouvez maintenant vous connecter en utilisant ce code comme mot de passe.",
|
||||
"youCanLoginOmbi": "Vous pouvez maintenant vous connecter à Jellyfin et Ombi en utilisant ce mot de passe.",
|
||||
"changeYourPassword": "Assurez-vous de changer votre mot de passe après s'être connecté.",
|
||||
"enterYourPassword": "Entrez votre nouveau mot de passe ici."
|
||||
"enterYourPassword": "Entrez votre nouveau mot de passe ici.",
|
||||
"youCanLoginPassword": "Vous pouvez maintenant vous connecter avec votre nouveau mot de passe. Appuyez ci-dessous pour continuer vers Jellyfin."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"youCanLogin": "Je kunt nu inloggen met onderstaande code als wachtwoord.",
|
||||
"youCanLoginOmbi": "Je kunt nu inloggen bij Jellyfin & Ombi met onderstaande code als wachtwoord.",
|
||||
"changeYourPassword": "Denk eraan je wachtwoord te wijzigen nadat je bent ingelogd.",
|
||||
"enterYourPassword": "Voer hieronder je nieuwe wachtwoord in."
|
||||
"enterYourPassword": "Voer hieronder je nieuwe wachtwoord in.",
|
||||
"youCanLoginPassword": "Je kunt nu inloggen met je nieuwe wachtwoord. Druk hieronder om verder te gaan naar Jellyfin."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": "Español(ES)"
|
||||
"name": "Español (ES)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Configuración - jfa-go",
|
||||
@@ -15,17 +15,21 @@
|
||||
"serverAddress": "Dirección del servidor",
|
||||
"emailSubject": "Asunto",
|
||||
"URL": "URL",
|
||||
"apiKey": "Llave de autorización (API)"
|
||||
"apiKey": "Llave de autorización (API)",
|
||||
"errorInvalidUserPass": "Usuario/contraseña inválido.",
|
||||
"errorNotAdmin": "El usuario no tiene permitido administrar el servidor.",
|
||||
"errorUserDisabled": "El usuario puede estar desactivado.",
|
||||
"error404": "404, verifique la URL interna."
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "¡Bienvenido!",
|
||||
"pressStart": "Deberá hacer algunas cosas para configurar jfa-go. Presione comenzar para continuar.",
|
||||
"pressStart": "Tendrás que hacer algunas cosas para configurar jfa-go. Pulsa el botón de inicio para continuar.",
|
||||
"httpsNotice": "Asegúrese de acceder a esta página a través de HTTPS o bien desde una red privada.",
|
||||
"start": "Empezar"
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "¡Terminado!",
|
||||
"restartMessage": "Hay más opciones que puede configurar en la página de administración. Haga clic a continuación para reiniciar, luego actualice la página.",
|
||||
"restartMessage": "Puede configurar los bots de Discord/Telegram/Matrix, personalizar sus mensajes y más en Configuración. Haga clic a continuación para reiniciar, luego actualice la página.",
|
||||
"refreshPage": "Actualizar"
|
||||
},
|
||||
"language": {
|
||||
@@ -37,8 +41,8 @@
|
||||
},
|
||||
"general": {
|
||||
"title": "General",
|
||||
"listenAddress": "Dirección de recibidor (Listen Address)",
|
||||
"urlBase": "Base de URL",
|
||||
"listenAddress": "Dirección de escucha (Listen Address)",
|
||||
"urlBase": "URL Base",
|
||||
"urlBaseNotice": "Solo es necesario si se usa un proxy inverso en un subdominio (por ejemplo, 'jellyf.in/accounts').",
|
||||
"lightTheme": "Claro",
|
||||
"darkTheme": "Oscuro",
|
||||
@@ -51,7 +55,7 @@
|
||||
"updates": {
|
||||
"title": "Actualizaciones",
|
||||
"description": "Habilite para recibir notificaciones cuando haya nuevas actualizaciones disponibles. jfa-go comprobará {n} cada 30 minutos. No se recopilan IP ni información de identificación personal.",
|
||||
"updateChannel": "Actualizar canal",
|
||||
"updateChannel": "Canal de actualización",
|
||||
"stable": "Estable",
|
||||
"unstable": "Inestable"
|
||||
},
|
||||
@@ -75,7 +79,7 @@
|
||||
"testConnection": "Probar conexión"
|
||||
},
|
||||
"ombi": {
|
||||
"title": "Ombi.",
|
||||
"title": "Ombi",
|
||||
"description": "Al conectarse a Ombi, se creará una cuenta de Jellyfin y Ombi cuando un usuario se una a través de jfa-go. Una vez finalizada la configuración, vaya a Configuración para establecer un perfil predeterminado para los nuevos usuarios de ombi.",
|
||||
"apiKeyNotice": "Encuentra esto en la primera pestaña de la configuración de Ombi."
|
||||
},
|
||||
@@ -93,16 +97,16 @@
|
||||
"mailgunApiURL": "URL de API"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notificaciones",
|
||||
"description": "Si está habilitado, puede elegir (por invitación) recibir un correo electrónico cuando una invitación caduque o se cree un usuario. Si no eligió el método de inicio de sesión de Jellyfin, asegúrese de proporcionar su dirección de correo electrónico."
|
||||
"title": "Notificaciones de administrador",
|
||||
"description": "Si está habilitado, puede elegir (por invitación) recibir un mensaje cuando caduca una invitación o se crea un usuario. Si no eligió el método de inicio de sesión de Jellyfin, asegúrese de proporcionar su dirección de correo electrónico o agregue otro método de contacto más adelante."
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "Correos de bienvenida",
|
||||
"description": "Si está habilitado, se enviará un correo electrónico a los nuevos usuarios con la URL de Jellyfin/Emby y su nombre de usuario."
|
||||
"title": "Mensajes de bienvenida",
|
||||
"description": "Si está habilitado, se enviará un mensaje a los nuevos usuarios con la URL de Jellyfin/Emby y su nombre de usuario."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "Correos de invitación",
|
||||
"description": "Si está habilitado, puede enviar invitaciones directamente a la dirección de correo electrónico de un usuario. Debido a que es posible que esté utilizando un proxy inverso, debe proporcionar la URL desde la que se accede a las invitaciones. Escriba su base de URL y agregue '/ invite'."
|
||||
"title": "Mensajes de invitación",
|
||||
"description": "Si está habilitado, puede enviar invitaciones directamente a la dirección de correo electrónico de un usuario, al usuario de Discord o Matrix. Debido a que es posible que esté utilizando un proxy inverso, debe proporcionar la URL desde la que se accede a las invitaciones. Escriba su base de URL y agregue '/ invite'."
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "Restablecimiento de contraseña",
|
||||
@@ -111,7 +115,9 @@
|
||||
"pathToJellyfinNotice": "Si no sabe dónde está, intente restablecer su contraseña en Jellyfin. Aparecerá una ventana emergente con '<ruta a jellyfin>/passwordreset-. Json'.",
|
||||
"resetLinks": "Envía un enlace en lugar de un PIN",
|
||||
"resetLinksNotice": "Si la integración de Ombi está habilitada, utilícela para sincronizar los restablecimientos de contraseña de Jellyfin con Ombi.",
|
||||
"resetLinksLanguage": "Enlace de restablecimiento predeterminado"
|
||||
"resetLinksLanguage": "Enlace de restablecimiento predeterminado",
|
||||
"setPassword": "Establecer contraseña a través del enlace",
|
||||
"setPasswordNotice": "Habilitar esto significa que el usuario no tiene que cambiar su contraseña del PIN después del restablecimiento. También se aplicará la validación de la contraseña."
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "Validación de contraseña",
|
||||
@@ -133,5 +139,9 @@
|
||||
"successMessageNotice": "Se muestra cuando un usuario crea su cuenta.",
|
||||
"emailMessage": "Mensaje de correo electrónico",
|
||||
"emailMessageNotice": "Aparece en la parte inferior de los correos electrónicos."
|
||||
},
|
||||
"messages": {
|
||||
"description": "jfa-go puede enviar restablecimientos de contraseña y varios mensajes a través de correo electrónico, Discord, Telegram y/o Matrix. Puede configurar el correo electrónico a continuación y los demás se pueden configurar en Configuración más adelante. Las instrucciones se pueden encontrar en el {n}. Si no lo necesita, puede desactivar estas funciones aquí.",
|
||||
"title": "Mensajes"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
"serverAddress": "Adresse du serveur",
|
||||
"emailSubject": "Objet de l'e-mail",
|
||||
"URL": "URL",
|
||||
"apiKey": "Clé API"
|
||||
"apiKey": "Clé API",
|
||||
"errorUserDisabled": "L'utilisateur est peut-être désactivé.",
|
||||
"error404": "404, vérifiez l'URL interne.",
|
||||
"errorInvalidUserPass": "Nom d'utilisateur/mot de passe invalide.",
|
||||
"errorNotAdmin": "L'utilisateur n'est pas autorisé à gérer le serveur."
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "Bienvenue !",
|
||||
@@ -25,7 +29,7 @@
|
||||
},
|
||||
"endPage": {
|
||||
"finished": "Terminé !",
|
||||
"restartMessage": "Il y a d'autres paramètres que vous pouvez configurer sur la page administrateur. Cliquez en dessous pour redémarrer, rafraichissez ensuite la page.",
|
||||
"restartMessage": "Vous pouvez configurer les bots Discord/Telegram/Matrix, personnaliser vos messages et plus encore dans Paramètres. Cliquez ci-dessous pour redémarrer, puis actualisez la page.",
|
||||
"refreshPage": "Rafraichir"
|
||||
},
|
||||
"language": {
|
||||
@@ -86,16 +90,16 @@
|
||||
"mailgunApiURL": "URL de l'API"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"description": "Si activé, vous pouvez choisir (par invitation) de recevoir un e-mail lorsque l'invitation expire, ou lorsque l'utilisateur est créé. Si vous ne choisissez pas la méthode de connexion par Jellyfin, assurez-vous d'avoir fourni votre adresse e-mail."
|
||||
"title": "Notifications d'administrateur",
|
||||
"description": "Si activé, vous pouvez choisir (par invitation) de recevoir un message lorsque l'invitation expire, ou lorsque l'utilisateur est créé. Si vous ne choisissez pas la méthode de connexion par Jellyfin, assurez-vous d'avoir fourni votre adresse e-mail ou un autre moyen de contact."
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "E-mails de bienvenue",
|
||||
"description": "Si activé, un e-mail sera envoyé aux nouveaux utilisateurs avec l'URL de Jellyfin/Emby et leur nom d'utilisateur."
|
||||
"title": "Messages de bienvenue",
|
||||
"description": "Si activé, un message sera envoyé aux nouveaux utilisateurs avec l'URL de Jellyfin/Emby et leur nom d'utilisateur."
|
||||
},
|
||||
"inviteEmails": {
|
||||
"title": "E-mails d'invitation",
|
||||
"description": "Si activé, vous pouvez envoyer une invitation directement à l'adresse e-mail de l'utilisateur. Parce que vous pourriez utiliser un reverse proxy, vous devez renseigner l'URL d'accès aux invitations. Renseignez votre Base URL et ajoutez '/invite'."
|
||||
"title": "Messages d'invitation",
|
||||
"description": "Si activé, vous pouvez envoyer une invitation directement à l'adresse e-mail, le Discord ou Matrix de l'utilisateur. Parce que vous pourriez utiliser un reverse proxy, vous devez renseigner l'URL d'accès aux invitations. Renseignez votre Base URL et ajoutez '/invite'."
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "Réinitialisation de mot de passe",
|
||||
@@ -104,7 +108,9 @@
|
||||
"pathToJellyfinNotice": "Si vous ne savez pas où c'est, essayez de réinitialiser votre mot de passe dans Jellyfin. Une popup avec '<path to jellyfin>/passwordreset-*.json' apparaitra.",
|
||||
"resetLinks": "Envoyer un lien plutôt qu'un PIN",
|
||||
"resetLinksNotice": "Si l'intégration est activée, utilisez ceci pour synchroniser les réinitialisations de mots de passe Jellyfin avec Ombi.",
|
||||
"resetLinksLanguage": "Langue du lien de réinitialisation par défaut"
|
||||
"resetLinksLanguage": "Langue du lien de réinitialisation par défaut",
|
||||
"setPassword": "Définir le mot de passe via le lien",
|
||||
"setPasswordNotice": "L'activation de cette option signifie que l'utilisateur n'a pas à modifier son mot de passe à partir du code PIN après la réinitialisation. La validation du mot de passe sera également appliquée."
|
||||
},
|
||||
"passwordValidation": {
|
||||
"title": "Validation du mot de passe",
|
||||
@@ -133,5 +139,9 @@
|
||||
"updateChannel": "Mettre à jour la chaîne",
|
||||
"stable": "Stable",
|
||||
"unstable": "Instable"
|
||||
},
|
||||
"messages": {
|
||||
"title": "Messages",
|
||||
"description": "jfa-go peut envoyer des réinitialisations de mot de passe et divers messages par e-mail, Discord, Telegram et/ou Matrix. Vous pouvez configurer l'e-mail ci-dessous, et les autres peuvent être configurés dans les paramètres plus tard. Les instructions se trouvent sur le {n}. Si vous n'en avez pas besoin, vous pouvez désactiver ces fonctionnalités ici."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Nederlands (NL)"
|
||||
},
|
||||
"strings": {
|
||||
"pageTitle": "Insstallatie - jfa-go",
|
||||
"pageTitle": "Installatie - jfa-go",
|
||||
"next": "Volgende",
|
||||
"back": "Terug",
|
||||
"optional": "Optioneel",
|
||||
@@ -15,7 +15,11 @@
|
||||
"serverAddress": "Serveradres",
|
||||
"emailSubject": "Onderwerp e-mail",
|
||||
"URL": "URL",
|
||||
"apiKey": "API-sleutel"
|
||||
"apiKey": "API-sleutel",
|
||||
"errorInvalidUserPass": "Ongeldige gebruikersnaam/wachtwoord.",
|
||||
"errorUserDisabled": "De gebruiker kan uitgeschakeld zijn.",
|
||||
"error404": "404, controleer de interne URL.",
|
||||
"errorNotAdmin": "Gebruiker heeft geen beheersrechten."
|
||||
},
|
||||
"startPage": {
|
||||
"welcome": "Welkom!",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"meta": {
|
||||
"name": ""
|
||||
"name": "Español (ES)"
|
||||
},
|
||||
"strings": {
|
||||
"startMessage": "¡Hola!\nIntroduce tu código PIN de Jellyfin para verificar tu cuenta.",
|
||||
"matrixStartMessage": "Hola,\nIntroduce el PIN de abajo para logarte en la página de Jellyfin y verificar tu cuenta.",
|
||||
"invalidPIN": "El PIN es inváido, inténtalo de nuevo.",
|
||||
"pinSuccess": "Ok! Ya puedes volver a la página de login.",
|
||||
"languageMessage": "Nota: Ver los idiomas disponibles con {command}, y ajusta el idioma con {command} <language code>."
|
||||
"matrixStartMessage": "Hola\nIngrese el PIN a continuación en la página de registro de Jellyfin para verificar su cuenta.",
|
||||
"invalidPIN": "Ese PIN no es válido, inténtalo de nuevo.",
|
||||
"pinSuccess": "¡Éxito! Ahora puedes volver a la página de registro.",
|
||||
"languageMessage": "Nota: Revisa los idiomas disponibles con {command}, y establece el idioma con {command} <language code>."
|
||||
}
|
||||
}
|
||||
|
||||
5
log.go
5
log.go
@@ -14,14 +14,15 @@ import (
|
||||
var logPath string = filepath.Join(temp, "jfa-go.log")
|
||||
var lineCache = linecache.NewLineCache(100)
|
||||
|
||||
func logOutput() (closeFunc func()) {
|
||||
func logOutput() (closeFunc func(), err error) {
|
||||
old := os.Stdout
|
||||
writers := []io.Writer{old, colorStripper{lineCache}}
|
||||
wExit := make(chan bool)
|
||||
r, w, _ := os.Pipe()
|
||||
var f *os.File
|
||||
if TRAY {
|
||||
log.Printf("Logging to \"%s\"", logPath)
|
||||
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
f, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
closeFunc = func() {}
|
||||
return
|
||||
|
||||
12
main.go
12
main.go
@@ -82,7 +82,7 @@ type appContext struct {
|
||||
configBase settings
|
||||
dataPath string
|
||||
webFS httpFS
|
||||
cssClass string // Default theme, "light-theme"|"dark-theme".
|
||||
cssClass string // Default theme, "light"|"dark".
|
||||
jellyfinLogin bool
|
||||
users []User
|
||||
invalidTokens []string
|
||||
@@ -108,6 +108,7 @@ type appContext struct {
|
||||
newUpdate bool // Whether whatever's in update is new.
|
||||
tag Tag
|
||||
update Update
|
||||
internalPWRs map[string]InternalPWR
|
||||
}
|
||||
|
||||
func generateSecret(length int) (string, error) {
|
||||
@@ -621,7 +622,7 @@ func flagPassed(name string) (found bool) {
|
||||
}
|
||||
|
||||
// @title jfa-go internal API
|
||||
// @version 0.3.6
|
||||
// @version 0.3.10
|
||||
// @description API for the jfa-go frontend
|
||||
// @contact.name Harvey Tindall
|
||||
// @contact.email hrfee@hrfee.dev
|
||||
@@ -637,7 +638,7 @@ func flagPassed(name string) (found bool) {
|
||||
// @name getTokenAuth
|
||||
|
||||
// @tag.name Auth
|
||||
// @tag.description --------Get a token here first!--------
|
||||
// @tag.description -Get a token here if running swagger UI locally.-
|
||||
|
||||
// @tag.name Users
|
||||
// @tag.description Jellyfin user related operations.
|
||||
@@ -666,7 +667,10 @@ func printVersion() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
f := logOutput()
|
||||
f, err := logOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to start logging: %v\n", err)
|
||||
}
|
||||
defer f()
|
||||
printVersion()
|
||||
SOCK = filepath.Join(temp, SOCK)
|
||||
|
||||
@@ -13,6 +13,7 @@ func runMigrations(app *appContext) {
|
||||
migrateProfiles(app)
|
||||
migrateBootstrap(app)
|
||||
migrateEmailStorage(app)
|
||||
migrateNotificationMethods(app)
|
||||
// migrateHyphens(app)
|
||||
}
|
||||
|
||||
@@ -39,8 +40,8 @@ func migrateProfiles(app *appContext) {
|
||||
// Migrate pre-0.2.5 bootstrap theme choice to a17t version.
|
||||
func migrateBootstrap(app *appContext) {
|
||||
themes := map[string]string{
|
||||
"Jellyfin (Dark)": "dark-theme",
|
||||
"Default (Light)": "light-theme",
|
||||
"Jellyfin (Dark)": "dark",
|
||||
"Default (Light)": "light",
|
||||
}
|
||||
|
||||
if app.config.Section("ui").Key("theme").String() == "Bootstrap (Light)" {
|
||||
@@ -123,6 +124,40 @@ func migrateEmailStorage(app *appContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pre-0.3.10, Admin notifications for invites were indexed by and only sent to email addresses. Now, when Jellyfin Login is enabled, They are indexed by the admin's Jellyfin ID, and send by any method enabled for them. This migrates storage to that format.
|
||||
func migrateNotificationMethods(app *appContext) error {
|
||||
if !app.config.Section("ui").Key("jellyfin_login").MustBool(false) {
|
||||
return nil
|
||||
}
|
||||
changes := false
|
||||
for code, invite := range app.storage.invites {
|
||||
if invite.Notify == nil {
|
||||
continue
|
||||
}
|
||||
for address, notifyPrefs := range invite.Notify {
|
||||
if !strings.Contains(address, "@") {
|
||||
continue
|
||||
}
|
||||
for id, email := range app.storage.emails {
|
||||
if email.Addr == address {
|
||||
invite.Notify[id] = notifyPrefs
|
||||
delete(invite.Notify, address)
|
||||
changes = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if changes {
|
||||
app.storage.invites[code] = invite
|
||||
}
|
||||
}
|
||||
if changes {
|
||||
app.info.Printf("Migrated to modified invite storage format.")
|
||||
return app.storage.storeInvites()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Migrate between hyphenated & non-hyphenated user IDs. Doesn't seem to happen anymore, so disabled.
|
||||
// func migrateHyphens(app *appContext) {
|
||||
// checkVersion := func(version string) int {
|
||||
|
||||
27
models.go
27
models.go
@@ -1,5 +1,7 @@
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
type stringResponse struct {
|
||||
Response string `json:"response" example:"message"`
|
||||
Error string `json:"error" example:"errorDescription"`
|
||||
@@ -69,6 +71,7 @@ type profileDTO struct {
|
||||
Admin bool `json:"admin" example:"false"` // Whether profile has admin rights or not
|
||||
LibraryAccess string `json:"libraries" example:"all"` // Number of libraries profile has access to
|
||||
FromUser string `json:"fromUser" example:"jeff"` // The user the profile is based on
|
||||
Ombi bool `json:"ombi"` // Whether or not Ombi settings are stored in this profile.
|
||||
}
|
||||
|
||||
type getProfilesDTO struct {
|
||||
@@ -81,9 +84,10 @@ type profileChangeDTO struct {
|
||||
}
|
||||
|
||||
type newProfileDTO struct {
|
||||
Name string `json:"name" example:"DefaultProfile" binding:"required"` // Name of the profile
|
||||
ID string `json:"id" example:"kasdjlaskjd342342" binding:"required"` // ID of user to source settings from
|
||||
Homescreen bool `json:"homescreen" example:"true"` // Whether to store homescreen layout or not
|
||||
Name string `json:"name" example:"DefaultProfile" binding:"required"` // Name of the profile
|
||||
ID string `json:"id" example:"ZXhhbXBsZTEyMzQ1Njc4OQo" binding:"required"` // ID of user to source settings from
|
||||
Homescreen bool `json:"homescreen" example:"true"` // Whether to store homescreen layout or not
|
||||
OmbiID string `json:"ombi_id" example:"ZXhhbXBsZTEyMzQ1Njc4OQo"` // ID of Ombi user to source settings from (optional)
|
||||
}
|
||||
|
||||
type inviteDTO struct {
|
||||
@@ -320,3 +324,20 @@ type ResetPasswordDTO struct {
|
||||
PIN string `json:"pin"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type AdminPasswordResetDTO struct {
|
||||
Users []string `json:"users"` // List of Jellyfin user IDs
|
||||
}
|
||||
|
||||
type AdminPasswordResetRespDTO struct {
|
||||
Link string `json:"link"` // Only returned if one of the given users doesn't have a contact method set, or only one user was requested.
|
||||
Manual bool `json:"manual"` // Whether or not the admin has to send the link manually or not.
|
||||
}
|
||||
|
||||
// InternalPWR stores a local version of a password reset PIN used for resets triggered by the admin when reset links are enabled.
|
||||
type InternalPWR struct {
|
||||
PIN string `json:"pin"`
|
||||
Username string `json:"username"`
|
||||
ID string `json:"id"`
|
||||
Expiry time.Time `json:"expiry"`
|
||||
}
|
||||
|
||||
@@ -217,5 +217,6 @@ func (ombi *Ombi) NewUser(username, password, email string, template map[string]
|
||||
}
|
||||
return lst, code, err
|
||||
}
|
||||
ombi.cacheExpiry = time.Now()
|
||||
return nil, code, err
|
||||
}
|
||||
|
||||
7388
package-lock.json
generated
7388
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,18 +19,21 @@
|
||||
"dependencies": {
|
||||
"@ts-stack/markdown": "^1.3.0",
|
||||
"@types/node": "^15.0.1",
|
||||
"a17t": "^0.4.0",
|
||||
"a17t": "^0.10.1",
|
||||
"browserslist": "^4.16.6",
|
||||
"esbuild": "^0.8.57",
|
||||
"inline-source-cli": "^2.0.0",
|
||||
"inline-source": "^7.2.0",
|
||||
"jsdom": "^19.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mjml": "^4.8.0",
|
||||
"nightwind": "github:yonson2/nightwind",
|
||||
"remixicon": "^2.5.0",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"typescript": "^4.0.3",
|
||||
"uncss": "^0.17.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"live-server": "^1.2.1"
|
||||
"live-server": "^1.2.1",
|
||||
"tailwindcss": "^3.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
19
pwreset.go
19
pwreset.go
@@ -9,6 +9,22 @@ import (
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
// GenInternalReset generates a local password reset PIN, for use with the PWR option on the Admin page.
|
||||
func (app *appContext) GenInternalReset(userID string) (InternalPWR, error) {
|
||||
pin := genAuthToken()
|
||||
user, status, err := app.jf.UserByID(userID, false)
|
||||
if err != nil || status != 200 {
|
||||
return InternalPWR{}, err
|
||||
}
|
||||
pwr := InternalPWR{
|
||||
PIN: pin,
|
||||
Username: user.Name,
|
||||
ID: userID,
|
||||
Expiry: time.Now().Add(30 * time.Minute),
|
||||
}
|
||||
return pwr, nil
|
||||
}
|
||||
|
||||
func (app *appContext) StartPWR() {
|
||||
app.info.Println("Starting password reset daemon")
|
||||
path := app.config.Section("password_resets").Key("watch_directory").String()
|
||||
@@ -38,6 +54,7 @@ type PasswordReset struct {
|
||||
Pin string `json:"Pin"`
|
||||
Username string `json:"UserName"`
|
||||
Expiry time.Time `json:"ExpirationDate"`
|
||||
Internal bool `json:"Internal,omitempty"`
|
||||
}
|
||||
|
||||
func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
||||
@@ -81,7 +98,7 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
||||
msg, err := app.email.constructReset(pwr, app, false)
|
||||
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to construct password reset message for %s", pwr.Username)
|
||||
app.err.Printf("Failed to construct password reset message for \"%s\"", pwr.Username)
|
||||
app.debug.Printf("%s: Error: %s", pwr.Username, err)
|
||||
} else if err := app.sendByID(msg, uid); err != nil {
|
||||
app.err.Printf("Failed to send password reset message to \"%s\"", name)
|
||||
|
||||
@@ -169,6 +169,8 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||
api.GET(p+"/users/announce/:name", app.GetAnnounceTemplate)
|
||||
api.DELETE(p+"/users/announce/:name", app.DeleteAnnounceTemplate)
|
||||
|
||||
api.POST(p+"/users/password-reset", app.AdminPasswordReset)
|
||||
|
||||
api.GET(p+"/config/update", app.CheckUpdate)
|
||||
api.POST(p+"/config/update", app.ApplyUpdate)
|
||||
api.GET(p+"/config/emails", app.GetCustomEmails)
|
||||
@@ -190,7 +192,8 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||
}
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
api.GET(p+"/ombi/users", app.OmbiUsers)
|
||||
api.POST(p+"/ombi/defaults", app.SetOmbiDefaults)
|
||||
api.POST(p+"/profiles/ombi/:profile", app.SetOmbiProfile)
|
||||
api.DELETE(p+"/profiles/ombi/:profile", app.DeleteOmbiProfile)
|
||||
}
|
||||
api.POST(p+"/matrix/login", app.MatrixLogin)
|
||||
|
||||
|
||||
52
scripts/dark-variant.sh
Executable file
52
scripts/dark-variant.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
|
||||
# scan all typescript and automatically add dark variants to color tags if they're not already present.
|
||||
|
||||
if [[ "$1" == "ts" ]]; then
|
||||
for f in $2/*.ts; do
|
||||
# FIXME: inline html
|
||||
for l in $(grep -n "~neutral\|~positive\|~urge\|~warning\|~info\|~critical" $f | sed -e 's/:.*//g'); do
|
||||
# for l in $(sed -n '/classList/=' $f); do
|
||||
line=$(sed -n "${l}p" $f)
|
||||
echo $line | grep "classList" &> /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo $line | sed 's/.*classList//; s/).*//' | grep "~neutral\|~positive\|~urge\|~warning\|~info\|~critical" &> /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
# echo "found classList @ " $l
|
||||
echo $line | grep "dark:" &>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
for color in neutral positive urge warning info critical; do
|
||||
sed -i "${l},${l}s/\"~${color}\"/\"~${color}\", \"dark:~d_${color}\"/g" $f
|
||||
done
|
||||
fi
|
||||
else
|
||||
echo "FIX: classList found, but color tag wasn't in it"
|
||||
fi
|
||||
else
|
||||
echo $line | grep "querySelector" &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
# echo "found inline in " $f " @ " $l ", " $(sed -n "${l}p" $f)
|
||||
echo $line | grep "dark:" &>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
for color in neutral positive urge warning info critical; do
|
||||
sed -i "${l},${l}s/~${color}/~${color} dark:~d_${color}/g" $f
|
||||
done
|
||||
fi
|
||||
else
|
||||
echo $line | sed 's/.*querySelector//; s/).*//' | grep "~neutral\|~positive\|~urge\|~warning\|~info\|~critical" &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo $line | grep "dark:" &>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
# echo "found inline in " $f " @ " $l ", " $(sed -n "${l}p" $f)
|
||||
for color in neutral positive urge warning info critical; do
|
||||
sed -i "${l},${l}s/~${color}/~${color} dark:~d_${color}/g" $f
|
||||
done
|
||||
fi
|
||||
#else
|
||||
#echo "FIX: querySelector found, but color tag wasn't in it: " $line
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
35
scripts/inline.js
Normal file
35
scripts/inline.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// equiv to npx inline-source --root data data/crash.html out-cli.html
|
||||
|
||||
const { inlineSource } = require('inline-source');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
if (process.argv.length < 4) {
|
||||
console.log(`Usage
|
||||
${process.argv[0]} ${process.argv[1]} [root <rootdir>] in.html out.html`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let htmlpath = path.resolve(process.argv[2]);
|
||||
let root = path.resolve('.');
|
||||
let out = path.resolve(process.argv[3]);
|
||||
if (process.argv[2] == 'root') {
|
||||
root = path.resolve(process.argv[3]);
|
||||
htmlpath = path.resolve(process.argv[4]);
|
||||
out = path.resolve(process.argv[5]);
|
||||
}
|
||||
|
||||
inlineSource(htmlpath, {
|
||||
compress: true,
|
||||
rootpath: root,
|
||||
}).then((html) => {
|
||||
fs.writeFile(out, html, (err) => {
|
||||
if (err) {
|
||||
console.log("Failed:", err);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.log("Failed:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
56
scripts/missing-colors.js
Normal file
56
scripts/missing-colors.js
Normal file
@@ -0,0 +1,56 @@
|
||||
let parser = require("jsdom");
|
||||
let fs = require("fs");
|
||||
let path = require("path");
|
||||
|
||||
const hasDark = (item) => {
|
||||
for (let i = 0; i < item.classList.length; i++) {
|
||||
if (item.classList[i].substring(0,5) == "dark:") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
const fixHTML = (infile, outfile) => {
|
||||
console.log(infile, outfile)
|
||||
let doc = new parser.JSDOM(fs.readFileSync(infile));
|
||||
for (let item of ["badge", "chip", "shield", "input", "table", "button", "portal", "select", "aside", "card", "field", "textarea"]) {
|
||||
let items = doc.window.document.body.querySelectorAll("."+item);
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let hasColor = false;
|
||||
for (let color of ["neutral", "positive", "urge", "warning", "info", "critical"]) {
|
||||
//console.log(color);
|
||||
if (items[i].classList.contains("~"+color)) {
|
||||
hasColor = true;
|
||||
// console.log("adding to", items[i].classList)
|
||||
if (!hasDark(items[i])) {
|
||||
items[i].classList.add("dark:~d_"+color);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasColor) {
|
||||
if (!hasDark(items[i])) {
|
||||
// card without ~neutral look different than with.
|
||||
if (item != "card") items[i].classList.add("~neutral");
|
||||
items[i].classList.add("dark:~d_neutral");
|
||||
}
|
||||
}
|
||||
if (!items[i].classList.contains("@low") && !items[i].classList.contains("@high")) {
|
||||
items[i].classList.add("@low");
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(outfile, doc.window.document.documentElement.outerHTML);
|
||||
};
|
||||
|
||||
let inpath = process.argv[process.argv.length-2];
|
||||
let outpath = process.argv[process.argv.length-1];
|
||||
|
||||
let files = fs.readdirSync(inpath);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (files[i].indexOf(".html")>=0) {
|
||||
fixHTML(path.join(inpath, files[i]), path.join(outpath, files[i]));
|
||||
}
|
||||
}
|
||||
1
setup.go
1
setup.go
@@ -143,6 +143,7 @@ func (st *Storage) loadLangSetup(filesystems ...fs.FS) error {
|
||||
patchLang(&lang.Login, &english.Login)
|
||||
patchLang(&lang.JellyfinEmby, &english.JellyfinEmby)
|
||||
patchLang(&lang.Email, &english.Email)
|
||||
patchLang(&lang.Messages, &english.Messages)
|
||||
patchLang(&lang.Notifications, &english.Notifications)
|
||||
patchLang(&lang.PasswordResets, &english.PasswordResets)
|
||||
patchLang(&lang.InviteEmails, &english.InviteEmails)
|
||||
|
||||
@@ -11,3 +11,7 @@ body {
|
||||
background: #AA5CC3;
|
||||
background: linear-gradient(90deg, #AA5CC3 0%, #00A4DC 100%) !important;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -53,24 +53,50 @@ sudo apt-get install jfa-go-tray
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-container" id="page-container">
|
||||
<div class="card ~neutral !low mb-1">
|
||||
<div class="card ~neutral @low mb-1">
|
||||
<div class="row col flex center">
|
||||
<span class="heading welcome">jellyfin-accounts (go)</span>
|
||||
</div>
|
||||
<div class="row col flex center">
|
||||
<p class="content">a better way to manage your Jellyfin users.</p>
|
||||
</div>
|
||||
<div class="row col flex center">
|
||||
<ul class="support">
|
||||
<li>Send invite links to your users, let them sign up themselves</li>
|
||||
<li>Create setting profiles to restrict permissions of new users</li>
|
||||
<li>Handles password resets without your intervention</li>
|
||||
<li>Enforce password requirements on sign-up</li>
|
||||
<li>Send messages & notifications to your users (email, discord, telegram, matrix available)</li>
|
||||
<li>Set accounts to expire after a specified time</li>
|
||||
<li>Manage your users in bulk</li>
|
||||
<li><a href="https://github.com/hrfee/jfa-go#features" target="_blank">More</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<span class="row col flex center supra">links</span>
|
||||
<div class="row col flex center">
|
||||
<a class="button ~info mr-half mt-1 mb-1" href="https://github.com/hrfee/jfa-go">github</a>
|
||||
<a class="button ~neutral mr-half mt-1 mb-1" href="https://github.com/hrfee/jfa-go">github</a>
|
||||
<a class="button ~urge mt-1 mb-1 mr-half" href="https://wiki.jfa-go.com">wiki/docs</a>
|
||||
<a class="button ~positive mt-1 mb-1 mr-half" href="https://weblate.jfa-go.com">translation</a>
|
||||
<div class="dropdown mr-half" tabindex="0">
|
||||
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button ~info mt-1 mb-1 dropdown-button">
|
||||
<i class="ri-hand-heart-line mr-half"></i>
|
||||
donate
|
||||
<span class="ml-1 chev"></span>
|
||||
</a>
|
||||
<div class="dropdown-display">
|
||||
<div class="card ~info @low">
|
||||
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button input ~neutral field mb-half lang-link">GitHub</a>
|
||||
<a href="https://ko-fi.com/hrfee" target="_blank" class="button input ~neutral field mb-half lang-link">Ko-fi</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="button ~urge mt-1 mb-1 @low discord" href="https://discord.com/invite/MrtvuQmyhP" target="_blank"><i class="ri-discord-line mr-half"></i>discord</a>
|
||||
</div>
|
||||
<p class="row col flex center supra">downloads</p>
|
||||
<p class="row col flex center support">instructions can be found <a target="_blank" href="https://github.com/hrfee/jfa-go#install">here</a></p>
|
||||
<p class="row col flex center support">note: tray icon builds on linux require extra dependencies, see the github README for more info.</p>
|
||||
<p class="row col flex center text-center support">note: tray icon builds should only be used on systems with a Desktop Interface, and require extra dependencies on linux, see the github README for more info.</p>
|
||||
<div class="row col flex center">
|
||||
<span class="button ~neutral !high mr-1 mt-1" id="download-stable">Stable</span>
|
||||
<span class="button ~neutral @high mr-1 mt-1" id="download-stable">Stable</span>
|
||||
<span class="button ~neutral mt-1 mr-1" id="download-unstable">Unstable</span>
|
||||
</div>
|
||||
<div class="mt-1" id="sect-stable">
|
||||
@@ -84,9 +110,8 @@ sudo apt-get install jfa-go-tray
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 unfocused" id="sect-unstable">
|
||||
<p class="row center">These are built on every commit, so may include incomplete/broken features.</p>
|
||||
<p class="row center">These are built on every commit, so may include incomplete/broken features. Take care.</p>
|
||||
<div class="row col flex center">
|
||||
<a class="button ~info mr-half mb-half lang-link" target="_blank" href="https://dl.jfa-go.com/view/hrfee/jfa-go">windows/mac/linux</a>
|
||||
<a class="button ~info mr-half mb-half lang-link" id="download-docker-unstable">docker</a>
|
||||
<a class="button ~info mr-half mb-half lang-link" id="download-deb-unstable">debian/ubuntu</a>
|
||||
<a class="button ~info mr-half mb-half lang-link" target="_blank" href="https://aur.archlinux.org/packages/jfa-go-git">arch (aur git)</a>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Modal } from "../../ts/modules/modal.js";
|
||||
import { whichAnimationEvent } from "../../ts/modules/common.js";
|
||||
import { loadBuilds } from "./repo.js";
|
||||
|
||||
interface window extends Window {
|
||||
animationEvent: string;
|
||||
@@ -18,7 +19,7 @@ const debUnstableButton = document.getElementById("download-deb-unstable") as HT
|
||||
debUnstableButton.onclick = debModal.toggle;
|
||||
|
||||
const stableSect = document.getElementById("sect-stable");
|
||||
const unstableSect = document.getElementById("sect-unstable");
|
||||
export const unstableSect = document.getElementById("sect-unstable");
|
||||
|
||||
const stableButton = document.getElementById("download-stable") as HTMLSpanElement;
|
||||
const unstableButton = document.getElementById("download-unstable") as HTMLSpanElement;
|
||||
@@ -28,8 +29,8 @@ const dockerUnstable = document.getElementById("docker-unstable");
|
||||
stableButton.onclick = () => {
|
||||
debUnstable.classList.add("unfocused");
|
||||
dockerUnstable.classList.add("unfocused");
|
||||
stableButton.classList.add("!high");
|
||||
unstableButton.classList.remove("!high");
|
||||
stableButton.classList.add("@high");
|
||||
unstableButton.classList.remove("@high");
|
||||
stableSect.classList.remove("unfocused");
|
||||
unstableSect.classList.add("unfocused");
|
||||
|
||||
@@ -38,8 +39,8 @@ stableButton.onclick = () => {
|
||||
unstableButton.onclick = () => {
|
||||
debUnstable.classList.remove("unfocused");
|
||||
dockerUnstable.classList.remove("unfocused");
|
||||
unstableButton.classList.add("!high");
|
||||
stableButton.classList.remove("!high");
|
||||
unstableButton.classList.add("@high");
|
||||
stableButton.classList.remove("@high");
|
||||
stableSect.classList.add("unfocused");
|
||||
unstableSect.classList.remove("unfocused");
|
||||
}
|
||||
@@ -51,4 +52,4 @@ const dockerUnstableButton = document.getElementById("download-docker-unstable")
|
||||
dockerButton.onclick = dockerModal.toggle;
|
||||
dockerUnstableButton.onclick = dockerModal.toggle;
|
||||
|
||||
|
||||
loadBuilds();
|
||||
|
||||
57
site/ts/repo.ts
Normal file
57
site/ts/repo.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { _get, _post, _delete } from "../../ts/modules/common.js";
|
||||
import { unstableSect } from "./main.js";
|
||||
|
||||
const urlBase = "https://builds.hrfee.pw/repo/hrfee/jfa-go/latest/file/";
|
||||
const categories = {
|
||||
"Windows (Tray)": {
|
||||
"x64": "Windows"
|
||||
},
|
||||
"Linux (Tray)": {
|
||||
"x64": "TrayIcon_Linux_x86_64.zip"
|
||||
},
|
||||
"Linux": {
|
||||
"x64": "Linux_x86_64.zip",
|
||||
"ARM (32-bit)": "Linux_arm.zip",
|
||||
"ARM (64-bit)": "Linux_arm64.zip"
|
||||
},
|
||||
"macOS": {
|
||||
"x64": "macOS_x86_64",
|
||||
"ARM": "macOS_arm64"
|
||||
}
|
||||
};
|
||||
|
||||
export const loadBuilds = () => {
|
||||
for (let buildName in categories) {
|
||||
if (Object.keys(categories[buildName]).length == 1) {
|
||||
const button = document.createElement("a") as HTMLAnchorElement;
|
||||
button.classList.add("button", "~info", "mr-half", "mb-half", "lang-link");
|
||||
button.target = "_blank";
|
||||
button.textContent = buildName.toLowerCase();
|
||||
button.href = urlBase + categories[buildName][Object.keys(categories[buildName])[0]];
|
||||
unstableSect.querySelector(".row.col.flex.center").appendChild(button);
|
||||
} else {
|
||||
const dropdown = document.createElement("span") as HTMLSpanElement;
|
||||
dropdown.tabIndex = 0;
|
||||
dropdown.classList.add("dropdown");
|
||||
let innerHTML = `
|
||||
<span class="button ~info mr-half mb-half lang-link">
|
||||
${buildName.toLowerCase()}
|
||||
<span class="ml-half chev"></span>
|
||||
</span>
|
||||
<div class="dropdown-display above">
|
||||
<div class="card ~info @low">
|
||||
`;
|
||||
for (let arch in categories[buildName]) {
|
||||
innerHTML += `
|
||||
<a href="${urlBase + categories[buildName][arch]}" target="_blank" class="button input ~neutral field mb-half lang-link">${arch}</a>
|
||||
`;
|
||||
}
|
||||
innerHTML += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
dropdown.innerHTML = innerHTML;
|
||||
unstableSect.querySelector(".row.col.flex.center").appendChild(dropdown);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -85,6 +85,7 @@ type Profile struct {
|
||||
Configuration mediabrowser.Configuration `json:"configuration,omitempty"`
|
||||
Displayprefs map[string]interface{} `json:"displayprefs,omitempty"`
|
||||
Default bool `json:"default,omitempty"`
|
||||
Ombi map[string]interface{} `json:"ombi,omitempty"`
|
||||
}
|
||||
|
||||
type Invite struct {
|
||||
|
||||
26
tailwind.config.js
Normal file
26
tailwind.config.js
Normal file
@@ -0,0 +1,26 @@
|
||||
let colors = require("tailwindcss/colors")
|
||||
let dark = require("./css/dark");
|
||||
|
||||
module.exports = {
|
||||
content: ["./data/html/*.html", "./build/data/html/*.html", "./ts/*.ts", "./ts/modules/*.ts"],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
neutral: colors.slate,
|
||||
positive: colors.green,
|
||||
urge: colors.violet,
|
||||
warning: colors.yellow,
|
||||
info: colors.blue,
|
||||
critical: colors.red,
|
||||
d_neutral: dark.d_neutral,
|
||||
d_positive: dark.d_positive,
|
||||
d_urge: dark.d_urge,
|
||||
d_warning: dark.d_warning,
|
||||
d_info: dark.d_info,
|
||||
d_critical: dark.d_critical
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [require("a17t")],
|
||||
}
|
||||
45
ts/admin.ts
45
ts/admin.ts
@@ -1,4 +1,4 @@
|
||||
import { toggleTheme, loadTheme } from "./modules/theme.js";
|
||||
import { nightwind } from "./modules/theme.js";
|
||||
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
||||
import { Modal } from "./modules/modal.js";
|
||||
import { Tabs } from "./modules/tabs.js";
|
||||
@@ -9,8 +9,31 @@ import { ProfileEditor } from "./modules/profiles.js";
|
||||
import { _get, _post, notificationBox, whichAnimationEvent, toggleLoader } from "./modules/common.js";
|
||||
import { Updater } from "./modules/update.js";
|
||||
|
||||
loadTheme();
|
||||
(document.getElementById('button-theme') as HTMLSpanElement).onclick = toggleTheme;
|
||||
let theme = new nightwind();
|
||||
|
||||
const themeButton = document.getElementById('button-theme') as HTMLSpanElement;
|
||||
const switchThemeIcon = () => {
|
||||
const icon = themeButton.childNodes[0] as HTMLElement;
|
||||
if (document.documentElement.classList.contains("dark")) {
|
||||
icon.classList.add("ri-sun-line");
|
||||
icon.classList.remove("ri-moon-line");
|
||||
themeButton.classList.add("~warning");
|
||||
themeButton.classList.remove("~neutral");
|
||||
themeButton.classList.remove("@high");
|
||||
} else {
|
||||
icon.classList.add("ri-moon-line");
|
||||
icon.classList.remove("ri-sun-line");
|
||||
themeButton.classList.add("@high");
|
||||
themeButton.classList.add("~neutral");
|
||||
themeButton.classList.remove("~warning");
|
||||
}
|
||||
};
|
||||
themeButton.onclick = () => {
|
||||
theme.toggle();
|
||||
switchThemeIcon();
|
||||
}
|
||||
switchThemeIcon();
|
||||
|
||||
|
||||
window.lang = new lang(window.langFile as LangFile);
|
||||
loadLangSelector("admin");
|
||||
@@ -46,8 +69,8 @@ window.availableProfiles = window.availableProfiles || [];
|
||||
|
||||
window.modals.settingsRefresh = new Modal(document.getElementById('modal-refresh'));
|
||||
|
||||
window.modals.ombiDefaults = new Modal(document.getElementById('modal-ombi-defaults'));
|
||||
document.getElementById('form-ombi-defaults').addEventListener('submit', window.modals.ombiDefaults.close);
|
||||
window.modals.ombiProfile = new Modal(document.getElementById('modal-ombi-profile'));
|
||||
document.getElementById('form-ombi-defaults').addEventListener('submit', window.modals.ombiProfile.close);
|
||||
|
||||
window.modals.profiles = new Modal(document.getElementById("modal-user-profiles"));
|
||||
|
||||
@@ -72,6 +95,10 @@ window.availableProfiles = window.availableProfiles || [];
|
||||
if (window.discordEnabled) {
|
||||
window.modals.discord = new Modal(document.getElementById("modal-discord"));
|
||||
}
|
||||
|
||||
if (window.linkResetEnabled) {
|
||||
window.modals.sendPWR = new Modal(document.getElementById("modal-send-pwr"));
|
||||
}
|
||||
})();
|
||||
|
||||
var inviteCreator = new createInvite();
|
||||
@@ -91,10 +118,10 @@ window.notifications = new notificationBox(document.getElementById('notification
|
||||
const user = document.getElementById('radio-use-user') as HTMLInputElement;
|
||||
const profileSelect = document.getElementById('modify-user-profiles') as HTMLDivElement;
|
||||
const userSelect = document.getElementById('modify-user-users') as HTMLDivElement;
|
||||
(user.nextElementSibling as HTMLSpanElement).classList.toggle('!normal');
|
||||
(user.nextElementSibling as HTMLSpanElement).classList.toggle('!high');
|
||||
(profile.nextElementSibling as HTMLSpanElement).classList.toggle('!normal');
|
||||
(profile.nextElementSibling as HTMLSpanElement).classList.toggle('!high');
|
||||
(user.nextElementSibling as HTMLSpanElement).classList.toggle('@low');
|
||||
(user.nextElementSibling as HTMLSpanElement).classList.toggle('@high');
|
||||
(profile.nextElementSibling as HTMLSpanElement).classList.toggle('@low');
|
||||
(profile.nextElementSibling as HTMLSpanElement).classList.toggle('@high');
|
||||
profileSelect.classList.toggle('unfocused');
|
||||
userSelect.classList.toggle('unfocused');
|
||||
}*/
|
||||
|
||||
16
ts/crash.ts
16
ts/crash.ts
@@ -11,17 +11,17 @@ const buttonChange = (type: string) => {
|
||||
if (type == "normal") {
|
||||
logSanitized.classList.add("unfocused");
|
||||
logNormal.classList.remove("unfocused");
|
||||
buttonNormal.classList.add("!high");
|
||||
buttonNormal.classList.remove("!normal");
|
||||
buttonSanitized.classList.add("!normal");
|
||||
buttonSanitized.classList.remove("!high");
|
||||
buttonNormal.classList.add("@high");
|
||||
buttonNormal.classList.remove("@low");
|
||||
buttonSanitized.classList.add("@low");
|
||||
buttonSanitized.classList.remove("@high");
|
||||
} else {
|
||||
logNormal.classList.add("unfocused");
|
||||
logSanitized.classList.remove("unfocused");
|
||||
buttonSanitized.classList.add("!high");
|
||||
buttonSanitized.classList.remove("!normal");
|
||||
buttonNormal.classList.add("!normal");
|
||||
buttonNormal.classList.remove("!high");
|
||||
buttonSanitized.classList.add("@high");
|
||||
buttonSanitized.classList.remove("@low");
|
||||
buttonNormal.classList.add("@low");
|
||||
buttonNormal.classList.remove("@high");
|
||||
}
|
||||
}
|
||||
buttonNormal.onclick = () => buttonChange("normal");
|
||||
|
||||
18
ts/form.ts
18
ts/form.ts
@@ -29,6 +29,7 @@ interface formWindow extends Window {
|
||||
userExpiryHours: number;
|
||||
userExpiryMinutes: number;
|
||||
userExpiryMessage: string;
|
||||
emailRequired: boolean;
|
||||
}
|
||||
|
||||
loadLangSelector("form");
|
||||
@@ -39,6 +40,7 @@ window.animationEvent = whichAnimationEvent();
|
||||
|
||||
window.successModal = new Modal(document.getElementById("modal-success"), true);
|
||||
|
||||
|
||||
var telegramVerified = false;
|
||||
if (window.telegramEnabled) {
|
||||
window.telegramModal = new Modal(document.getElementById("modal-telegram"), window.telegramRequired);
|
||||
@@ -99,7 +101,7 @@ if (window.discordEnabled) {
|
||||
link.classList.add("subheading", "link-center");
|
||||
link.href = inv.invite;
|
||||
link.target = "_blank";
|
||||
link.innerHTML = `<span class="img-circle lg mr-1"><img class="img-circle" src="${inv.icon}" width="64" height="64"></span>${window.discordServerName}`;
|
||||
link.innerHTML = `<span class="img-circle lg mr-4"><img class="img-circle" src="${inv.icon}" width="64" height="64"></span>${window.discordServerName}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -230,6 +232,18 @@ if (!window.usernameEnabled) { usernameField.parentElement.remove(); usernameFie
|
||||
const passwordField = document.getElementById("create-password") as HTMLInputElement;
|
||||
const rePasswordField = document.getElementById("create-reenter-password") as HTMLInputElement;
|
||||
|
||||
if (window.emailRequired) {
|
||||
emailField.addEventListener("keyup", () => {
|
||||
if (emailField.value.includes("@")) {
|
||||
submitButton.disabled = false;
|
||||
submitSpan.removeAttribute("disabled");
|
||||
} else {
|
||||
submitButton.disabled = true;
|
||||
submitSpan.setAttribute("disabled", "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var requirements = initValidator(passwordField, rePasswordField, submitButton, submitSpan)
|
||||
|
||||
interface respDTO {
|
||||
@@ -302,7 +316,7 @@ const create = (event: SubmitEvent) => {
|
||||
}, true, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(submitSpan);
|
||||
if (req.status == 401) {
|
||||
if (req.status == 401 || req.status == 400) {
|
||||
if (req.response["error"] as string) {
|
||||
if (req.response["error"] == "confirmEmail") {
|
||||
window.confirmationModal.show();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { _get, _post, _delete, toggleLoader, addLoader, removeLoader, toDateString, insertText } from "../modules/common.js";
|
||||
import { _get, _post, _delete, toggleLoader, addLoader, removeLoader, toDateString, insertText, toClipboard } from "../modules/common.js";
|
||||
import { templateEmail } from "../modules/settings.js";
|
||||
import { Marked } from "@ts-stack/markdown";
|
||||
import { stripMarkdown } from "../modules/stripmd.js";
|
||||
@@ -63,7 +63,7 @@ class user implements User {
|
||||
id = "";
|
||||
private _selected: boolean;
|
||||
|
||||
private _lastNotifyMethod = (): string => {
|
||||
lastNotifyMethod = (): string => {
|
||||
// Telegram, Matrix, Discord
|
||||
const telegram = window.telegramEnabled && this._telegramUsername && this._telegramUsername != "";
|
||||
const discord = window.discordEnabled && this._discordUsername && this._discordUsername != "";
|
||||
@@ -86,10 +86,10 @@ class user implements User {
|
||||
get admin(): boolean { return this._admin.classList.contains("chip"); }
|
||||
set admin(state: boolean) {
|
||||
if (state) {
|
||||
this._admin.classList.add("chip", "~info", "ml-1");
|
||||
this._admin.classList.add("chip", "~info", "ml-4");
|
||||
this._admin.textContent = window.lang.strings("admin");
|
||||
} else {
|
||||
this._admin.classList.remove("chip", "~info", "ml-1");
|
||||
this._admin.classList.remove("chip", "~info", "ml-4");
|
||||
this._admin.textContent = "";
|
||||
}
|
||||
}
|
||||
@@ -97,10 +97,10 @@ class user implements User {
|
||||
get disabled(): boolean { return this._disabled.classList.contains("chip"); }
|
||||
set disabled(state: boolean) {
|
||||
if (state) {
|
||||
this._disabled.classList.add("chip", "~warning", "ml-1");
|
||||
this._disabled.classList.add("chip", "~warning", "ml-4");
|
||||
this._disabled.textContent = window.lang.strings("disabled");
|
||||
} else {
|
||||
this._disabled.classList.remove("chip", "~warning", "ml-1");
|
||||
this._disabled.classList.remove("chip", "~warning", "ml-4");
|
||||
this._disabled.textContent = "";
|
||||
}
|
||||
}
|
||||
@@ -130,30 +130,30 @@ class user implements User {
|
||||
const matrix = this._matrixID != "";
|
||||
if (!telegram && !discord && !matrix) return;
|
||||
let innerHTML = `
|
||||
<i class="icon ri-settings-2-line ml-half dropdown-button"></i>
|
||||
<i class="icon ri-settings-2-line ml-2 dropdown-button"></i>
|
||||
<div class="dropdown manual">
|
||||
<div class="dropdown-display lg">
|
||||
<div class="card ~neutral !low">
|
||||
<div class="card ~neutral @low">
|
||||
<span class="supra sm">${window.lang.strings("contactThrough")}</span>
|
||||
<label class="row switch pb-1 mt-half">
|
||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-email">
|
||||
<label class="row switch pb-4 mt-2">
|
||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-email mr-2">
|
||||
</span>Email</span>
|
||||
</label>
|
||||
<div class="accounts-area-telegram">
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-telegram">
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-telegram mr-2">
|
||||
<span>Telegram</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="accounts-area-discord">
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-discord">
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-discord mr-2">
|
||||
<span>Discord</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="accounts-area-matrix">
|
||||
<label class="row switch pb-1">
|
||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-matrix">
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-matrix mr-2">
|
||||
<span>Matrix</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -188,13 +188,15 @@ class user implements User {
|
||||
this._notifyDropdown.querySelector(".accounts-area-matrix").classList.add("unfocused");
|
||||
return;
|
||||
}
|
||||
const lastNotifyMethod = this._lastNotifyMethod() == "matrix";
|
||||
const lastNotifyMethod = this.lastNotifyMethod() == "matrix";
|
||||
this._matrixID = u;
|
||||
if (!u) {
|
||||
this._notifyDropdown.querySelector(".accounts-area-matrix").classList.add("unfocused");
|
||||
this._matrix.innerHTML = `
|
||||
<span class="chip btn !low">${window.lang.strings("add")}</span>
|
||||
<input type="text" class="input ~neutral !normal stealth-input unfocused" placeholder="@user:riot.im">
|
||||
<div class="table-inline justify-center">
|
||||
<span class="chip btn @low"><i class="ri-link" alt="${window.lang.strings("add")}"></i></span>
|
||||
<input type="text" class="input ~neutral @low stealth-input unfocused" placeholder="@user:riot.im">
|
||||
</div>
|
||||
`;
|
||||
(this._matrix.querySelector("span") as HTMLSpanElement).onclick = this._addMatrix;
|
||||
} else {
|
||||
@@ -212,15 +214,20 @@ class user implements User {
|
||||
|
||||
private _addMatrix = () => {
|
||||
const addButton = this._matrix.querySelector(".btn") as HTMLSpanElement;
|
||||
const icon = this._matrix.querySelector("i");
|
||||
const input = this._matrix.querySelector("input.stealth-input") as HTMLInputElement;
|
||||
const addIcon = addButton.querySelector("i");
|
||||
if (addButton.classList.contains("chip")) {
|
||||
input.classList.remove("unfocused");
|
||||
addButton.innerHTML = `<i class="ri-check-line"></i>`;
|
||||
addIcon.classList.add("ri-check-line");
|
||||
addIcon.classList.remove("ri-link");
|
||||
addButton.classList.remove("chip")
|
||||
if (icon) {
|
||||
icon.classList.add("unfocused");
|
||||
}
|
||||
const outerClickListener = (event: Event) => {
|
||||
if (!(event.target instanceof HTMLElement && (this._matrix.contains(event.target) || addButton.contains(event.target)))) {
|
||||
document.dispatchEvent(new CustomEvent("accounts-reload"));
|
||||
document.removeEventListener("click", outerClickListener);
|
||||
}
|
||||
};
|
||||
document.addEventListener("click", outerClickListener);
|
||||
} else {
|
||||
if (input.value.charAt(0) != "@" || !input.value.includes(":")) return;
|
||||
const send = {
|
||||
@@ -253,11 +260,11 @@ class user implements User {
|
||||
this._notifyDropdown.querySelector(".accounts-area-telegram").classList.add("unfocused");
|
||||
return;
|
||||
}
|
||||
const lastNotifyMethod = this._lastNotifyMethod() == "telegram";
|
||||
const lastNotifyMethod = this.lastNotifyMethod() == "telegram";
|
||||
this._telegramUsername = u;
|
||||
if (!u) {
|
||||
this._notifyDropdown.querySelector(".accounts-area-telegram").classList.add("unfocused");
|
||||
this._telegram.innerHTML = `<span class="chip btn !low">${window.lang.strings("add")}</span>`;
|
||||
this._telegram.innerHTML = `<div class="table-inline justify-center"><span class="chip btn @low"><i class="ri-link" alt="${window.lang.strings("add")}"></i></span></div>`;
|
||||
(this._telegram.querySelector("span") as HTMLSpanElement).onclick = this._addTelegram;
|
||||
} else {
|
||||
this._notifyDropdown.querySelector(".accounts-area-telegram").classList.remove("unfocused");
|
||||
@@ -319,10 +326,10 @@ class user implements User {
|
||||
this._notifyDropdown.querySelector(".accounts-area-discord").classList.add("unfocused");
|
||||
return;
|
||||
}
|
||||
const lastNotifyMethod = this._lastNotifyMethod() == "discord";
|
||||
const lastNotifyMethod = this.lastNotifyMethod() == "discord";
|
||||
this._discordUsername = u;
|
||||
if (!u) {
|
||||
this._discord.innerHTML = `<span class="chip btn !low">Add</span>`;
|
||||
this._discord.innerHTML = `<div class="table-inline justify-center"><span class="chip btn @low"><i class="ri-link" alt="${window.lang.strings("add")}"></i></span></div>`;
|
||||
(this._discord.querySelector("span") as HTMLSpanElement).onclick = () => addDiscord(this.id);
|
||||
this._notifyDropdown.querySelector(".accounts-area-discord").classList.add("unfocused");
|
||||
} else {
|
||||
@@ -380,8 +387,8 @@ class user implements User {
|
||||
this._row = document.createElement("tr") as HTMLTableRowElement;
|
||||
let innerHTML = `
|
||||
<td><input type="checkbox" value=""></td>
|
||||
<td><div class="table-inline"><span class="accounts-username"></span> <span class="accounts-admin"></span> <span class="accounts-disabled"></span></span></td>
|
||||
<td><div class="table-inline"><i class="icon ri-edit-line accounts-email-edit"></i><span class="accounts-email-container ml-half"></span></div></td>
|
||||
<td><div class="table-inline"><span class="accounts-username py-2"></span> <span class="accounts-admin"></span> <span class="accounts-disabled"></span></span></td>
|
||||
<td><div class="table-inline"><i class="icon ri-edit-line accounts-email-edit"></i><span class="accounts-email-container ml-2"></span></div></td>
|
||||
`;
|
||||
if (window.telegramEnabled) {
|
||||
innerHTML += `
|
||||
@@ -400,10 +407,10 @@ class user implements User {
|
||||
}
|
||||
innerHTML += `
|
||||
<td class="accounts-expiry"></td>
|
||||
<td class="accounts-last-active"></td>
|
||||
<td class="accounts-last-active whitespace-nowrap"></td>
|
||||
`;
|
||||
this._row.innerHTML = innerHTML;
|
||||
const emailEditor = `<input type="email" class="input ~neutral !normal stealth-input">`;
|
||||
const emailEditor = `<input type="email" class="input ~neutral @low stealth-input">`;
|
||||
this._check = this._row.querySelector("input[type=checkbox]") as HTMLInputElement;
|
||||
this._username = this._row.querySelector(".accounts-username") as HTMLSpanElement;
|
||||
this._admin = this._row.querySelector(".accounts-admin") as HTMLSpanElement;
|
||||
@@ -423,10 +430,10 @@ class user implements User {
|
||||
if (this._emailEditButton.classList.contains("ri-edit-line")) {
|
||||
this._email.innerHTML = emailEditor;
|
||||
this._email.querySelector("input").value = this._emailAddress;
|
||||
this._email.classList.remove("ml-half");
|
||||
this._email.classList.remove("ml-2");
|
||||
} else {
|
||||
this._email.textContent = this._emailAddress;
|
||||
this._email.classList.add("ml-half");
|
||||
this._email.classList.add("ml-2");
|
||||
}
|
||||
this._emailEditButton.classList.toggle("ri-check-line");
|
||||
this._emailEditButton.classList.toggle("ri-edit-line");
|
||||
@@ -560,12 +567,16 @@ export class accountsList {
|
||||
private _announceTextarea = document.getElementById("textarea-announce") as HTMLTextAreaElement;
|
||||
private _deleteUser = document.getElementById("accounts-delete-user") as HTMLSpanElement;
|
||||
private _disableEnable = document.getElementById("accounts-disable-enable") as HTMLSpanElement;
|
||||
private _enableExpiry = document.getElementById("accounts-enable-expiry") as HTMLSpanElement;
|
||||
private _deleteNotify = document.getElementById("delete-user-notify") as HTMLInputElement;
|
||||
private _deleteReason = document.getElementById("textarea-delete-user") as HTMLTextAreaElement;
|
||||
private _extendExpiry = document.getElementById("accounts-extend-expiry") as HTMLSpanElement;
|
||||
private _enableExpiryNotify = document.getElementById("expiry-extend-enable") as HTMLInputElement;
|
||||
private _enableExpiryReason = document.getElementById("textarea-extend-enable") as HTMLTextAreaElement;
|
||||
private _modifySettings = document.getElementById("accounts-modify-user") as HTMLSpanElement;
|
||||
private _modifySettingsProfile = document.getElementById("radio-use-profile") as HTMLInputElement;
|
||||
private _modifySettingsUser = document.getElementById("radio-use-user") as HTMLInputElement;
|
||||
private _sendPWR = document.getElementById("accounts-send-pwr") as HTMLSpanElement;
|
||||
private _profileSelect = document.getElementById("modify-user-profiles") as HTMLSelectElement;
|
||||
private _userSelect = document.getElementById("modify-user-users") as HTMLSelectElement;
|
||||
private _search = document.getElementById("accounts-search") as HTMLInputElement;
|
||||
@@ -611,7 +622,7 @@ export class accountsList {
|
||||
if (word.includes(":")) {
|
||||
const querySplit = word.split(":")
|
||||
let state = false;
|
||||
if (querySplit[1] == "true") {
|
||||
if (querySplit[1] == "true" || querySplit[1] == "yes") {
|
||||
state = true;
|
||||
}
|
||||
for (let id in this._users) {
|
||||
@@ -698,6 +709,7 @@ export class accountsList {
|
||||
}
|
||||
this._extendExpiry.classList.add("unfocused");
|
||||
this._disableEnable.classList.add("unfocused");
|
||||
this._sendPWR.classList.add("unfocused");
|
||||
} else {
|
||||
let visibleCount = 0;
|
||||
for (let id in this._users) {
|
||||
@@ -719,6 +731,7 @@ export class accountsList {
|
||||
this._announceButton.classList.remove("unfocused");
|
||||
}
|
||||
let anyNonExpiries = list.length == 0 ? true : false;
|
||||
let noContactCount = 0;
|
||||
// Only show enable/disable button if all selected have the same state.
|
||||
this._shouldEnable = this._users[list[0]].disabled
|
||||
let showDisableEnable = true;
|
||||
@@ -732,17 +745,28 @@ export class accountsList {
|
||||
this._disableEnable.classList.add("unfocused");
|
||||
}
|
||||
if (!showDisableEnable && anyNonExpiries) { break; }
|
||||
if (!this._users[id].lastNotifyMethod() && !this._users[id].email) {
|
||||
noContactCount++;
|
||||
}
|
||||
}
|
||||
if (!anyNonExpiries) {
|
||||
this._extendExpiry.classList.remove("unfocused");
|
||||
}
|
||||
// Only show "Send PWR" if a maximum of 1 user selected doesn't have a contact method
|
||||
if (noContactCount > 1) {
|
||||
this._sendPWR.classList.add("unfocused");
|
||||
} else if (window.linkResetEnabled) {
|
||||
this._sendPWR.classList.remove("unfocused");
|
||||
}
|
||||
if (showDisableEnable) {
|
||||
let message: string;
|
||||
if (this._shouldEnable) {
|
||||
this._disableEnable.parentElement.classList.remove("manual");
|
||||
message = window.lang.strings("reEnable");
|
||||
this._disableEnable.classList.add("~positive");
|
||||
this._disableEnable.classList.remove("~warning");
|
||||
} else {
|
||||
this._disableEnable.parentElement.classList.add("manual");
|
||||
message = window.lang.strings("disable");
|
||||
this._disableEnable.classList.add("~warning");
|
||||
this._disableEnable.classList.remove("~positive");
|
||||
@@ -885,7 +909,7 @@ export class accountsList {
|
||||
if (req.readyState == 4) {
|
||||
const preview = document.getElementById("announce-preview") as HTMLDivElement;
|
||||
if (req.status != 200) {
|
||||
preview.innerHTML = `<pre class="preview-content" class="monospace"></pre>`;
|
||||
preview.innerHTML = `<pre class="preview-content" class="font-mono bg-inherit"></pre>`;
|
||||
window.modals.announce.show();
|
||||
this._previewLoaded = false;
|
||||
return;
|
||||
@@ -893,7 +917,7 @@ export class accountsList {
|
||||
|
||||
let templ = req.response as templateEmail;
|
||||
if (!templ.html) {
|
||||
preview.innerHTML = `<pre class="preview-content" class="monospace"></pre>`;
|
||||
preview.innerHTML = `<pre class="preview-content" class="font-mono bg-inherit"></pre>`;
|
||||
this._previewLoaded = false;
|
||||
} else {
|
||||
preview.innerHTML = templ.html;
|
||||
@@ -921,9 +945,9 @@ export class accountsList {
|
||||
dList.textContent = '';
|
||||
for (let name of list) {
|
||||
const el = document.createElement("div") as HTMLDivElement;
|
||||
el.classList.add("flex-expand", "ellipsis", "mt-half");
|
||||
el.classList.add("flex-expand", "ellipsis", "mt-2");
|
||||
el.innerHTML = `
|
||||
<span class="button ~neutral sm full-width accounts-announce-template-button">${name}</span><span class="button ~critical fr ml-1 accounts-announce-template-delete">×</span>
|
||||
<span class="button ~neutral sm full-width accounts-announce-template-button">${name}</span><span class="button ~critical fr ml-4 accounts-announce-template-delete">×</span>
|
||||
`;
|
||||
(el.querySelector("span.accounts-announce-template-button") as HTMLSpanElement).onclick = () => {
|
||||
_get("/users/announce/" + name, null, (req: XMLHttpRequest) => {
|
||||
@@ -952,6 +976,17 @@ export class accountsList {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
private _enableDisableUsers = (users: string[], enable: boolean, notify: boolean, reason: string|null, post: (req: XMLHttpRequest) => void) => {
|
||||
let send = {
|
||||
"users": users,
|
||||
"enabled": enable,
|
||||
"notify": notify
|
||||
};
|
||||
if (reason) send["reason"] = reason;
|
||||
_post("/users/enable", send, post, true);
|
||||
};
|
||||
|
||||
enableDisableUsers = () => {
|
||||
// We can share the delete modal for this
|
||||
const modalHeader = document.getElementById("header-delete-user");
|
||||
@@ -975,13 +1010,7 @@ export class accountsList {
|
||||
form.onsubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
toggleLoader(button);
|
||||
let send = {
|
||||
"users": list,
|
||||
"enabled": this._shouldEnable,
|
||||
"notify": this._deleteNotify.checked,
|
||||
"reason": this._deleteNotify ? this._deleteReason.value : ""
|
||||
};
|
||||
_post("/users/enable", send, (req: XMLHttpRequest) => {
|
||||
this._enableDisableUsers(list, this._shouldEnable, this._deleteNotify.checked, this._deleteNotify ? this._deleteReason.value : null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(button);
|
||||
window.modals.deleteUser.close();
|
||||
@@ -998,7 +1027,7 @@ export class accountsList {
|
||||
}
|
||||
this.reload();
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
window.modals.deleteUser.show();
|
||||
}
|
||||
@@ -1042,6 +1071,63 @@ export class accountsList {
|
||||
};
|
||||
window.modals.deleteUser.show();
|
||||
}
|
||||
|
||||
sendPWR = () => {
|
||||
addLoader(this._sendPWR);
|
||||
let list = this._collectUsers();
|
||||
let manualUser: user;
|
||||
for (let id of list) {
|
||||
let user = this._users[id];
|
||||
console.log(user, user.notify_email, user.notify_matrix, user.notify_discord, user.notify_telegram);
|
||||
if (!user.lastNotifyMethod() && !user.email) {
|
||||
manualUser = user;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const messageBox = document.getElementById("send-pwr-note") as HTMLParagraphElement;
|
||||
let message: string;
|
||||
let send = {
|
||||
users: list
|
||||
};
|
||||
_post("/users/password-reset", send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(this._sendPWR);
|
||||
let link: string;
|
||||
if (req.status == 200) {
|
||||
link = req.response["link"];
|
||||
if (req.response["manual"] as boolean) {
|
||||
message = window.lang.var("strings", "sendPWRManual", manualUser.name);
|
||||
} else {
|
||||
message = window.lang.strings("sendPWRSuccess") + " " + window.lang.strings("sendPWRSuccessManual");
|
||||
}
|
||||
} else if (req.status == 204) {
|
||||
message = window.lang.strings("sendPWRSuccess");
|
||||
} else {
|
||||
window.notifications.customError("errorSendPWR", window.lang.strings("errorFailureCheckLogs"));
|
||||
return;
|
||||
}
|
||||
message += " " + window.lang.strings("sendPWRValidFor");
|
||||
messageBox.textContent = message;
|
||||
let linkButton = document.getElementById("send-pwr-link") as HTMLSpanElement;
|
||||
if (link) {
|
||||
linkButton.classList.remove("unfocused");
|
||||
linkButton.onclick = () => {
|
||||
toClipboard(link);
|
||||
linkButton.textContent = window.lang.strings("copied");
|
||||
linkButton.classList.add("~positive");
|
||||
linkButton.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
linkButton.textContent = window.lang.strings("copy");
|
||||
linkButton.classList.add("~urge");
|
||||
linkButton.classList.remove("~positive");
|
||||
}, 800);
|
||||
};
|
||||
} else {
|
||||
linkButton.classList.add("unfocused");
|
||||
}
|
||||
window.modals.sendPWR.show();
|
||||
}, true);
|
||||
}
|
||||
|
||||
modifyUsers = () => {
|
||||
const modalHeader = document.getElementById("header-modify-user");
|
||||
@@ -1112,18 +1198,27 @@ export class accountsList {
|
||||
window.modals.modifyUser.show();
|
||||
}
|
||||
|
||||
extendExpiry = () => {
|
||||
extendExpiry = (enableUser?: boolean) => {
|
||||
const list = this._collectUsers();
|
||||
let applyList: string[] = [];
|
||||
for (let id of list) {
|
||||
if (this._users[id].expiry) {
|
||||
if (this._users[id].expiry || enableUser) {
|
||||
applyList.push(id);
|
||||
}
|
||||
}
|
||||
document.getElementById("header-extend-expiry").textContent = window.lang.quantity("extendExpiry", applyList.length);
|
||||
const form = document.getElementById("form-extend-expiry") as HTMLFormElement;
|
||||
form.onsubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
this._enableExpiryReason.classList.add("unfocused");
|
||||
let header: string;
|
||||
if (enableUser) {
|
||||
header = window.lang.quantity("reEnableUsers", list.length);
|
||||
this._enableExpiryNotify.parentElement.classList.remove("unfocused");
|
||||
this._enableExpiryNotify.checked = false;
|
||||
this._enableExpiryReason.value = "";
|
||||
} else {
|
||||
header = window.lang.quantity("extendExpiry", applyList.length);
|
||||
this._enableExpiryNotify.parentElement.classList.add("unfocused");
|
||||
}
|
||||
document.getElementById("header-extend-expiry").textContent = header;
|
||||
const extend = () => {
|
||||
let send = { "users": applyList }
|
||||
for (let field of ["months", "days", "hours", "minutes"]) {
|
||||
send[field] = +(document.getElementById("extend-expiry-"+field) as HTMLSelectElement).value;
|
||||
@@ -1139,6 +1234,28 @@ export class accountsList {
|
||||
this.reload();
|
||||
}
|
||||
});
|
||||
};
|
||||
const form = document.getElementById("form-extend-expiry") as HTMLFormElement;
|
||||
form.onsubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
if (enableUser) {
|
||||
this._enableDisableUsers(applyList, true, this._enableExpiryNotify.checked, this._enableExpiryNotify ? this._enableExpiryReason.value : null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200 && req.status != 204) {
|
||||
window.modals.extendExpiry.close();
|
||||
let errorMsg = window.lang.notif("errorFailureCheckLogs");
|
||||
if (!("error" in req.response)) {
|
||||
errorMsg = window.lang.notif("errorPartialFailureCheckLogs");
|
||||
}
|
||||
window.notifications.customError("deleteUserError", errorMsg);
|
||||
return;
|
||||
}
|
||||
extend();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
extend();
|
||||
}
|
||||
}
|
||||
window.modals.extendExpiry.show();
|
||||
}
|
||||
@@ -1171,17 +1288,17 @@ export class accountsList {
|
||||
if (this._modifySettingsProfile.checked) {
|
||||
this._userSelect.parentElement.classList.add("unfocused");
|
||||
this._profileSelect.parentElement.classList.remove("unfocused")
|
||||
profileSpan.classList.add("!high");
|
||||
profileSpan.classList.remove("!normal");
|
||||
userSpan.classList.remove("!high");
|
||||
userSpan.classList.add("!normal");
|
||||
profileSpan.classList.add("@high");
|
||||
profileSpan.classList.remove("@low");
|
||||
userSpan.classList.remove("@high");
|
||||
userSpan.classList.add("@low");
|
||||
} else {
|
||||
this._userSelect.parentElement.classList.remove("unfocused");
|
||||
this._profileSelect.parentElement.classList.add("unfocused");
|
||||
userSpan.classList.add("!high");
|
||||
userSpan.classList.remove("!normal");
|
||||
profileSpan.classList.remove("!high");
|
||||
profileSpan.classList.add("!normal");
|
||||
userSpan.classList.add("@high");
|
||||
userSpan.classList.remove("@low");
|
||||
profileSpan.classList.remove("@high");
|
||||
profileSpan.classList.add("@low");
|
||||
}
|
||||
};
|
||||
this._modifySettingsProfile.onchange = checkSource;
|
||||
@@ -1193,16 +1310,31 @@ export class accountsList {
|
||||
this._announceButton.onclick = this.announce;
|
||||
this._announceButton.classList.add("unfocused");
|
||||
|
||||
this._extendExpiry.onclick = this.extendExpiry;
|
||||
this._extendExpiry.onclick = () => { this.extendExpiry(); };
|
||||
this._extendExpiry.classList.add("unfocused");
|
||||
|
||||
this._disableEnable.onclick = this.enableDisableUsers;
|
||||
this._disableEnable.classList.add("unfocused");
|
||||
|
||||
this._enableExpiry.onclick = () => { this.extendExpiry(true); };
|
||||
this._enableExpiryNotify.onchange = () => {
|
||||
if (this._enableExpiryNotify.checked) {
|
||||
this._enableExpiryReason.classList.remove("unfocused");
|
||||
} else {
|
||||
this._enableExpiryReason.classList.add("unfocused");
|
||||
}
|
||||
};
|
||||
|
||||
if (!window.usernameEnabled) {
|
||||
this._addUserName.classList.add("unfocused");
|
||||
this._addUserName = this._addUserEmail;
|
||||
}
|
||||
|
||||
if (!window.linkResetEnabled) {
|
||||
this._sendPWR.classList.add("unfocused");
|
||||
} else {
|
||||
this._sendPWR.onclick = this.sendPWR;
|
||||
}
|
||||
/*if (!window.emailEnabled) {
|
||||
this._deleteNotify.parentElement.classList.add("unfocused");
|
||||
this._deleteNotify.checked = false;
|
||||
|
||||
@@ -104,14 +104,14 @@ export class notificationBox implements NotificationBox {
|
||||
|
||||
private _error = (message: string): HTMLElement => {
|
||||
const noti = document.createElement('aside');
|
||||
noti.classList.add("aside", "~critical", "!normal", "mt-half", "notification-error");
|
||||
noti.classList.add("aside", "~critical", "@low", "mt-2", "notification-error");
|
||||
let error = "";
|
||||
if (window.lang) {
|
||||
error = window.lang.strings("error") + ":"
|
||||
}
|
||||
noti.innerHTML = `<strong>${error}</strong> ${message}`;
|
||||
const closeButton = document.createElement('span') as HTMLSpanElement;
|
||||
closeButton.classList.add("button", "~critical", "!low", "ml-1");
|
||||
closeButton.classList.add("button", "~critical", "@low", "ml-4");
|
||||
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;
|
||||
closeButton.onclick = () => { this._box.removeChild(noti); };
|
||||
noti.appendChild(closeButton);
|
||||
@@ -120,10 +120,10 @@ export class notificationBox implements NotificationBox {
|
||||
|
||||
private _positive = (bold: string, message: string): HTMLElement => {
|
||||
const noti = document.createElement('aside');
|
||||
noti.classList.add("aside", "~positive", "!normal", "mt-half", "notification-positive");
|
||||
noti.classList.add("aside", "~positive", "@low", "mt-2", "notification-positive");
|
||||
noti.innerHTML = `<strong>${bold}</strong> ${message}`;
|
||||
const closeButton = document.createElement('span') as HTMLSpanElement;
|
||||
closeButton.classList.add("button", "~positive", "!low", "ml-1");
|
||||
closeButton.classList.add("button", "~positive", "@low", "ml-4");
|
||||
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;
|
||||
closeButton.onclick = () => { this._box.removeChild(noti); };
|
||||
noti.appendChild(closeButton);
|
||||
@@ -221,5 +221,3 @@ export function insertText(textarea: HTMLTextAreaElement, text: string) {
|
||||
textarea.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@ export function newDiscordSearch(title: string, description: string, buttonText:
|
||||
}
|
||||
list.innerHTML = ``;
|
||||
addLoader(list);
|
||||
list.parentElement.classList.add("mb-1", "mt-1");
|
||||
list.parentElement.classList.add("mb-4", "mt-4");
|
||||
timer = setTimeout(() => {
|
||||
_get("/users/discord/" + input.value, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
removeLoader(list);
|
||||
list.parentElement.classList.remove("mb-1", "mt-1");
|
||||
list.parentElement.classList.remove("mb-4", "mt-4");
|
||||
return;
|
||||
}
|
||||
const users = req.response["users"] as Array<DiscordUser>;
|
||||
@@ -39,18 +39,18 @@ export function newDiscordSearch(title: string, description: string, buttonText:
|
||||
<td class="img-circle sm">
|
||||
<img class="img-circle" src="${users[i].avatar_url}" width="32" height="32">
|
||||
</td>
|
||||
<td class="w-100 sm">
|
||||
<td class="sm">
|
||||
<p class="content">${users[i].name}</p>
|
||||
</td>
|
||||
<td class="sm">
|
||||
<span id="discord-user-${users[i].id}" class="button ~info !high">${buttonText}</span>
|
||||
<td class="sm float-right">
|
||||
<span id="discord-user-${users[i].id}" class="button ~info @high">${buttonText}</span>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
list.innerHTML = innerHTML;
|
||||
removeLoader(list);
|
||||
list.parentElement.classList.remove("mb-1", "mt-1");
|
||||
list.parentElement.classList.remove("mb-4", "mt-4");
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
const button = document.getElementById(`discord-user-${users[i].id}`) as HTMLInputElement;
|
||||
button.onclick = () => buttonFunction(users[i], event.detail);
|
||||
|
||||
@@ -93,14 +93,14 @@ class DOMInvite implements Invite {
|
||||
const chip = container.querySelector("span.inv-email-chip");
|
||||
const tooltip = container.querySelector("span.content") as HTMLSpanElement;
|
||||
if (address == "") {
|
||||
container.classList.remove("mr-1");
|
||||
container.classList.remove("mr-4");
|
||||
icon.classList.remove("ri-mail-line");
|
||||
icon.classList.remove("ri-mail-close-line");
|
||||
chip.classList.remove("~neutral");
|
||||
chip.classList.remove("~critical");
|
||||
chip.classList.remove("chip");
|
||||
} else {
|
||||
container.classList.add("mr-1");
|
||||
container.classList.add("mr-4");
|
||||
chip.classList.add("chip");
|
||||
if (address.includes("Failed")) {
|
||||
icon.classList.remove("ri-mail-line");
|
||||
@@ -251,14 +251,14 @@ class DOMInvite implements Invite {
|
||||
|
||||
this._header = document.createElement('div') as HTMLDivElement;
|
||||
this._container.appendChild(this._header);
|
||||
this._header.classList.add("card", "~neutral", "!normal", "inv-header", "elem-pad", "no-pad", "flex-expand", "row", "mt-half", "overflow-y");
|
||||
this._header.classList.add("card", "dark:~d_neutral", "@low", "inv-header", "elem-pad", "no-pad", "flex-expand", "row", "mt-2", "overflow-y");
|
||||
|
||||
this._codeArea = document.createElement('div') as HTMLDivElement;
|
||||
this._header.appendChild(this._codeArea);
|
||||
this._codeArea.classList.add("inv-codearea");
|
||||
this._codeArea.innerHTML = `
|
||||
<a class="invite-link code monospace mr-1" href=""></a>
|
||||
<span class="button ~info !normal" title="${window.lang.strings("copy")}"><i class="ri-file-copy-line"></i></span>
|
||||
<a class="invite-link code font-mono bg-inherit mr-4" href=""></a>
|
||||
<span class="button ~info @low" title="${window.lang.strings("copy")}"><i class="ri-file-copy-line"></i></span>
|
||||
`;
|
||||
const copyButton = this._codeArea.querySelector("span.button") as HTMLSpanElement;
|
||||
copyButton.onclick = () => {
|
||||
@@ -284,8 +284,8 @@ class DOMInvite implements Invite {
|
||||
<span class="inv-email-chip"><i></i></span>
|
||||
<span class="content sm"></span>
|
||||
</div>
|
||||
<span class="inv-duration mr-1"></span>
|
||||
<span class="button ~critical !normal inv-delete">${window.lang.strings("delete")}</span>
|
||||
<span class="inv-duration mr-4"></span>
|
||||
<span class="button ~critical @low inv-delete">${window.lang.strings("delete")}</span>
|
||||
<label>
|
||||
<i class="icon clickable ri-arrow-down-s-line not-rotated"></i>
|
||||
<input class="inv-toggle-details unfocused" type="checkbox">
|
||||
@@ -304,7 +304,7 @@ class DOMInvite implements Invite {
|
||||
|
||||
this._details = document.createElement('div') as HTMLDivElement;
|
||||
this._container.appendChild(this._details);
|
||||
this._details.classList.add("card", "~neutral", "!normal", "mt-half", "no-pad", "inv-details");
|
||||
this._details.classList.add("card", "~neutral", "@low", "mt-2", "no-pad", "inv-details");
|
||||
const detailsInner = document.createElement('div') as HTMLDivElement;
|
||||
this._details.appendChild(detailsInner);
|
||||
detailsInner.classList.add("inv-row", "flex-expand", "row", "elem-pad", "align-top");
|
||||
@@ -313,8 +313,8 @@ class DOMInvite implements Invite {
|
||||
detailsInner.appendChild(this._left);
|
||||
this._left.classList.add("inv-profilearea");
|
||||
let innerHTML = `
|
||||
<p class="supra mb-1 top">${window.lang.strings("profile")}</p>
|
||||
<div class="select ~neutral !normal inv-profileselect inline-block">
|
||||
<p class="supra mb-2 top">${window.lang.strings("profile")}</p>
|
||||
<div class="select ~neutral @low inv-profileselect inline-block mb-2">
|
||||
<select>
|
||||
<option value="noProfile" selected>${window.lang.strings("inviteNoProfile")}</option>
|
||||
</select>
|
||||
@@ -322,7 +322,7 @@ class DOMInvite implements Invite {
|
||||
`;
|
||||
if (window.notificationsEnabled) {
|
||||
innerHTML += `
|
||||
<p class="label supra">${window.lang.strings("notifyEvent")}</p>
|
||||
<p class="label supra mb-2">${window.lang.strings("notifyEvent")}</p>
|
||||
<label class="switch block">
|
||||
<input class="inv-notify-expiry" type="checkbox">
|
||||
<span>${window.lang.strings("notifyInviteExpiry")}</span>
|
||||
@@ -348,14 +348,14 @@ class DOMInvite implements Invite {
|
||||
detailsInner.appendChild(this._middle);
|
||||
this._middle.classList.add("block");
|
||||
this._middle.innerHTML = `
|
||||
<p class="supra mb-1 top">${window.lang.strings("inviteDateCreated")} <strong class="inv-created"></strong></p>
|
||||
<p class="supra mb-1">${window.lang.strings("inviteRemainingUses")} <strong class="inv-remaining"></strong></p>
|
||||
<p class="supra mb-1"><span class="user-expiry"></span> <strong class="user-expiry-time"></strong></p>
|
||||
<p class="supra mb-4 top">${window.lang.strings("inviteDateCreated")} <strong class="inv-created"></strong></p>
|
||||
<p class="supra mb-4">${window.lang.strings("inviteRemainingUses")} <strong class="inv-remaining"></strong></p>
|
||||
<p class="supra mb-4"><span class="user-expiry"></span> <strong class="user-expiry-time"></strong></p>
|
||||
`;
|
||||
|
||||
this._right = document.createElement('div') as HTMLDivElement;
|
||||
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">${window.lang.strings("inviteUsersCreated")}</strong>`;
|
||||
this._userTable = document.createElement('div') as HTMLDivElement;
|
||||
this._right.appendChild(this._userTable);
|
||||
@@ -425,9 +425,9 @@ export class inviteList implements inviteList {
|
||||
this._list.classList.add("empty");
|
||||
this._list.innerHTML = `
|
||||
<div class="inv inv-empty">
|
||||
<div class="card ~neutral !normal inv-header flex-expand mt-half">
|
||||
<div class="card dark:~d_neutral @low inv-header flex-expand mt-2">
|
||||
<div class="inv-codearea">
|
||||
<span class="code monospace">${window.lang.strings("inviteNoInvites")}</span>
|
||||
<span class="code font-mono bg-inherit">${window.lang.strings("inviteNoInvites")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -787,17 +787,17 @@ export class createInvite {
|
||||
if (this._invDurationButton.checked) {
|
||||
this._invDuration.classList.remove("unfocused");
|
||||
this._userExpiry.classList.add("unfocused");
|
||||
invSpan.classList.add("!high");
|
||||
invSpan.classList.remove("!normal");
|
||||
userSpan.classList.add("!normal");
|
||||
userSpan.classList.remove("!high");
|
||||
invSpan.classList.add("@high");
|
||||
invSpan.classList.remove("@low");
|
||||
userSpan.classList.add("@low");
|
||||
userSpan.classList.remove("@high");
|
||||
} else if (this._userExpiryButton.checked) {
|
||||
this._userExpiry.classList.remove("unfocused");
|
||||
this._invDuration.classList.add("unfocused");
|
||||
invSpan.classList.add("!normal");
|
||||
invSpan.classList.remove("!high");
|
||||
userSpan.classList.add("!high");
|
||||
userSpan.classList.remove("!normal");
|
||||
invSpan.classList.add("@low");
|
||||
invSpan.classList.remove("@high");
|
||||
userSpan.classList.add("@high");
|
||||
userSpan.classList.remove("@low");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ export const loadLangSelector = (page: string) => {
|
||||
t12.checked = false;
|
||||
}
|
||||
}
|
||||
let queryString = new URLSearchParams(window.location.search);
|
||||
if (queryString.has("lang")) queryString.delete("lang");
|
||||
_get("/lang/" + page, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
@@ -77,7 +79,9 @@ export const loadLangSelector = (page: string) => {
|
||||
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 lang-link">${req.response[code]}</a>`;
|
||||
queryString.set("lang", code);
|
||||
innerHTML += `<a href="?${queryString.toString()}" class="button w-100 al justify-start ~neutral mb-2 lang-link">${req.response[code]}</a>`;
|
||||
queryString.delete("lang");
|
||||
}
|
||||
list.innerHTML = innerHTML;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ interface Profile {
|
||||
admin: boolean;
|
||||
libraries: string;
|
||||
fromUser: string;
|
||||
ombi: boolean;
|
||||
}
|
||||
|
||||
class profile implements Profile {
|
||||
@@ -11,8 +12,10 @@ class profile implements Profile {
|
||||
private _name: HTMLElement;
|
||||
private _adminChip: HTMLSpanElement;
|
||||
private _libraries: HTMLTableDataCellElement;
|
||||
private _ombiButton: HTMLSpanElement;
|
||||
private _fromUser: HTMLTableDataCellElement;
|
||||
private _defaultRadio: HTMLInputElement;
|
||||
private _ombi: boolean;
|
||||
|
||||
get name(): string { return this._name.textContent; }
|
||||
set name(v: string) { this._name.textContent = v; }
|
||||
@@ -20,10 +23,10 @@ class profile implements Profile {
|
||||
get admin(): boolean { return this._adminChip.classList.contains("chip"); }
|
||||
set admin(state: boolean) {
|
||||
if (state) {
|
||||
this._adminChip.classList.add("chip", "~info", "ml-half");
|
||||
this._adminChip.classList.add("chip", "~info", "ml-2");
|
||||
this._adminChip.textContent = "Admin";
|
||||
} else {
|
||||
this._adminChip.classList.remove("chip", "~info", "ml-half");
|
||||
this._adminChip.classList.remove("chip", "~info", "ml-2");
|
||||
this._adminChip.textContent = "";
|
||||
}
|
||||
}
|
||||
@@ -31,6 +34,21 @@ class profile implements Profile {
|
||||
get libraries(): string { return this._libraries.textContent; }
|
||||
set libraries(v: string) { this._libraries.textContent = v; }
|
||||
|
||||
get ombi(): boolean { return this._ombi; }
|
||||
set ombi(v: boolean) {
|
||||
if (!window.ombiEnabled) return;
|
||||
this._ombi = v;
|
||||
if (v) {
|
||||
this._ombiButton.textContent = window.lang.strings("delete");
|
||||
this._ombiButton.classList.add("~critical");
|
||||
this._ombiButton.classList.remove("~neutral");
|
||||
} else {
|
||||
this._ombiButton.textContent = window.lang.strings("add");
|
||||
this._ombiButton.classList.add("~neutral");
|
||||
this._ombiButton.classList.remove("~critical");
|
||||
}
|
||||
}
|
||||
|
||||
get fromUser(): string { return this._fromUser.textContent; }
|
||||
set fromUser(v: string) { this._fromUser.textContent = v; }
|
||||
|
||||
@@ -39,20 +57,28 @@ class profile implements Profile {
|
||||
|
||||
constructor(name: string, p: Profile) {
|
||||
this._row = document.createElement("tr") as HTMLTableRowElement;
|
||||
this._row.innerHTML = `
|
||||
let innerHTML = `
|
||||
<td><b class="profile-name"></b> <span class="profile-admin"></span></td>
|
||||
<td><input type="radio" name="profile-default"></td>
|
||||
`;
|
||||
if (window.ombiEnabled) innerHTML += `
|
||||
<td><span class="button @low profile-ombi"></span></td>
|
||||
`;
|
||||
innerHTML += `
|
||||
<td class="profile-from ellipsis"></td>
|
||||
<td class="profile-libraries"></td>
|
||||
<td><span class="button ~critical !normal">${window.lang.strings("delete")}</span></td>
|
||||
<td><span class="button ~critical @low">${window.lang.strings("delete")}</span></td>
|
||||
`;
|
||||
this._row.innerHTML = innerHTML;
|
||||
this._name = this._row.querySelector("b.profile-name");
|
||||
this._adminChip = this._row.querySelector("span.profile-admin") as HTMLSpanElement;
|
||||
this._libraries = this._row.querySelector("td.profile-libraries") as HTMLTableDataCellElement;
|
||||
if (window.ombiEnabled)
|
||||
this._ombiButton = this._row.querySelector("span.profile-ombi") as HTMLSpanElement;
|
||||
this._fromUser = this._row.querySelector("td.profile-from") as HTMLTableDataCellElement;
|
||||
this._defaultRadio = this._row.querySelector("input[type=radio]") as HTMLInputElement;
|
||||
this._defaultRadio.onclick = () => document.dispatchEvent(new CustomEvent("profiles-default", { detail: this.name }));
|
||||
(this._row.querySelector("span.button") as HTMLSpanElement).onclick = this.delete;
|
||||
(this._row.querySelector("span.\\~critical") as HTMLSpanElement).onclick = this.delete;
|
||||
|
||||
this.update(name, p);
|
||||
}
|
||||
@@ -62,8 +88,11 @@ class profile implements Profile {
|
||||
this.admin = p.admin;
|
||||
this.fromUser = p.fromUser;
|
||||
this.libraries = p.libraries;
|
||||
this.ombi = p.ombi;
|
||||
}
|
||||
|
||||
setOmbiFunc = (ombiFunc: (ombi: boolean) => void) => { this._ombiButton.onclick = () => ombiFunc(this._ombi); }
|
||||
|
||||
remove = () => { document.dispatchEvent(new CustomEvent("profiles-delete", { detail: this._name })); this._row.remove(); }
|
||||
|
||||
delete = () => _delete("/profiles", { "name": this.name }, (req: XMLHttpRequest) => {
|
||||
@@ -89,6 +118,7 @@ export class ProfileEditor {
|
||||
private _createButton = document.getElementById("button-profile-create") as HTMLSpanElement;
|
||||
private _profiles: { [name: string]: profile } = {};
|
||||
private _default: string;
|
||||
private _ombiProfiles: ombiProfiles;
|
||||
|
||||
private _createForm = document.getElementById("form-add-profile") as HTMLFormElement;
|
||||
private _profileName = document.getElementById("add-profile-name") as HTMLInputElement;
|
||||
@@ -126,6 +156,23 @@ export class ProfileEditor {
|
||||
this._profiles[name].update(name, resp.profiles[name]);
|
||||
} else {
|
||||
this._profiles[name] = new profile(name, resp.profiles[name]);
|
||||
if (window.ombiEnabled)
|
||||
this._profiles[name].setOmbiFunc((ombi: boolean) => {
|
||||
if (ombi) {
|
||||
this._ombiProfiles.delete(name, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 204) {
|
||||
window.notifications.customError("errorDeleteOmbi", window.lang.notif("errorUnknown"));
|
||||
return;
|
||||
}
|
||||
this._profiles[name].ombi = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.modals.profiles.close();
|
||||
this._ombiProfiles.load(name);
|
||||
}
|
||||
});
|
||||
this._table.appendChild(this._profiles[name].asElement());
|
||||
}
|
||||
}
|
||||
@@ -159,6 +206,9 @@ export class ProfileEditor {
|
||||
this.load();
|
||||
});
|
||||
|
||||
if (window.ombiEnabled)
|
||||
this._ombiProfiles = new ombiProfiles();
|
||||
|
||||
this._createButton.onclick = () => _get("/users", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200 || req.status == 204) {
|
||||
@@ -202,3 +252,61 @@ export class ProfileEditor {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
interface ombiUser {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ombiProfiles {
|
||||
private _form: HTMLFormElement;
|
||||
private _select: HTMLSelectElement;
|
||||
private _users: { [id: string]: string } = {};
|
||||
private _currentProfile: string;
|
||||
|
||||
constructor() {
|
||||
this._form = document.getElementById("form-ombi-defaults") as HTMLFormElement;
|
||||
this._form.onsubmit = this.send;
|
||||
this._select = this._form.querySelector("select") as HTMLSelectElement;
|
||||
}
|
||||
send = () => {
|
||||
const button = this._form.querySelector("span.submit") as HTMLSpanElement;
|
||||
toggleLoader(button);
|
||||
let resp = {} as ombiUser;
|
||||
resp.id = this._select.value;
|
||||
resp.name = this._users[resp.id];
|
||||
_post("/profiles/ombi/" + this._currentProfile, resp, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(button);
|
||||
if (req.status == 200 || req.status == 204) {
|
||||
window.notifications.customSuccess("ombiDefaults", window.lang.notif("setOmbiProfile"));
|
||||
} else {
|
||||
window.notifications.customError("ombiDefaults", window.lang.notif("errorSetOmbiProfile"));
|
||||
}
|
||||
window.modals.ombiProfile.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delete = (profile: string, post?: (req: XMLHttpRequest) => void) => _delete("/profiles/ombi/" + profile, null, post);
|
||||
|
||||
load = (profile: string) => {
|
||||
this._currentProfile = profile;
|
||||
_get("/ombi/users", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200 && "users" in req.response) {
|
||||
const users = req.response["users"] as ombiUser[];
|
||||
let innerHTML = "";
|
||||
for (let user of users) {
|
||||
this._users[user.id] = user.name;
|
||||
innerHTML += `<option value="${user.id}">${user.name}</option>`;
|
||||
}
|
||||
this._select.innerHTML = innerHTML;
|
||||
window.modals.ombiProfile.show();
|
||||
} else {
|
||||
window.notifications.customError("ombiLoadError", window.lang.notif("errorLoadOmbiUsers"))
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user