mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-29 12:30:11 +00:00
Compare commits
3 Commits
2d6b1717db
...
29775e2e75
Author | SHA1 | Date | |
---|---|---|---|
29775e2e75 | |||
9d62b70daa | |||
301f502052 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,6 +8,7 @@ data/static/*.css
|
|||||||
data/static/*.js
|
data/static/*.js
|
||||||
data/static/*.js.map
|
data/static/*.js.map
|
||||||
data/static/ts/
|
data/static/ts/
|
||||||
|
data/static/modules/
|
||||||
!data/static/setup.js
|
!data/static/setup.js
|
||||||
data/config-base.json
|
data/config-base.json
|
||||||
data/config-default.ini
|
data/config-default.ini
|
||||||
|
@ -16,7 +16,7 @@ before:
|
|||||||
- python3 scss/compile.py
|
- python3 scss/compile.py
|
||||||
- python3 mail/generate.py
|
- python3 mail/generate.py
|
||||||
- python3 version.py {{.Version}} version.go
|
- python3 version.py {{.Version}} version.go
|
||||||
- bash -c 'npx esbuild ts/* --outdir=data/static --minify'
|
- bash -c 'npx esbuild ts/*.ts ts/modules/*.ts --outdir=data/static --minify'
|
||||||
- go get -u github.com/swaggo/swag/cmd/swag
|
- go get -u github.com/swaggo/swag/cmd/swag
|
||||||
- swag init -g main.go
|
- swag init -g main.go
|
||||||
builds:
|
builds:
|
||||||
@ -41,10 +41,11 @@ archives:
|
|||||||
- data/*
|
- data/*
|
||||||
- data/templates/*
|
- data/templates/*
|
||||||
- data/static/*
|
- data/static/*
|
||||||
|
- data/static/modules/*
|
||||||
checksum:
|
checksum:
|
||||||
name_template: 'checksums.txt'
|
name_template: 'checksums.txt'
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ .Tag }}-testing"
|
name_template: "git-{{.ShortCommit}}"
|
||||||
changelog:
|
changelog:
|
||||||
sort: asc
|
sort: asc
|
||||||
filters:
|
filters:
|
||||||
|
6
Makefile
6
Makefile
@ -18,12 +18,15 @@ email:
|
|||||||
|
|
||||||
typescript:
|
typescript:
|
||||||
$(info Compiling typescript)
|
$(info Compiling typescript)
|
||||||
npx esbuild ts/* --outdir=data/static --minify
|
npx esbuild ts/*.ts ts/modules/*.ts --outdir=data/static --minify
|
||||||
-rm -r data/static/ts
|
-rm -r data/static/ts
|
||||||
|
-rm -r data/static/typings
|
||||||
-rm data/static/*.map
|
-rm data/static/*.map
|
||||||
|
|
||||||
ts-debug:
|
ts-debug:
|
||||||
-npx tsc -p ts/ --sourceMap
|
-npx tsc -p ts/ --sourceMap
|
||||||
|
-rm -r data/static/ts
|
||||||
|
-rm -r data/static/typings
|
||||||
cp -r ts data/static/
|
cp -r ts data/static/
|
||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
@ -51,3 +54,4 @@ install:
|
|||||||
cp -r build $(DESTDIR)/jfa-go
|
cp -r build $(DESTDIR)/jfa-go
|
||||||
|
|
||||||
all: configuration sass email version typescript swagger compile copy
|
all: configuration sass email version typescript swagger compile copy
|
||||||
|
debug: configuration sass email version ts-debug swagger compile copy
|
||||||
|
4
api.go
4
api.go
@ -626,12 +626,12 @@ func (app *appContext) DeleteProfile(gc *gin.Context) {
|
|||||||
// @tags Invites
|
// @tags Invites
|
||||||
func (app *appContext) GetInvites(gc *gin.Context) {
|
func (app *appContext) GetInvites(gc *gin.Context) {
|
||||||
app.debug.Println("Invites requested")
|
app.debug.Println("Invites requested")
|
||||||
current_time := time.Now()
|
currentTime := time.Now()
|
||||||
app.storage.loadInvites()
|
app.storage.loadInvites()
|
||||||
app.checkInvites()
|
app.checkInvites()
|
||||||
var invites []inviteDTO
|
var invites []inviteDTO
|
||||||
for code, inv := range app.storage.invites {
|
for code, inv := range app.storage.invites {
|
||||||
_, _, days, hours, minutes, _ := timeDiff(inv.ValidTill, current_time)
|
_, _, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
|
||||||
invite := inviteDTO{
|
invite := inviteDTO{
|
||||||
Code: code,
|
Code: code,
|
||||||
Days: days,
|
Days: days,
|
||||||
|
@ -31,11 +31,11 @@
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
{{ if .bs5 }}
|
{{ if .bs5 }}
|
||||||
var bsVersion = 5;
|
window.bsVersion = 5;
|
||||||
{{ else }}
|
{{ else }}
|
||||||
var bsVersion = 4;
|
window.bsVersion = 4;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
var cssFile = "{{ .cssFile }}";
|
window.cssFile = "{{ .cssFile }}";
|
||||||
var css = document.createElement('link');
|
var css = document.createElement('link');
|
||||||
css.setAttribute('rel', 'stylesheet');
|
css.setAttribute('rel', 'stylesheet');
|
||||||
css.setAttribute('type', 'text/css');
|
css.setAttribute('type', 'text/css');
|
||||||
@ -465,27 +465,19 @@
|
|||||||
<p>{{ .contactMessage }}</p>
|
<p>{{ .contactMessage }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="common.js"></script>
|
<script>
|
||||||
<script>
|
window.bs5 = {{ .bs5 }};
|
||||||
var availableProfiles = [];
|
window.availableProfiles = [];
|
||||||
{{ if .notifications }}
|
{{ if .notifications }}
|
||||||
var notifications_enabled = true;
|
window.notifications_enabled = true;
|
||||||
{{ else }}
|
{{ else }}
|
||||||
var notifications_enabled = false;
|
window.notifications_enabled = false;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</script>
|
</script>
|
||||||
{{ if .bs5 }}
|
<script src="admin.js" type="module"></script>
|
||||||
<script src="bs5.js"></script>
|
<script src="invites.js" type="module"></script>
|
||||||
{{ else }}
|
|
||||||
<script src="bs4.js"></script>
|
|
||||||
{{ end }}
|
|
||||||
<script src="animation.js"></script>
|
|
||||||
<script src="accounts.js"></script>
|
|
||||||
<script src="invites.js"></script>
|
|
||||||
<script src="admin.js"></script>
|
|
||||||
<script src="settings.js"></script>
|
|
||||||
{{ if .ombiEnabled }}
|
{{ if .ombiEnabled }}
|
||||||
<script src="ombi.js"></script>
|
<script src="ombi.js" type="module"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
7
data/templates/form-base.html
Normal file
7
data/templates/form-base.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{{ define "form-base" }}
|
||||||
|
<script>
|
||||||
|
window.bs5 = {{ .bs5 }};
|
||||||
|
window.usernameEnabled = {{ .username }};
|
||||||
|
</script>
|
||||||
|
<script src="form.js" type="module"></script>
|
||||||
|
{{ end }}
|
1
data/templates/form-loader.html
Normal file
1
data/templates/form-loader.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
{{ template "form.html" . }}
|
@ -14,11 +14,11 @@
|
|||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link rel="stylesheet" type="text/css" href="{{ .cssFile }}">
|
<link rel="stylesheet" type="text/css" href="{{ .cssFile }}">
|
||||||
{{ if not .bs5 }}
|
{{ if not .settings.bs5 }}
|
||||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
||||||
{{ if .bs5 }}
|
{{ if .settings.bs5 }}
|
||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
|
||||||
@ -74,9 +74,9 @@
|
|||||||
<form action="#" method="POST" id="accountForm">
|
<form action="#" method="POST" id="accountForm">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputEmail">Email</label>
|
<label for="inputEmail">Email</label>
|
||||||
<input type="email" class="form-control" id="{{ if .username }}inputEmail{{ else }}inputUsername{{ end }}" name="{{ if .username }}email{{ else }}username{{ end }}" placeholder="Email" value="{{ .email }}" required>
|
<input type="email" class="form-control" id="{{ if .settings.username }}inputEmail{{ else }}inputUsername{{ end }}" name="{{ if .settings.username }}email{{ else }}username{{ end }}" placeholder="Email" value="{{ .email }}" required>
|
||||||
</div>
|
</div>
|
||||||
{{ if .username }}
|
{{ if .settings.username }}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="inputUsername">Username</label>
|
<label for="inputUsername">Username</label>
|
||||||
<input type="username" class="form-control" id="inputUsername" name="username" placeholder="Username" required>
|
<input type="username" class="form-control" id="inputUsername" name="username" placeholder="Username" required>
|
||||||
@ -114,10 +114,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="common.js"></script>
|
<script>
|
||||||
<script>
|
window.validationStrings = {
|
||||||
var usernameEnabled = {{ .username }}
|
|
||||||
var validationStrings = {
|
|
||||||
"length": {
|
"length": {
|
||||||
"singular": "Must have at least {n} character",
|
"singular": "Must have at least {n} character",
|
||||||
"plural": "Must have a least {n} characters"
|
"plural": "Must have a least {n} characters"
|
||||||
@ -138,8 +136,9 @@
|
|||||||
"singular": "Must have at least {n} special character",
|
"singular": "Must have at least {n} special character",
|
||||||
"plural": "Must have at least {n} special characters"
|
"plural": "Must have at least {n} special characters"
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
<script src="form.js"></script>
|
{{ template "form-base" .settings }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
7
go.mod
7
go.mod
@ -12,8 +12,8 @@ require (
|
|||||||
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
|
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
|
||||||
github.com/gin-gonic/gin v1.6.3
|
github.com/gin-gonic/gin v1.6.3
|
||||||
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
||||||
github.com/go-openapi/spec v0.19.9 // indirect
|
github.com/go-openapi/spec v0.19.10 // indirect
|
||||||
github.com/go-openapi/swag v0.19.9 // indirect
|
github.com/go-openapi/swag v0.19.10 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.4.0 // indirect
|
github.com/go-playground/validator/v10 v10.4.0 // indirect
|
||||||
github.com/golang/protobuf v1.4.2
|
github.com/golang/protobuf v1.4.2
|
||||||
github.com/google/uuid v1.1.2 // indirect
|
github.com/google/uuid v1.1.2 // indirect
|
||||||
@ -35,8 +35,7 @@ require (
|
|||||||
github.com/ugorji/go v1.1.9 // indirect
|
github.com/ugorji/go v1.1.9 // indirect
|
||||||
github.com/urfave/cli/v2 v2.2.0 // indirect
|
github.com/urfave/cli/v2 v2.2.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
|
||||||
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 // indirect
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9 // indirect
|
||||||
golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 // indirect
|
|
||||||
google.golang.org/protobuf v1.25.0 // indirect
|
google.golang.org/protobuf v1.25.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.61.0
|
gopkg.in/ini.v1 v1.61.0
|
||||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||||
|
9
go.sum
9
go.sum
@ -69,12 +69,16 @@ github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOA
|
|||||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||||
github.com/go-openapi/spec v0.19.9 h1:9z9cbFuZJ7AcvOHKIY+f6Aevb4vObNDkTEyoMfO7rAc=
|
github.com/go-openapi/spec v0.19.9 h1:9z9cbFuZJ7AcvOHKIY+f6Aevb4vObNDkTEyoMfO7rAc=
|
||||||
github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28=
|
github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28=
|
||||||
|
github.com/go-openapi/spec v0.19.10 h1:pcNevfYytLaOQuTju0wm6OqcqU/E/pRwuSGigrLTI28=
|
||||||
|
github.com/go-openapi/spec v0.19.10/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28=
|
||||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
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.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE=
|
github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE=
|
||||||
github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
|
github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
|
||||||
|
github.com/go-openapi/swag v0.19.10 h1:A1SWXruroGP15P1sOiegIPbaKio+G9N5TwWTFaVPmAU=
|
||||||
|
github.com/go-openapi/swag v0.19.10/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
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.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 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||||
@ -272,12 +276,15 @@ golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARV
|
|||||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 h1:5kGOVHlq0euqwzgTC9Vu15p6fV1Wi0ArVi8da2urnVg=
|
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 h1:5kGOVHlq0euqwzgTC9Vu15p6fV1Wi0ArVi8da2urnVg=
|
||||||
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -325,6 +332,8 @@ golang.org/x/tools v0.0.0-20201008184944-d01b322e6f06 h1:w9ail9jFLaySAm61Zjhciu0
|
|||||||
golang.org/x/tools v0.0.0-20201008184944-d01b322e6f06/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
golang.org/x/tools v0.0.0-20201008184944-d01b322e6f06/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||||
golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 h1:ZB1XYzdDo7c/O48jzjMkvIjnC120Z9/CwgDWhePjQdQ=
|
golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 h1:ZB1XYzdDo7c/O48jzjMkvIjnC120Z9/CwgDWhePjQdQ=
|
||||||
golang.org/x/tools v0.0.0-20201017001424-6003fad69a88/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
golang.org/x/tools v0.0.0-20201017001424-6003fad69a88/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||||
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9 h1:sEvmEcJVKBNUvgCUClbUQeHOAa9U0I2Ce1BooMvVCY4=
|
||||||
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -50,9 +50,9 @@
|
|||||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||||
},
|
},
|
||||||
"@types/jquery": {
|
"@types/jquery": {
|
||||||
"version": "3.5.1",
|
"version": "3.5.3",
|
||||||
"resolved": "https://registry.npm.taobao.org/@types/jquery/download/@types/jquery-3.5.1.tgz",
|
"resolved": "https://registry.npm.taobao.org/@types/jquery/download/@types/jquery-3.5.3.tgz?cache=0&sync_timestamp=1602524936372&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fjquery%2Fdownload%2F%40types%2Fjquery-3.5.3.tgz",
|
||||||
"integrity": "sha1-zrsFes9QccQOQ58w6EDFejDUBsM=",
|
"integrity": "sha1-rcxkfkxnW9nrrn+5gOnKddWO6Mc=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/sizzle": "*"
|
"@types/sizzle": "*"
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/hrfee/jellyfin-accounts#readme",
|
"homepage": "https://github.com/hrfee/jellyfin-accounts#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/jquery": "^3.5.1",
|
"@types/jquery": "^3.5.3",
|
||||||
"autoprefixer": "^9.8.5",
|
"autoprefixer": "^9.8.5",
|
||||||
"bootstrap": "^5.0.0-alpha1",
|
"bootstrap": "^5.0.0-alpha1",
|
||||||
"bootstrap4": "npm:bootstrap@^4.5.0",
|
"bootstrap4": "npm:bootstrap@^4.5.0",
|
||||||
|
2
pwval.go
2
pwval.go
@ -38,7 +38,7 @@ func (vd *Validator) validate(password string) map[string]bool {
|
|||||||
} else if unicode.IsLower(c) {
|
} else if unicode.IsLower(c) {
|
||||||
count["lowercase"] += 1
|
count["lowercase"] += 1
|
||||||
} else if unicode.IsNumber(c) {
|
} else if unicode.IsNumber(c) {
|
||||||
count["numbers"] += 1
|
count["number"] += 1
|
||||||
} else {
|
} else {
|
||||||
for _, s := range vd.specialChars {
|
for _, s := range vd.specialChars {
|
||||||
if c == s {
|
if c == s {
|
||||||
|
128
ts/accounts.ts
128
ts/accounts.ts
@ -1,25 +1,17 @@
|
|||||||
const checkCheckboxes = (): void => {
|
import { checkCheckboxes, populateUsers, populateRadios } from "./modules/accounts.js";
|
||||||
const defaultsButton = document.getElementById('accountsTabSetDefaults');
|
import { _post, _get, _delete, rmAttr, addAttr } from "./modules/common.js";
|
||||||
const deleteButton = document.getElementById('accountsTabDelete');
|
import { populateProfiles } from "./modules/settings.js";
|
||||||
const checkboxes: NodeListOf<HTMLInputElement> = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]:checked');
|
import { Focus, Unfocus, createEl, storeDefaults } from "./modules/admin.js";
|
||||||
let checked = checkboxes.length;
|
|
||||||
if (checked == 0) {
|
interface aWindow extends Window {
|
||||||
Unfocus(defaultsButton);
|
changeEmail(icon: HTMLElement, id: string): void;
|
||||||
Unfocus(deleteButton);
|
|
||||||
} else {
|
|
||||||
Focus(defaultsButton);
|
|
||||||
Focus(deleteButton);
|
|
||||||
if (checked == 1) {
|
|
||||||
deleteButton.textContent = 'Delete User';
|
|
||||||
} else {
|
|
||||||
deleteButton.textContent = 'Delete Users';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare var window: aWindow;
|
||||||
|
|
||||||
const validateEmail = (email: string): boolean => /\S+@\S+\.\S+/.test(email);
|
const validateEmail = (email: string): boolean => /\S+@\S+\.\S+/.test(email);
|
||||||
|
|
||||||
function changeEmail(icon: HTMLElement, id: string): void {
|
window.changeEmail = (icon: HTMLElement, id: string): void => {
|
||||||
const iconContent = icon.outerHTML;
|
const iconContent = icon.outerHTML;
|
||||||
icon.setAttribute('class', '');
|
icon.setAttribute('class', '');
|
||||||
const entry = icon.nextElementSibling as HTMLInputElement;
|
const entry = icon.nextElementSibling as HTMLInputElement;
|
||||||
@ -79,84 +71,6 @@ function changeEmail(icon: HTMLElement, id: string): void {
|
|||||||
icon.parentNode.appendChild(cross);
|
icon.parentNode.appendChild(cross);
|
||||||
};
|
};
|
||||||
|
|
||||||
var jfUsers: Array<Object>;
|
|
||||||
|
|
||||||
function populateUsers(): void {
|
|
||||||
const acList = document.getElementById('accountsList');
|
|
||||||
acList.innerHTML = `
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<strong>Getting Users...</strong>
|
|
||||||
<div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
Unfocus(acList.parentNode.querySelector('thead'));
|
|
||||||
const accountsList = document.createElement('tbody');
|
|
||||||
accountsList.id = 'accountsList';
|
|
||||||
const generateEmail = (id: string, name: string, email: string): string => {
|
|
||||||
let entry: HTMLDivElement = document.createElement('div');
|
|
||||||
entry.id = 'email_' + id;
|
|
||||||
let emailValue: string = email;
|
|
||||||
if (emailValue == undefined) {
|
|
||||||
emailValue = "";
|
|
||||||
}
|
|
||||||
entry.innerHTML = `
|
|
||||||
<i class="fa fa-edit d-inline-block icon-button" style="margin-right: 2%;" onclick="changeEmail(this, '${id}')"></i>
|
|
||||||
<input type="email" class="form-control-plaintext form-control-sm text-muted d-inline-block addressText" id="address_${id}" style="width: auto;" value="${emailValue}" readonly>
|
|
||||||
`;
|
|
||||||
return entry.outerHTML;
|
|
||||||
};
|
|
||||||
const template = (id: string, username: string, email: string, lastActive: string, admin: boolean): string => {
|
|
||||||
let isAdmin = "No";
|
|
||||||
if (admin) {
|
|
||||||
isAdmin = "Yes";
|
|
||||||
}
|
|
||||||
let fci = "form-check-input";
|
|
||||||
if (bsVersion != 5) {
|
|
||||||
fci = "";
|
|
||||||
}
|
|
||||||
return `
|
|
||||||
<td nowrap="nowrap" class="align-middle" scope="row"><input class="${fci}" type="checkbox" value="" id="select_${id}" onclick="checkCheckboxes();"></td>
|
|
||||||
<td nowrap="nowrap" class="align-middle">${username}</td>
|
|
||||||
<td nowrap="nowrap" class="align-middle">${generateEmail(id, name, email)}</td>
|
|
||||||
<td nowrap="nowrap" class="align-middle">${lastActive}</td>
|
|
||||||
<td nowrap="nowrap" class="align-middle">${isAdmin}</td>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
_get("/users", null, function (): void {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
jfUsers = this.response['users'];
|
|
||||||
for (const user of jfUsers) {
|
|
||||||
let tr = document.createElement('tr');
|
|
||||||
tr.innerHTML = template(user['id'], user['name'], user['email'], user['last_active'], user['admin']);
|
|
||||||
accountsList.appendChild(tr);
|
|
||||||
}
|
|
||||||
Focus(acList.parentNode.querySelector('thead'));
|
|
||||||
acList.replaceWith(accountsList);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateRadios(): void {
|
|
||||||
const radioList = document.getElementById('defaultUserRadios');
|
|
||||||
radioList.textContent = '';
|
|
||||||
let first = true;
|
|
||||||
for (const i in jfUsers) {
|
|
||||||
const user = jfUsers[i];
|
|
||||||
const radio = document.createElement('div');
|
|
||||||
radio.classList.add('form-check');
|
|
||||||
let checked = '';
|
|
||||||
if (first) {
|
|
||||||
checked = 'checked';
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
radio.innerHTML = `
|
|
||||||
<input class="form-check-input" type="radio" name="defaultRadios" id="default_${user['id']}" ${checked}>
|
|
||||||
<label class="form-check-label" for="default_${user['id']}">${user['name']}</label>`;
|
|
||||||
radioList.appendChild(radio);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(<HTMLInputElement>document.getElementById('selectAll')).onclick = function (): void {
|
(<HTMLInputElement>document.getElementById('selectAll')).onclick = function (): void {
|
||||||
const checkboxes: NodeListOf<HTMLInputElement> = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]');
|
const checkboxes: NodeListOf<HTMLInputElement> = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]');
|
||||||
for (let i = 0; i < checkboxes.length; i++) {
|
for (let i = 0; i < checkboxes.length; i++) {
|
||||||
@ -217,18 +131,18 @@ function populateRadios(): void {
|
|||||||
}
|
}
|
||||||
setTimeout((): void => {
|
setTimeout((): void => {
|
||||||
Unfocus(deleteButton);
|
Unfocus(deleteButton);
|
||||||
deleteModal.hide();
|
window.Modals.delete.hide();
|
||||||
}, 4000);
|
}, 4000);
|
||||||
} else {
|
} else {
|
||||||
Unfocus(deleteButton);
|
Unfocus(deleteButton);
|
||||||
deleteModal.hide()
|
window.Modals.delete.hide()
|
||||||
}
|
}
|
||||||
populateUsers();
|
populateUsers();
|
||||||
checkCheckboxes();
|
checkCheckboxes();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
deleteModal.show();
|
window.Modals.delete.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
(<HTMLInputElement>document.getElementById('selectAll')).checked = false;
|
(<HTMLInputElement>document.getElementById('selectAll')).checked = false;
|
||||||
@ -250,9 +164,9 @@ function populateRadios(): void {
|
|||||||
populateProfiles(true);
|
populateProfiles(true);
|
||||||
const profileSelect = document.getElementById('profileSelect') as HTMLSelectElement;
|
const profileSelect = document.getElementById('profileSelect') as HTMLSelectElement;
|
||||||
profileSelect.textContent = '';
|
profileSelect.textContent = '';
|
||||||
for (let i = 0; i < availableProfiles.length; i++) {
|
for (let i = 0; i < window.availableProfiles.length; i++) {
|
||||||
profileSelect.innerHTML += `
|
profileSelect.innerHTML += `
|
||||||
<option value="${availableProfiles[i]}" ${(i == 0) ? "selected" : ""}>${availableProfiles[i]}</option>
|
<option value="${window.availableProfiles[i]}" ${(i == 0) ? "selected" : ""}>${window.availableProfiles[i]}</option>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
document.getElementById('defaultsTitle').textContent = `Apply settings to ${userIDs.length} ${userString}`;
|
document.getElementById('defaultsTitle').textContent = `Apply settings to ${userIDs.length} ${userString}`;
|
||||||
@ -266,7 +180,7 @@ function populateRadios(): void {
|
|||||||
Unfocus(document.getElementById('defaultUserRadiosBox'));
|
Unfocus(document.getElementById('defaultUserRadiosBox'));
|
||||||
Unfocus(document.getElementById('newProfileBox'));
|
Unfocus(document.getElementById('newProfileBox'));
|
||||||
document.getElementById('storeDefaults').onclick = (): void => storeDefaults(userIDs);
|
document.getElementById('storeDefaults').onclick = (): void => storeDefaults(userIDs);
|
||||||
userDefaultsModal.show();
|
window.Modals.userDefaults.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
(<HTMLSelectElement>document.getElementById('defaultsSource')).addEventListener('change', function (): void {
|
(<HTMLSelectElement>document.getElementById('defaultsSource')).addEventListener('change', function (): void {
|
||||||
@ -311,7 +225,7 @@ function populateRadios(): void {
|
|||||||
rmAttr(button, 'btn-success');
|
rmAttr(button, 'btn-success');
|
||||||
addAttr(button, 'btn-primary');
|
addAttr(button, 'btn-primary');
|
||||||
button.textContent = ogText;
|
button.textContent = ogText;
|
||||||
newUserModal.hide();
|
window.Modals.newUser.hide();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
populateUsers();
|
populateUsers();
|
||||||
} else {
|
} else {
|
||||||
@ -338,11 +252,5 @@ function populateRadios(): void {
|
|||||||
if (document.getElementById('newUserName') != null) {
|
if (document.getElementById('newUserName') != null) {
|
||||||
(<HTMLInputElement>document.getElementById('newUserName')).value = '';
|
(<HTMLInputElement>document.getElementById('newUserName')).value = '';
|
||||||
}
|
}
|
||||||
newUserModal.show();
|
window.Modals.newUser.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
131
ts/admin.ts
131
ts/admin.ts
@ -1,8 +1,19 @@
|
|||||||
// Set in admin.html
|
import { serializeForm, rmAttr, addAttr, _get, _post, _delete } from "./modules/common.js";
|
||||||
var cssFile: string;
|
import { Focus, Unfocus } from "./modules/admin.js";
|
||||||
|
import { toggleCSS } from "./modules/animation.js";
|
||||||
|
import { populateUsers, checkCheckboxes } from "./modules/accounts.js";
|
||||||
|
import { generateInvites, addOptions, checkDuration } from "./modules/invites.js";
|
||||||
|
import { showSetting, openSettings } from "./modules/settings.js";
|
||||||
|
import { BS4 } from "./modules/bs4.js";
|
||||||
|
import { BS5 } from "./modules/bs5.js";
|
||||||
|
import "./accounts.js";
|
||||||
|
import "./settings.js";
|
||||||
|
|
||||||
const Focus = (el: HTMLElement): void => rmAttr(el, 'unfocused');
|
interface aWindow extends Window {
|
||||||
const Unfocus = (el: HTMLElement): void => addAttr(el, 'unfocused');
|
toClipboard(str: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var window: aWindow;
|
||||||
|
|
||||||
interface TabSwitcher {
|
interface TabSwitcher {
|
||||||
els: Array<HTMLDivElement>;
|
els: Array<HTMLDivElement>;
|
||||||
@ -35,27 +46,43 @@ const tabs: TabSwitcher = {
|
|||||||
tabs.focus(1);
|
tabs.focus(1);
|
||||||
},
|
},
|
||||||
settings: (): void => openSettings(document.getElementById('settingsSections'), document.getElementById('settingsContent'), (): void => {
|
settings: (): void => openSettings(document.getElementById('settingsSections'), document.getElementById('settingsContent'), (): void => {
|
||||||
triggerTooltips();
|
window.BS.triggerTooltips();
|
||||||
showSetting("ui");
|
showSetting("ui");
|
||||||
tabs.focus(2);
|
tabs.focus(2);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// for (let i = 0; i < tabs.els.length; i++) {
|
window.bsVersion = window.bs5 ? 5 : 4
|
||||||
// tabs.tabButtons[i].onclick = (): void => tabs.focus(i);
|
|
||||||
// }
|
if (window.bs5) {
|
||||||
|
window.BS = new BS5;
|
||||||
|
} else {
|
||||||
|
window.BS = new BS4;
|
||||||
|
window.BS.Compat();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Modals = {} as BSModals;
|
||||||
|
|
||||||
|
window.Modals.login = window.BS.newModal('login');
|
||||||
|
window.Modals.userDefaults = window.BS.newModal('userDefaults');
|
||||||
|
window.Modals.users = window.BS.newModal('users');
|
||||||
|
window.Modals.restart = window.BS.newModal('restartModal');
|
||||||
|
window.Modals.refresh = window.BS.newModal('refreshModal');
|
||||||
|
window.Modals.about = window.BS.newModal('aboutModal');
|
||||||
|
window.Modals.delete = window.BS.newModal('deleteModal');
|
||||||
|
window.Modals.newUser = window.BS.newModal('newUserModal');
|
||||||
|
|
||||||
tabs.tabButtons[0].onclick = tabs.invites;
|
tabs.tabButtons[0].onclick = tabs.invites;
|
||||||
tabs.tabButtons[1].onclick = tabs.accounts;
|
tabs.tabButtons[1].onclick = tabs.accounts;
|
||||||
tabs.tabButtons[2].onclick = tabs.settings;
|
tabs.tabButtons[2].onclick = tabs.settings;
|
||||||
|
|
||||||
|
|
||||||
tabs.invites();
|
tabs.invites();
|
||||||
|
|
||||||
// Predefined colors for the theme button.
|
// Predefined colors for the theme button.
|
||||||
var buttonColor: string = "custom";
|
var buttonColor: string = "custom";
|
||||||
if (cssFile.includes("jf")) {
|
if (window.cssFile.includes("jf")) {
|
||||||
buttonColor = "rgb(255,255,255)";
|
buttonColor = "rgb(255,255,255)";
|
||||||
} else if (cssFile == ("bs" + bsVersion + ".css")) {
|
} else if (window.cssFile == ("bs" + window.bsVersion + ".css")) {
|
||||||
buttonColor = "rgb(16,16,16)";
|
buttonColor = "rgb(16,16,16)";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,20 +97,11 @@ if (buttonColor != "custom") {
|
|||||||
document.getElementById('headerButtons').appendChild(switchButton);
|
document.getElementById('headerButtons').appendChild(switchButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
var loginModal = createModal('login');
|
|
||||||
var userDefaultsModal = createModal('userDefaults');
|
|
||||||
var usersModal = createModal('users');
|
|
||||||
var restartModal = createModal('restartModal');
|
|
||||||
var refreshModal = createModal('refreshModal');
|
|
||||||
var aboutModal = createModal('aboutModal');
|
|
||||||
var deleteModal = createModal('deleteModal');
|
|
||||||
var newUserModal = createModal('newUserModal');
|
|
||||||
|
|
||||||
var availableProfiles: Array<string>;
|
var availableProfiles: Array<string>;
|
||||||
|
|
||||||
window["token"] = "";
|
window["token"] = "";
|
||||||
|
|
||||||
function toClipboard(str: string): void {
|
window.toClipboard = (str: string): void => {
|
||||||
const el = document.createElement('textarea') as HTMLTextAreaElement;
|
const el = document.createElement('textarea') as HTMLTextAreaElement;
|
||||||
el.value = str;
|
el.value = str;
|
||||||
el.readOnly = true;
|
el.readOnly = true;
|
||||||
@ -123,7 +141,7 @@ function login(username: string, password: string, modal: boolean, button?: HTML
|
|||||||
button.textContent = "Login";
|
button.textContent = "Login";
|
||||||
}, 4000);
|
}, 4000);
|
||||||
} else {
|
} else {
|
||||||
loginModal.show();
|
window.Modals.login.show();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const data = this.response;
|
const data = this.response;
|
||||||
@ -137,7 +155,7 @@ function login(username: string, password: string, modal: boolean, button?: HTML
|
|||||||
minutes.value = "30";
|
minutes.value = "30";
|
||||||
checkDuration();
|
checkDuration();
|
||||||
if (modal) {
|
if (modal) {
|
||||||
loginModal.hide();
|
window.Modals.login.hide();
|
||||||
}
|
}
|
||||||
Focus(document.getElementById('logoutButton'));
|
Focus(document.getElementById('logoutButton'));
|
||||||
}
|
}
|
||||||
@ -149,12 +167,6 @@ function login(username: string, password: string, modal: boolean, button?: HTML
|
|||||||
req.send();
|
req.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEl(html: string): HTMLElement {
|
|
||||||
let div = document.createElement('div') as HTMLDivElement;
|
|
||||||
div.innerHTML = html;
|
|
||||||
return div.firstElementChild as HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
(document.getElementById('loginForm') as HTMLFormElement).onsubmit = function (): boolean {
|
(document.getElementById('loginForm') as HTMLFormElement).onsubmit = function (): boolean {
|
||||||
window.token = "";
|
window.token = "";
|
||||||
const details = serializeForm('loginForm');
|
const details = serializeForm('loginForm');
|
||||||
@ -169,70 +181,11 @@ function createEl(html: string): HTMLElement {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
function storeDefaults(users: string | Array<string>): void {
|
|
||||||
// not sure if this does anything, but w/e
|
|
||||||
this.disabled = true;
|
|
||||||
this.innerHTML =
|
|
||||||
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="margin-right: 0.5rem;"></span>' +
|
|
||||||
'Loading...';
|
|
||||||
const button = document.getElementById('storeDefaults') as HTMLButtonElement;
|
|
||||||
let data = { "homescreen": false };
|
|
||||||
if ((document.getElementById('defaultsSource') as HTMLSelectElement).value == 'profile') {
|
|
||||||
data["from"] = "profile";
|
|
||||||
data["profile"] = (document.getElementById('profileSelect') as HTMLSelectElement).value;
|
|
||||||
} else {
|
|
||||||
const radio = document.querySelector('input[name=defaultRadios]:checked') as HTMLInputElement
|
|
||||||
let id = radio.id.replace("default_", "");
|
|
||||||
data["from"] = "user";
|
|
||||||
data["id"] = id;
|
|
||||||
}
|
|
||||||
if (users != "all") {
|
|
||||||
data["apply_to"] = users;
|
|
||||||
}
|
|
||||||
if ((document.getElementById('storeDefaultHomescreen') as HTMLInputElement).checked) {
|
|
||||||
data["homescreen"] = true;
|
|
||||||
}
|
|
||||||
_post("/users/settings", data, function (): void {
|
|
||||||
if (this.readyState == 4) {
|
|
||||||
if (this.status == 200 || this.status == 204) {
|
|
||||||
button.textContent = "Success";
|
|
||||||
addAttr(button, "btn-success");
|
|
||||||
rmAttr(button, "btn-danger");
|
|
||||||
rmAttr(button, "btn-primary");
|
|
||||||
button.disabled = false;
|
|
||||||
setTimeout((): void => {
|
|
||||||
button.textContent = "Submit";
|
|
||||||
addAttr(button, "btn-primary");
|
|
||||||
rmAttr(button, "btn-success");
|
|
||||||
button.disabled = false;
|
|
||||||
userDefaultsModal.hide();
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
|
||||||
if ("error" in this.response) {
|
|
||||||
button.textContent = this.response["error"];
|
|
||||||
} else if (("policy" in this.response) || ("homescreen" in this.response)) {
|
|
||||||
button.textContent = "Failed (check console)";
|
|
||||||
} else {
|
|
||||||
button.textContent = "Failed";
|
|
||||||
}
|
|
||||||
addAttr(button, "btn-danger");
|
|
||||||
rmAttr(button, "btn-primary");
|
|
||||||
setTimeout((): void => {
|
|
||||||
button.textContent = "Submit";
|
|
||||||
addAttr(button, "btn-primary");
|
|
||||||
rmAttr(button, "btn-danger");
|
|
||||||
button.disabled = false;
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
generateInvites(true);
|
generateInvites(true);
|
||||||
|
|
||||||
login("", "", false, null, (status: number): void => {
|
login("", "", false, null, (status: number): void => {
|
||||||
if (!(status == 200 || status == 204)) {
|
if (!(status == 200 || status == 204)) {
|
||||||
loginModal.show();
|
window.Modals.login.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
36
ts/bs4.ts
36
ts/bs4.ts
@ -1,36 +0,0 @@
|
|||||||
var bsVersion = 4;
|
|
||||||
|
|
||||||
const send_to_addess_enabled = document.getElementById('send_to_addess_enabled');
|
|
||||||
if (send_to_addess_enabled) {
|
|
||||||
send_to_addess_enabled.classList.remove("form-check-input");
|
|
||||||
}
|
|
||||||
const multiUseEnabled = document.getElementById('multiUseEnabled');
|
|
||||||
if (multiUseEnabled) {
|
|
||||||
multiUseEnabled.classList.remove("form-check-input");
|
|
||||||
}
|
|
||||||
|
|
||||||
function createModal(id: string, find?: boolean): any {
|
|
||||||
$(`#${id}`).on("shown.bs.modal", (): void => document.body.classList.add("modal-open"));
|
|
||||||
return {
|
|
||||||
show: function (): any {
|
|
||||||
const temp = ($(`#${id}`) as any).modal("show");
|
|
||||||
return temp;
|
|
||||||
},
|
|
||||||
hide: function (): any {
|
|
||||||
return ($(`#${id}`) as any).modal("hide");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggerTooltips(): void {
|
|
||||||
const checkboxes = [].slice.call(document.getElementById('settingsContent').querySelectorAll('input[type="checkbox"]'));
|
|
||||||
for (const i in checkboxes) {
|
|
||||||
checkboxes[i].click();
|
|
||||||
checkboxes[i].click();
|
|
||||||
}
|
|
||||||
const tooltips = [].slice.call(document.querySelectorAll('a[data-toggle="tooltip"]'));
|
|
||||||
tooltips.map((el: HTMLAnchorElement): any => {
|
|
||||||
return ($(el) as any).tooltip();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
34
ts/bs5.ts
34
ts/bs5.ts
@ -1,34 +0,0 @@
|
|||||||
declare var bootstrap: any;
|
|
||||||
|
|
||||||
var bsVersion = 5;
|
|
||||||
|
|
||||||
function createModal(id: string, find?: boolean): any {
|
|
||||||
let modal: any;
|
|
||||||
if (find) {
|
|
||||||
modal = bootstrap.Modal.getInstance(document.getElementById(id));
|
|
||||||
} else {
|
|
||||||
modal = new bootstrap.Modal(document.getElementById(id));
|
|
||||||
}
|
|
||||||
document.getElementById(id).addEventListener('shown.bs.modal', (): void => document.body.classList.add("modal-open"));
|
|
||||||
return {
|
|
||||||
modal: modal,
|
|
||||||
show: function (): any {
|
|
||||||
const temp = this.modal.show();
|
|
||||||
return temp;
|
|
||||||
},
|
|
||||||
hide: function (): any { return this.modal.hide(); }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggerTooltips(): void {
|
|
||||||
const checkboxes = [].slice.call(document.getElementById('settingsContent').querySelectorAll('input[type="checkbox"]'));
|
|
||||||
for (const i in checkboxes) {
|
|
||||||
checkboxes[i].click();
|
|
||||||
checkboxes[i].click();
|
|
||||||
}
|
|
||||||
const tooltips = [].slice.call(document.querySelectorAll('a[data-toggle="tooltip"]'));
|
|
||||||
tooltips.map((el: HTMLAnchorElement): any => {
|
|
||||||
return new bootstrap.Tooltip(el);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
53
ts/form.ts
53
ts/form.ts
@ -1,3 +1,14 @@
|
|||||||
|
import { serializeForm, _post, _get, _delete, addAttr, rmAttr } from "./modules/common.js";
|
||||||
|
import { BS5 } from "./modules/bs5.js";
|
||||||
|
import { BS4 } from "./modules/bs4.js";
|
||||||
|
|
||||||
|
interface formWindow extends Window {
|
||||||
|
usernameEnabled: boolean;
|
||||||
|
validationStrings: pwValStrings;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var window: formWindow;
|
||||||
|
|
||||||
interface pwValString {
|
interface pwValString {
|
||||||
singular: string;
|
singular: string;
|
||||||
plural: string;
|
plural: string;
|
||||||
@ -7,9 +18,6 @@ interface pwValStrings {
|
|||||||
length, uppercase, lowercase, number, special: pwValString;
|
length, uppercase, lowercase, number, special: pwValString;
|
||||||
}
|
}
|
||||||
|
|
||||||
var validationStrings: pwValStrings;
|
|
||||||
var bsVersion: number;
|
|
||||||
|
|
||||||
var defaultPwValStrings: pwValStrings = {
|
var defaultPwValStrings: pwValStrings = {
|
||||||
length: {
|
length: {
|
||||||
singular: "Must have at least {n} character",
|
singular: "Must have at least {n} character",
|
||||||
@ -45,49 +53,30 @@ const toggleSpinner = (): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let key in validationStrings) {
|
for (let key in window.validationStrings) {
|
||||||
if (validationStrings[key].singular == "" || !(validationStrings[key].plural.includes("{n}"))) {
|
if (window.validationStrings[key].singular == "" || !(window.validationStrings[key].plural.includes("{n}"))) {
|
||||||
validationStrings[key].singular = defaultPwValStrings[key].singular;
|
window.validationStrings[key].singular = defaultPwValStrings[key].singular;
|
||||||
}
|
}
|
||||||
if (validationStrings[key].plural == "" || !(validationStrings[key].plural.includes("{n}"))) {
|
if (window.validationStrings[key].plural == "" || !(window.validationStrings[key].plural.includes("{n}"))) {
|
||||||
validationStrings[key].plural = defaultPwValStrings[key].plural;
|
window.validationStrings[key].plural = defaultPwValStrings[key].plural;
|
||||||
}
|
}
|
||||||
let el = document.getElementById(key) as HTMLUListElement;
|
let el = document.getElementById(key) as HTMLUListElement;
|
||||||
if (el) {
|
if (el) {
|
||||||
const min: number = +el.getAttribute("min");
|
const min: number = +el.getAttribute("min");
|
||||||
let text = "";
|
let text = "";
|
||||||
if (min == 1) {
|
if (min == 1) {
|
||||||
text = validationStrings[key].singular.replace("{n}", "1");
|
text = window.validationStrings[key].singular.replace("{n}", "1");
|
||||||
} else {
|
} else {
|
||||||
text = validationStrings[key].plural.replace("{n}", min.toString());
|
text = window.validationStrings[key].plural.replace("{n}", min.toString());
|
||||||
}
|
}
|
||||||
(document.getElementById(key).children[0] as HTMLDivElement).textContent = text;
|
(document.getElementById(key).children[0] as HTMLDivElement).textContent = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Modal {
|
window.BS = window.bs5 ? new BS5 : new BS4;
|
||||||
show: () => void;
|
var successBox: BSModal = window.BS.newModal('successBox');;
|
||||||
hide: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
var successBox: Modal;
|
|
||||||
|
|
||||||
if (bsVersion == 5) {
|
|
||||||
var bootstrap: any;
|
|
||||||
successBox = new bootstrap.Modal(document.getElementById('successBox'));
|
|
||||||
} else if (bsVersion == 4) {
|
|
||||||
successBox = {
|
|
||||||
show: (): void => {
|
|
||||||
($('#successBox') as any).modal('show');
|
|
||||||
},
|
|
||||||
hide: (): void => {
|
|
||||||
($('#successBox') as any).modal('hide');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var code = window.location.href.split('/').pop();
|
var code = window.location.href.split('/').pop();
|
||||||
var usernameEnabled: boolean;
|
|
||||||
|
|
||||||
(document.getElementById('accountForm') as HTMLFormElement).addEventListener('submit', (event: any): boolean => {
|
(document.getElementById('accountForm') as HTMLFormElement).addEventListener('submit', (event: any): boolean => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -98,7 +87,7 @@ var usernameEnabled: boolean;
|
|||||||
toggleSpinner();
|
toggleSpinner();
|
||||||
let send: Object = serializeForm('accountForm');
|
let send: Object = serializeForm('accountForm');
|
||||||
send["code"] = code;
|
send["code"] = code;
|
||||||
if (!usernameEnabled) {
|
if (!window.usernameEnabled) {
|
||||||
send["email"] = send["username"];
|
send["email"] = send["username"];
|
||||||
}
|
}
|
||||||
_post("/newUser", send, function (): void {
|
_post("/newUser", send, function (): void {
|
||||||
|
311
ts/invites.ts
311
ts/invites.ts
@ -1,297 +1,11 @@
|
|||||||
// Actually defined by templating in admin.html, this is just to avoid errors from tsc.
|
import { serializeForm, rmAttr, addAttr, _get, _post, _delete } from "./modules/common.js";
|
||||||
var notifications_enabled: any;
|
import { generateInvites, checkDuration } from "./modules/invites.js";
|
||||||
|
|
||||||
interface Invite {
|
interface aWindow extends Window {
|
||||||
code?: string;
|
setProfile(el: HTMLElement): void;
|
||||||
expiresIn?: string;
|
|
||||||
empty: boolean;
|
|
||||||
remainingUses?: string;
|
|
||||||
email?: string;
|
|
||||||
usedBy?: Array<Array<string>>;
|
|
||||||
created?: string;
|
|
||||||
notifyExpiry?: boolean;
|
|
||||||
notifyCreation?: boolean;
|
|
||||||
profile?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyInvite = (): Invite => { return { code: "None", empty: true } as Invite; }
|
declare var window: aWindow;
|
||||||
|
|
||||||
function parseInvite(invite: Object): Invite {
|
|
||||||
let inv: Invite = { code: invite["code"], empty: false, };
|
|
||||||
if (invite["email"]) {
|
|
||||||
inv.email = invite["email"];
|
|
||||||
}
|
|
||||||
let time = ""
|
|
||||||
const f = ["days", "hours", "minutes"];
|
|
||||||
for (const i in f) {
|
|
||||||
if (invite[f[i]] != 0) {
|
|
||||||
time += `${invite[f[i]]}${f[i][0]} `;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inv.expiresIn = `Expires in ${time.slice(0, -1)}`;
|
|
||||||
if (invite["no-limit"]) {
|
|
||||||
inv.remainingUses = "∞";
|
|
||||||
} else if ("remaining-uses" in invite) {
|
|
||||||
inv.remainingUses = invite["remaining-uses"];
|
|
||||||
}
|
|
||||||
if ("used-by" in invite) {
|
|
||||||
inv.usedBy = invite["used-by"];
|
|
||||||
}
|
|
||||||
if ("created" in invite) {
|
|
||||||
inv.created = invite["created"];
|
|
||||||
}
|
|
||||||
if ("notify-expiry" in invite) {
|
|
||||||
inv.notifyExpiry = invite["notify-expiry"];
|
|
||||||
}
|
|
||||||
if ("notify-creation" in invite) {
|
|
||||||
inv.notifyCreation = invite["notify-creation"];
|
|
||||||
}
|
|
||||||
if ("profile" in invite) {
|
|
||||||
inv.profile = invite["profile"];
|
|
||||||
}
|
|
||||||
return inv;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setNotify(el: HTMLElement): void {
|
|
||||||
let send = {};
|
|
||||||
let code: string;
|
|
||||||
let notifyType: string;
|
|
||||||
if (el.id.includes("Expiry")) {
|
|
||||||
code = el.id.replace("_notifyExpiry", "");
|
|
||||||
notifyType = "notify-expiry";
|
|
||||||
} else if (el.id.includes("Creation")) {
|
|
||||||
code = el.id.replace("_notifyCreation", "");
|
|
||||||
notifyType = "notify-creation";
|
|
||||||
}
|
|
||||||
send[code] = {};
|
|
||||||
send[code][notifyType] = (el as HTMLInputElement).checked;
|
|
||||||
_post("/invites/notify", send, function (): void {
|
|
||||||
if (this.readyState == 4 && this.status != 200) {
|
|
||||||
(el as HTMLInputElement).checked = !(el as HTMLInputElement).checked;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function genUsedBy(usedBy: Array<Array<string>>): string {
|
|
||||||
let uB = "";
|
|
||||||
if (usedBy && usedBy.length != 0) {
|
|
||||||
uB = `
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<li class="list-group-item py-1">Users created:</li>
|
|
||||||
`;
|
|
||||||
for (const i in usedBy) {
|
|
||||||
uB += `
|
|
||||||
<li class="list-group-item py-1 disabled">
|
|
||||||
<div class="d-flex float-left">${usedBy[i][0]}</div>
|
|
||||||
<div class="d-flex float-right">${usedBy[i][1]}</div>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
uB += `</ul>`
|
|
||||||
}
|
|
||||||
return uB;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addItem(invite: Invite): void {
|
|
||||||
const links = document.getElementById('invites');
|
|
||||||
const container = document.createElement('div') as HTMLDivElement;
|
|
||||||
container.id = invite.code;
|
|
||||||
const item = document.createElement('div') as HTMLDivElement;
|
|
||||||
item.classList.add('list-group-item', 'd-flex', 'justify-content-between', 'd-inline-block');
|
|
||||||
let link = "";
|
|
||||||
let innerHTML = `<a>None</a>`;
|
|
||||||
if (invite.empty) {
|
|
||||||
item.innerHTML = `
|
|
||||||
<div class="d-flex align-items-center font-monospace" style="width: 40%;">
|
|
||||||
${innerHTML}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
container.appendChild(item);
|
|
||||||
links.appendChild(container);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
link = window.location.href.split('#')[0] + "invite/" + invite.code;
|
|
||||||
innerHTML = `
|
|
||||||
<div class="d-flex align-items-center font-monospace" style="width: 40%;">
|
|
||||||
<a class="invite-link" href="${link}">${invite.code.replace(/-/g, '-')}</a>
|
|
||||||
<i class="fa fa-clipboard icon-button" onclick="toClipboard('${link}')" style="margin-right: 0.5rem; margin-left: 0.5rem;"></i>
|
|
||||||
`;
|
|
||||||
if (invite.email) {
|
|
||||||
let email = invite.email;
|
|
||||||
if (!invite.email.includes("Failed to send to")) {
|
|
||||||
email = `Sent to ${email}`;
|
|
||||||
}
|
|
||||||
innerHTML += `
|
|
||||||
<span class="text-muted" style="margin-left: 0.4rem; font-style: italic; font-size: 0.8rem;">${email}</span>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
innerHTML += `
|
|
||||||
</div>
|
|
||||||
<div style="text-align: right;">
|
|
||||||
<span id="${invite.code}_expiry" style="margin-right: 1rem;">${invite.expiresIn}</span>
|
|
||||||
<div style="display: inline-block;">
|
|
||||||
<button class="btn btn-outline-danger" onclick="deleteInvite('${invite.code}')">Delete</button>
|
|
||||||
<i class="fa fa-angle-down collapsed icon-button not-rotated" style="padding: 1rem; margin: -1rem -1rem -1rem 0;" data-toggle="collapse" aria-expanded="false" data-target="#${CSS.escape(invite.code)}_collapse" onclick="rotateButton(this)"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
item.innerHTML = innerHTML;
|
|
||||||
container.appendChild(item);
|
|
||||||
|
|
||||||
let profiles = `
|
|
||||||
<label class="input-group-text" for="profile_${CSS.escape(invite.code)}">Profile: </label>
|
|
||||||
<select class="form-select" id="profile_${CSS.escape(invite.code)}" onchange="setProfile(this)">
|
|
||||||
<option value="NoProfile" selected>No Profile</option>
|
|
||||||
`;
|
|
||||||
for (const i in availableProfiles) {
|
|
||||||
let selected = "";
|
|
||||||
if (availableProfiles[i] == invite.profile) {
|
|
||||||
selected = "selected";
|
|
||||||
}
|
|
||||||
profiles += `<option value="${availableProfiles[i]}" ${selected}>${availableProfiles[i]}</option>`;
|
|
||||||
}
|
|
||||||
profiles += `</select>`;
|
|
||||||
|
|
||||||
let dateCreated: string;
|
|
||||||
if (invite.created) {
|
|
||||||
dateCreated = `<li class="list-group-item py-1">Created: ${invite.created}</li>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let middle: string;
|
|
||||||
if (notifications_enabled) {
|
|
||||||
middle = `
|
|
||||||
<div class="col" id="${CSS.escape(invite.code)}_notifyButtons">
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
Notify on:
|
|
||||||
<li class="list-group-item py-1 form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" value="" id="${CSS.escape(invite.code)}_notifyExpiry" onclick="setNotify(this)" ${invite.notifyExpiry ? "checked" : ""}>
|
|
||||||
<label class="form-check-label" for="${CSS.escape(invite.code)}_notifyExpiry">Expiry</label>
|
|
||||||
</li>
|
|
||||||
<li class="list-group-item py-1 form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" value="" id="${CSS.escape(invite.code)}_notifyCreation" onclick="setNotify(this)" ${invite.notifyCreation ? "checked" : ""}>
|
|
||||||
<label class="form-check-label" for="${CSS.escape(invite.code)}_notifyCreation">User creation</label>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let right: string = genUsedBy(invite.usedBy)
|
|
||||||
|
|
||||||
const dropdown = document.createElement('div') as HTMLDivElement;
|
|
||||||
dropdown.id = `${CSS.escape(invite.code)}_collapse`;
|
|
||||||
dropdown.classList.add("collapse");
|
|
||||||
dropdown.innerHTML = `
|
|
||||||
<div class="container row align-items-start card-body">
|
|
||||||
<div class="col">
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<li class="input-group py-1">
|
|
||||||
${profiles}
|
|
||||||
</li>
|
|
||||||
${dateCreated}
|
|
||||||
<li class="list-group-item py-1" id="${CSS.escape(invite.code)}_remainingUses">Remaining uses: ${invite.remainingUses}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
${middle}
|
|
||||||
<div class="col" id="${CSS.escape(invite.code)}_usersCreated">
|
|
||||||
${right}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
container.appendChild(dropdown);
|
|
||||||
links.appendChild(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateInvite(invite: Invite): void {
|
|
||||||
document.getElementById(invite.code + "_expiry").textContent = invite.expiresIn;
|
|
||||||
const remainingUses: any = document.getElementById(CSS.escape(invite.code) + "_remainingUses");
|
|
||||||
if (remainingUses) {
|
|
||||||
remainingUses.textContent = `Remaining uses: ${invite.remainingUses}`;
|
|
||||||
}
|
|
||||||
document.getElementById(CSS.escape(invite.code) + "_usersCreated").innerHTML = genUsedBy(invite.usedBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete invite from DOM
|
|
||||||
const hideInvite = (code: string): void => document.getElementById(CSS.escape(code)).remove();
|
|
||||||
|
|
||||||
// delete invite from jfa-go
|
|
||||||
const deleteInvite = (code: string): void => _delete("/invites", { "code": code }, function (): void {
|
|
||||||
if (this.readyState == 4) {
|
|
||||||
generateInvites();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function generateInvites(empty?: boolean): void {
|
|
||||||
if (empty) {
|
|
||||||
document.getElementById('invites').textContent = '';
|
|
||||||
addItem(emptyInvite());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_get("/invites", null, function (): void {
|
|
||||||
if (this.readyState == 4) {
|
|
||||||
let data = this.response;
|
|
||||||
availableProfiles = data['profiles'];
|
|
||||||
const Profiles = document.getElementById('inviteProfile') as HTMLSelectElement;
|
|
||||||
let innerHTML = "";
|
|
||||||
for (let i = 0; i < availableProfiles.length; i++) {
|
|
||||||
const profile = availableProfiles[i];
|
|
||||||
innerHTML += `
|
|
||||||
<option value="${profile}" ${(i == 0) ? "selected" : ""}>${profile}</option>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
innerHTML += `
|
|
||||||
<option value="NoProfile" ${(availableProfiles.length == 0) ? "selected" : ""}>No Profile</option>
|
|
||||||
`;
|
|
||||||
Profiles.innerHTML = innerHTML;
|
|
||||||
if (data['invites'] == null || data['invites'].length == 0) {
|
|
||||||
document.getElementById('invites').textContent = '';
|
|
||||||
addItem(emptyInvite());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let items = document.getElementById('invites').children;
|
|
||||||
for (const i in data['invites']) {
|
|
||||||
let match = false;
|
|
||||||
const inv = parseInvite(data['invites'][i]);
|
|
||||||
for (const x in items) {
|
|
||||||
if (items[x].id == inv.code) {
|
|
||||||
match = true;
|
|
||||||
updateInvite(inv);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!match) {
|
|
||||||
addItem(inv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// second pass to check for expired invites
|
|
||||||
items = document.getElementById('invites').children;
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
let exists = false;
|
|
||||||
for (const x in data['invites']) {
|
|
||||||
if (items[i].id == data['invites'][x]['code']) {
|
|
||||||
exists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!exists) {
|
|
||||||
hideInvite(items[i].id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const addOptions = (length: number, el: HTMLSelectElement): void => {
|
|
||||||
for (let v = 0; v <= length; v++) {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.textContent = ""+v;
|
|
||||||
opt.value = ""+v;
|
|
||||||
el.appendChild(opt);
|
|
||||||
}
|
|
||||||
el.value = "0";
|
|
||||||
};
|
|
||||||
|
|
||||||
function fixCheckboxes(): void {
|
function fixCheckboxes(): void {
|
||||||
const send_to_address: Array<HTMLInputElement> = [document.getElementById('send_to_address') as HTMLInputElement, document.getElementById('send_to_address_enabled') as HTMLInputElement];
|
const send_to_address: Array<HTMLInputElement> = [document.getElementById('send_to_address') as HTMLInputElement, document.getElementById('send_to_address_enabled') as HTMLInputElement];
|
||||||
@ -329,7 +43,6 @@ fixCheckboxes();
|
|||||||
delete send['send_to_address'];
|
delete send['send_to_address'];
|
||||||
delete send['send_to_address_enabled'];
|
delete send['send_to_address_enabled'];
|
||||||
}
|
}
|
||||||
console.log(send);
|
|
||||||
_post("/invites", send, function (): void {
|
_post("/invites", send, function (): void {
|
||||||
if (this.readyState == 4) {
|
if (this.readyState == 4) {
|
||||||
button.textContent = 'Generate';
|
button.textContent = 'Generate';
|
||||||
@ -340,9 +53,9 @@ fixCheckboxes();
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
triggerTooltips();
|
window.BS.triggerTooltips();
|
||||||
|
|
||||||
function setProfile(select: HTMLSelectElement): void {
|
window.setProfile= (select: HTMLSelectElement): void => {
|
||||||
if (!select.value) {
|
if (!select.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -362,16 +75,6 @@ function setProfile(select: HTMLSelectElement): void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkDuration(): void {
|
|
||||||
const boxVals: Array<number> = [+(document.getElementById("days") as HTMLSelectElement).value, +(document.getElementById("hours") as HTMLSelectElement).value, +(document.getElementById("minutes") as HTMLSelectElement).value];
|
|
||||||
const submit = document.getElementById('generateSubmit') as HTMLButtonElement;
|
|
||||||
if (boxVals.reduce((a: number, b: number): number => a + b) == 0) {
|
|
||||||
submit.disabled = true;
|
|
||||||
} else {
|
|
||||||
submit.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const nE: Array<string> = ["days", "hours", "minutes"];
|
const nE: Array<string> = ["days", "hours", "minutes"];
|
||||||
for (const i in nE) {
|
for (const i in nE) {
|
||||||
document.getElementById(nE[i]).addEventListener("change", checkDuration);
|
document.getElementById(nE[i]).addEventListener("change", checkDuration);
|
||||||
|
106
ts/modules/accounts.ts
Normal file
106
ts/modules/accounts.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { _get, _post, _delete } from "../modules/common.js";
|
||||||
|
import { Focus, Unfocus } from "../modules/admin.js";
|
||||||
|
|
||||||
|
interface aWindow extends Window {
|
||||||
|
checkCheckboxes: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var window: aWindow;
|
||||||
|
|
||||||
|
export const checkCheckboxes = (): void => {
|
||||||
|
const defaultsButton = document.getElementById('accountsTabSetDefaults');
|
||||||
|
const deleteButton = document.getElementById('accountsTabDelete');
|
||||||
|
const checkboxes: NodeListOf<HTMLInputElement> = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]:checked');
|
||||||
|
let checked = checkboxes.length;
|
||||||
|
if (checked == 0) {
|
||||||
|
Unfocus(defaultsButton);
|
||||||
|
Unfocus(deleteButton);
|
||||||
|
} else {
|
||||||
|
Focus(defaultsButton);
|
||||||
|
Focus(deleteButton);
|
||||||
|
if (checked == 1) {
|
||||||
|
deleteButton.textContent = 'Delete User';
|
||||||
|
} else {
|
||||||
|
deleteButton.textContent = 'Delete Users';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.checkCheckboxes = checkCheckboxes;
|
||||||
|
|
||||||
|
export function populateUsers(): void {
|
||||||
|
const acList = document.getElementById('accountsList');
|
||||||
|
acList.innerHTML = `
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<strong>Getting Users...</strong>
|
||||||
|
<div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
Unfocus(acList.parentNode.querySelector('thead'));
|
||||||
|
const accountsList = document.createElement('tbody');
|
||||||
|
accountsList.id = 'accountsList';
|
||||||
|
const generateEmail = (id: string, name: string, email: string): string => {
|
||||||
|
let entry: HTMLDivElement = document.createElement('div');
|
||||||
|
entry.id = 'email_' + id;
|
||||||
|
let emailValue: string = email;
|
||||||
|
if (emailValue == undefined) {
|
||||||
|
emailValue = "";
|
||||||
|
}
|
||||||
|
entry.innerHTML = `
|
||||||
|
<i class="fa fa-edit d-inline-block icon-button" style="margin-right: 2%;" onclick="changeEmail(this, '${id}')"></i>
|
||||||
|
<input type="email" class="form-control-plaintext form-control-sm text-muted d-inline-block addressText" id="address_${id}" style="width: auto;" value="${emailValue}" readonly>
|
||||||
|
`;
|
||||||
|
return entry.outerHTML;
|
||||||
|
};
|
||||||
|
const template = (id: string, username: string, email: string, lastActive: string, admin: boolean): string => {
|
||||||
|
let isAdmin = "No";
|
||||||
|
if (admin) {
|
||||||
|
isAdmin = "Yes";
|
||||||
|
}
|
||||||
|
let fci = "form-check-input";
|
||||||
|
if (window.bsVersion != 5) {
|
||||||
|
fci = "";
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
<td nowrap="nowrap" class="align-middle" scope="row"><input class="${fci}" type="checkbox" value="" id="select_${id}" onclick="checkCheckboxes();"></td>
|
||||||
|
<td nowrap="nowrap" class="align-middle">${username}</td>
|
||||||
|
<td nowrap="nowrap" class="align-middle">${generateEmail(id, name, email)}</td>
|
||||||
|
<td nowrap="nowrap" class="align-middle">${lastActive}</td>
|
||||||
|
<td nowrap="nowrap" class="align-middle">${isAdmin}</td>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
_get("/users", null, function (): void {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
window.jfUsers = this.response['users'];
|
||||||
|
for (const user of window.jfUsers) {
|
||||||
|
let tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = template(user['id'], user['name'], user['email'], user['last_active'], user['admin']);
|
||||||
|
accountsList.appendChild(tr);
|
||||||
|
}
|
||||||
|
Focus(acList.parentNode.querySelector('thead'));
|
||||||
|
acList.replaceWith(accountsList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function populateRadios(): void {
|
||||||
|
const radioList = document.getElementById('defaultUserRadios');
|
||||||
|
radioList.textContent = '';
|
||||||
|
let first = true;
|
||||||
|
for (const i in window.jfUsers) {
|
||||||
|
const user = window.jfUsers[i];
|
||||||
|
const radio = document.createElement('div');
|
||||||
|
radio.classList.add('form-check');
|
||||||
|
let checked = '';
|
||||||
|
if (first) {
|
||||||
|
checked = 'checked';
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
radio.innerHTML = `
|
||||||
|
<input class="form-check-input" type="radio" name="defaultRadios" id="default_${user['id']}" ${checked}>
|
||||||
|
<label class="form-check-label" for="default_${user['id']}">${user['name']}</label>`;
|
||||||
|
radioList.appendChild(radio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
68
ts/modules/admin.ts
Normal file
68
ts/modules/admin.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { rmAttr, addAttr, _post, _get, _delete } from "../modules/common.js";
|
||||||
|
|
||||||
|
export const Focus = (el: HTMLElement): void => rmAttr(el, 'unfocused');
|
||||||
|
export const Unfocus = (el: HTMLElement): void => addAttr(el, 'unfocused');
|
||||||
|
|
||||||
|
export function createEl(html: string): HTMLElement {
|
||||||
|
let div = document.createElement('div') as HTMLDivElement;
|
||||||
|
div.innerHTML = html;
|
||||||
|
return div.firstElementChild as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function storeDefaults(users: string | Array<string>): void {
|
||||||
|
const button = document.getElementById('storeDefaults') as HTMLButtonElement;
|
||||||
|
button.disabled = true;
|
||||||
|
button.innerHTML =
|
||||||
|
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="margin-right: 0.5rem;"></span>' +
|
||||||
|
'Loading...';
|
||||||
|
let data = { "homescreen": false };
|
||||||
|
if ((document.getElementById('defaultsSource') as HTMLSelectElement).value == 'profile') {
|
||||||
|
data["from"] = "profile";
|
||||||
|
data["profile"] = (document.getElementById('profileSelect') as HTMLSelectElement).value;
|
||||||
|
} else {
|
||||||
|
const radio = document.querySelector('input[name=defaultRadios]:checked') as HTMLInputElement
|
||||||
|
let id = radio.id.replace("default_", "");
|
||||||
|
data["from"] = "user";
|
||||||
|
data["id"] = id;
|
||||||
|
}
|
||||||
|
if (users != "all") {
|
||||||
|
data["apply_to"] = users;
|
||||||
|
}
|
||||||
|
if ((document.getElementById('storeDefaultHomescreen') as HTMLInputElement).checked) {
|
||||||
|
data["homescreen"] = true;
|
||||||
|
}
|
||||||
|
_post("/users/settings", data, function (): void {
|
||||||
|
if (this.readyState == 4) {
|
||||||
|
if (this.status == 200 || this.status == 204) {
|
||||||
|
button.textContent = "Success";
|
||||||
|
addAttr(button, "btn-success");
|
||||||
|
rmAttr(button, "btn-danger");
|
||||||
|
rmAttr(button, "btn-primary");
|
||||||
|
button.disabled = false;
|
||||||
|
setTimeout((): void => {
|
||||||
|
button.textContent = "Submit";
|
||||||
|
addAttr(button, "btn-primary");
|
||||||
|
rmAttr(button, "btn-success");
|
||||||
|
button.disabled = false;
|
||||||
|
window.Modals.userDefaults.hide();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
if ("error" in this.response) {
|
||||||
|
button.textContent = this.response["error"];
|
||||||
|
} else if (("policy" in this.response) || ("homescreen" in this.response)) {
|
||||||
|
button.textContent = "Failed (check console)";
|
||||||
|
} else {
|
||||||
|
button.textContent = "Failed";
|
||||||
|
}
|
||||||
|
addAttr(button, "btn-danger");
|
||||||
|
rmAttr(button, "btn-primary");
|
||||||
|
setTimeout((): void => {
|
||||||
|
button.textContent = "Submit";
|
||||||
|
addAttr(button, "btn-primary");
|
||||||
|
rmAttr(button, "btn-danger");
|
||||||
|
button.disabled = false;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -1,3 +1,11 @@
|
|||||||
|
import { rmAttr, addAttr } from "../modules/common.js";
|
||||||
|
|
||||||
|
interface aWindow extends Window {
|
||||||
|
rotateButton(el: HTMLElement): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var window: aWindow;
|
||||||
|
|
||||||
// Used for animation on theme change
|
// Used for animation on theme change
|
||||||
const whichTransitionEvent = (): string => {
|
const whichTransitionEvent = (): string => {
|
||||||
const el = document.createElement('fakeElement');
|
const el = document.createElement('fakeElement');
|
||||||
@ -26,7 +34,7 @@ const _toggleCSS = (): void => {
|
|||||||
cssEl = 1;
|
cssEl = 1;
|
||||||
remove = true
|
remove = true
|
||||||
}
|
}
|
||||||
let href: string = "bs" + bsVersion;
|
let href: string = "bs" + window.bsVersion;
|
||||||
if (!els[cssEl].href.includes(href + "-jf")) {
|
if (!els[cssEl].href.includes(href + "-jf")) {
|
||||||
href += "-jf";
|
href += "-jf";
|
||||||
}
|
}
|
||||||
@ -41,8 +49,8 @@ const _toggleCSS = (): void => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Toggles between light and dark themes, but runs animation if window small enough.
|
// Toggles between light and dark themes, but runs animation if window small enough.
|
||||||
var buttonWidth = 0;
|
window.buttonWidth = 0;
|
||||||
const toggleCSS = (el: HTMLElement): void => {
|
export const toggleCSS = (el: HTMLElement): void => {
|
||||||
const switchToColor = window.getComputedStyle(document.body, null).backgroundColor;
|
const switchToColor = window.getComputedStyle(document.body, null).backgroundColor;
|
||||||
// Max page width for animation to take place
|
// Max page width for animation to take place
|
||||||
let maxWidth = 1500;
|
let maxWidth = 1500;
|
||||||
@ -51,7 +59,7 @@ const toggleCSS = (el: HTMLElement): void => {
|
|||||||
const radius = Math.sqrt(Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2));
|
const radius = Math.sqrt(Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2));
|
||||||
const currentRadius = el.getBoundingClientRect().width / 2;
|
const currentRadius = el.getBoundingClientRect().width / 2;
|
||||||
const scale = radius / currentRadius;
|
const scale = radius / currentRadius;
|
||||||
buttonWidth = +window.getComputedStyle(el, null).width;
|
window.buttonWidth = +window.getComputedStyle(el, null).width;
|
||||||
document.body.classList.remove('smooth-transition');
|
document.body.classList.remove('smooth-transition');
|
||||||
el.style.transform = `scale(${scale})`;
|
el.style.transform = `scale(${scale})`;
|
||||||
el.style.color = switchToColor;
|
el.style.color = switchToColor;
|
||||||
@ -68,7 +76,7 @@ const toggleCSS = (el: HTMLElement): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const rotateButton = (el: HTMLElement): void => {
|
window.rotateButton = (el: HTMLElement): void => {
|
||||||
if (el.classList.contains("rotated")) {
|
if (el.classList.contains("rotated")) {
|
||||||
rmAttr(el, "rotated")
|
rmAttr(el, "rotated")
|
||||||
addAttr(el, "not-rotated");
|
addAttr(el, "not-rotated");
|
45
ts/modules/bs4.ts
Normal file
45
ts/modules/bs4.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
declare var $: any;
|
||||||
|
|
||||||
|
class Modal implements BSModal {
|
||||||
|
el: HTMLDivElement;
|
||||||
|
modal: any;
|
||||||
|
|
||||||
|
constructor(id: string, find?: boolean) {
|
||||||
|
this.el = document.getElementById(id) as HTMLDivElement;
|
||||||
|
this.modal = $(this.el) as any;
|
||||||
|
this.modal.on("shown.b.modal", (): void => document.body.classList.add('modal-open'));
|
||||||
|
};
|
||||||
|
|
||||||
|
show(): void { this.modal.modal("show"); };
|
||||||
|
hide(): void { this.modal.modal("hide"); };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BS4 implements Bootstrap {
|
||||||
|
triggerTooltips: tooltipTrigger = function (): void {
|
||||||
|
const checkboxes = [].slice.call(document.getElementById('settingsContent').querySelectorAll('input[type="checkbox"]'));
|
||||||
|
for (const i in checkboxes) {
|
||||||
|
checkboxes[i].click();
|
||||||
|
checkboxes[i].click();
|
||||||
|
}
|
||||||
|
const tooltips = [].slice.call(document.querySelectorAll('a[data-toggle="tooltip"]'));
|
||||||
|
tooltips.map((el: HTMLAnchorElement): any => {
|
||||||
|
return ($(el) as any).tooltip();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Compat(): void {
|
||||||
|
console.log('Fixing BS4 Compatability');
|
||||||
|
const send_to_address_enabled = document.getElementById('send_to_address_enabled');
|
||||||
|
if (send_to_address_enabled) {
|
||||||
|
send_to_address_enabled.classList.remove("form-check-input");
|
||||||
|
}
|
||||||
|
const multiUseEnabled = document.getElementById('multiUseEnabled');
|
||||||
|
if (multiUseEnabled) {
|
||||||
|
multiUseEnabled.classList.remove("form-check-input");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newModal: ModalConstructor = function (id: string, find?: boolean): BSModal {
|
||||||
|
return new Modal(id, find);
|
||||||
|
};
|
||||||
|
}
|
37
ts/modules/bs5.ts
Normal file
37
ts/modules/bs5.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
declare var bootstrap: any;
|
||||||
|
|
||||||
|
class Modal implements BSModal {
|
||||||
|
el: HTMLDivElement;
|
||||||
|
modal: any;
|
||||||
|
|
||||||
|
constructor(id: string, find?: boolean) {
|
||||||
|
this.el = document.getElementById(id) as HTMLDivElement;
|
||||||
|
if (find) {
|
||||||
|
this.modal = bootstrap.Modal.getInstance(this.el);
|
||||||
|
} else {
|
||||||
|
this.modal = new bootstrap.Modal(this.el);
|
||||||
|
}
|
||||||
|
this.el.addEventListener('shown.bs.modal', (): void => document.body.classList.add("modal-open"));
|
||||||
|
};
|
||||||
|
|
||||||
|
show(): void { this.modal.show(); };
|
||||||
|
hide(): void { this.modal.hide(); };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BS5 implements Bootstrap {
|
||||||
|
triggerTooltips: tooltipTrigger = function (): void {
|
||||||
|
const checkboxes = [].slice.call(document.getElementById('settingsContent').querySelectorAll('input[type="checkbox"]'));
|
||||||
|
for (const i in checkboxes) {
|
||||||
|
checkboxes[i].click();
|
||||||
|
checkboxes[i].click();
|
||||||
|
}
|
||||||
|
const tooltips = [].slice.call(document.querySelectorAll('a[data-toggle="tooltip"]'));
|
||||||
|
tooltips.map((el: HTMLAnchorElement): any => {
|
||||||
|
return new bootstrap.Tooltip(el);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
newModal: ModalConstructor = function (id: string, find?: boolean): BSModal {
|
||||||
|
return new Modal(id, find);
|
||||||
|
};
|
||||||
|
};
|
@ -1,8 +1,6 @@
|
|||||||
interface Window {
|
declare var window: Window;
|
||||||
token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeForm(id: string): Object {
|
export function serializeForm(id: string): Object {
|
||||||
const form = document.getElementById(id) as HTMLFormElement;
|
const form = document.getElementById(id) as HTMLFormElement;
|
||||||
let formData = {};
|
let formData = {};
|
||||||
for (let i = 0; i < form.elements.length; i++) {
|
for (let i = 0; i < form.elements.length; i++) {
|
||||||
@ -38,15 +36,15 @@ function serializeForm(id: string): Object {
|
|||||||
return formData;
|
return formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rmAttr = (el: HTMLElement, attr: string): void => {
|
export const rmAttr = (el: HTMLElement, attr: string): void => {
|
||||||
if (el.classList.contains(attr)) {
|
if (el.classList.contains(attr)) {
|
||||||
el.classList.remove(attr);
|
el.classList.remove(attr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addAttr = (el: HTMLElement, attr: string): void => el.classList.add(attr);
|
export const addAttr = (el: HTMLElement, attr: string): void => el.classList.add(attr);
|
||||||
|
|
||||||
const _get = (url: string, data: Object, onreadystatechange: () => void): void => {
|
export const _get = (url: string, data: Object, onreadystatechange: () => void): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("GET", url, true);
|
req.open("GET", url, true);
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
@ -56,7 +54,7 @@ const _get = (url: string, data: Object, onreadystatechange: () => void): void =
|
|||||||
req.send(JSON.stringify(data));
|
req.send(JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const _post = (url: string, data: Object, onreadystatechange: () => void, response?: boolean): void => {
|
export const _post = (url: string, data: Object, onreadystatechange: () => void, response?: boolean): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("POST", url, true);
|
req.open("POST", url, true);
|
||||||
if (response) {
|
if (response) {
|
||||||
@ -68,7 +66,7 @@ const _post = (url: string, data: Object, onreadystatechange: () => void, respon
|
|||||||
req.send(JSON.stringify(data));
|
req.send(JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
function _delete(url: string, data: Object, onreadystatechange: () => void): void {
|
export function _delete(url: string, data: Object, onreadystatechange: () => void): void {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("DELETE", url, true);
|
req.open("DELETE", url, true);
|
||||||
req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
|
req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
|
297
ts/modules/invites.ts
Normal file
297
ts/modules/invites.ts
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
import { _get, _post, _delete } from "../modules/common.js";
|
||||||
|
|
||||||
|
interface aWindow extends Window {
|
||||||
|
setNotify(el: HTMLElement): void;
|
||||||
|
deleteInvite(code: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var window: aWindow;
|
||||||
|
|
||||||
|
const emptyInvite = (): Invite => { return { code: "None", empty: true } as Invite; }
|
||||||
|
|
||||||
|
function genUsedBy(usedBy: Array<Array<string>>): string {
|
||||||
|
let uB = "";
|
||||||
|
if (usedBy && usedBy.length != 0) {
|
||||||
|
uB = `
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item py-1">Users created:</li>
|
||||||
|
`;
|
||||||
|
for (const i in usedBy) {
|
||||||
|
uB += `
|
||||||
|
<li class="list-group-item py-1 disabled">
|
||||||
|
<div class="d-flex float-left">${usedBy[i][0]}</div>
|
||||||
|
<div class="d-flex float-right">${usedBy[i][1]}</div>
|
||||||
|
</li>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
uB += `</ul>`
|
||||||
|
}
|
||||||
|
return uB;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addItem(invite: Invite): void {
|
||||||
|
const links = document.getElementById('invites');
|
||||||
|
const container = document.createElement('div') as HTMLDivElement;
|
||||||
|
container.id = invite.code;
|
||||||
|
const item = document.createElement('div') as HTMLDivElement;
|
||||||
|
item.classList.add('list-group-item', 'd-flex', 'justify-content-between', 'd-inline-block');
|
||||||
|
let link = "";
|
||||||
|
let innerHTML = `<a>None</a>`;
|
||||||
|
if (invite.empty) {
|
||||||
|
item.innerHTML = `
|
||||||
|
<div class="d-flex align-items-center font-monospace" style="width: 40%;">
|
||||||
|
${innerHTML}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
container.appendChild(item);
|
||||||
|
links.appendChild(container);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
link = window.location.href.split('#')[0] + "invite/" + invite.code;
|
||||||
|
innerHTML = `
|
||||||
|
<div class="d-flex align-items-center font-monospace" style="width: 40%;">
|
||||||
|
<a class="invite-link" href="${link}">${invite.code.replace(/-/g, '-')}</a>
|
||||||
|
<i class="fa fa-clipboard icon-button" onclick="window.toClipboard('${link}')" style="margin-right: 0.5rem; margin-left: 0.5rem;"></i>
|
||||||
|
`;
|
||||||
|
if (invite.email) {
|
||||||
|
let email = invite.email;
|
||||||
|
if (!invite.email.includes("Failed to send to")) {
|
||||||
|
email = `Sent to ${email}`;
|
||||||
|
}
|
||||||
|
innerHTML += `
|
||||||
|
<span class="text-muted" style="margin-left: 0.4rem; font-style: italic; font-size: 0.8rem;">${email}</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
innerHTML += `
|
||||||
|
</div>
|
||||||
|
<div style="text-align: right;">
|
||||||
|
<span id="${invite.code}_expiry" style="margin-right: 1rem;">${invite.expiresIn}</span>
|
||||||
|
<div style="display: inline-block;">
|
||||||
|
<button class="btn btn-outline-danger" onclick="deleteInvite('${invite.code}')">Delete</button>
|
||||||
|
<i class="fa fa-angle-down collapsed icon-button not-rotated" style="padding: 1rem; margin: -1rem -1rem -1rem 0;" data-toggle="collapse" aria-expanded="false" data-target="#${CSS.escape(invite.code)}_collapse" onclick="window.rotateButton(this)"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
item.innerHTML = innerHTML;
|
||||||
|
container.appendChild(item);
|
||||||
|
|
||||||
|
let profiles = `
|
||||||
|
<label class="input-group-text" for="profile_${CSS.escape(invite.code)}">Profile: </label>
|
||||||
|
<select class="form-select" id="profile_${CSS.escape(invite.code)}" onchange="window.setProfile(this)">
|
||||||
|
<option value="NoProfile" selected>No Profile</option>
|
||||||
|
`;
|
||||||
|
for (const i in window.availableProfiles) {
|
||||||
|
let selected = "";
|
||||||
|
if (window.availableProfiles[i] == invite.profile) {
|
||||||
|
selected = "selected";
|
||||||
|
}
|
||||||
|
profiles += `<option value="${window.availableProfiles[i]}" ${selected}>${window.availableProfiles[i]}</option>`;
|
||||||
|
}
|
||||||
|
profiles += `</select>`;
|
||||||
|
|
||||||
|
let dateCreated: string;
|
||||||
|
if (invite.created) {
|
||||||
|
dateCreated = `<li class="list-group-item py-1">Created: ${invite.created}</li>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let middle: string;
|
||||||
|
if (window.notifications_enabled) {
|
||||||
|
middle = `
|
||||||
|
<div class="col" id="${CSS.escape(invite.code)}_notifyButtons">
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
Notify on:
|
||||||
|
<li class="list-group-item py-1 form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="${CSS.escape(invite.code)}_notifyExpiry" onclick="setNotify(this)" ${invite.notifyExpiry ? "checked" : ""}>
|
||||||
|
<label class="form-check-label" for="${CSS.escape(invite.code)}_notifyExpiry">Expiry</label>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item py-1 form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="${CSS.escape(invite.code)}_notifyCreation" onclick="setNotify(this)" ${invite.notifyCreation ? "checked" : ""}>
|
||||||
|
<label class="form-check-label" for="${CSS.escape(invite.code)}_notifyCreation">User creation</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let right: string = genUsedBy(invite.usedBy)
|
||||||
|
|
||||||
|
const dropdown = document.createElement('div') as HTMLDivElement;
|
||||||
|
dropdown.id = `${CSS.escape(invite.code)}_collapse`;
|
||||||
|
dropdown.classList.add("collapse");
|
||||||
|
dropdown.innerHTML = `
|
||||||
|
<div class="container row align-items-start card-body">
|
||||||
|
<div class="col">
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="input-group py-1">
|
||||||
|
${profiles}
|
||||||
|
</li>
|
||||||
|
${dateCreated}
|
||||||
|
<li class="list-group-item py-1" id="${CSS.escape(invite.code)}_remainingUses">Remaining uses: ${invite.remainingUses}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
${middle}
|
||||||
|
<div class="col" id="${CSS.escape(invite.code)}_usersCreated">
|
||||||
|
${right}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.appendChild(dropdown);
|
||||||
|
links.appendChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseInvite(invite: Object): Invite {
|
||||||
|
let inv: Invite = { code: invite["code"], empty: false, };
|
||||||
|
if (invite["email"]) {
|
||||||
|
inv.email = invite["email"];
|
||||||
|
}
|
||||||
|
let time = ""
|
||||||
|
const f = ["days", "hours", "minutes"];
|
||||||
|
for (const i in f) {
|
||||||
|
if (invite[f[i]] != 0) {
|
||||||
|
time += `${invite[f[i]]}${f[i][0]} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inv.expiresIn = `Expires in ${time.slice(0, -1)}`;
|
||||||
|
if (invite["no-limit"]) {
|
||||||
|
inv.remainingUses = "∞";
|
||||||
|
} else if ("remaining-uses" in invite) {
|
||||||
|
inv.remainingUses = invite["remaining-uses"];
|
||||||
|
}
|
||||||
|
if ("used-by" in invite) {
|
||||||
|
inv.usedBy = invite["used-by"];
|
||||||
|
}
|
||||||
|
if ("created" in invite) {
|
||||||
|
inv.created = invite["created"];
|
||||||
|
}
|
||||||
|
if ("notify-expiry" in invite) {
|
||||||
|
inv.notifyExpiry = invite["notify-expiry"];
|
||||||
|
}
|
||||||
|
if ("notify-creation" in invite) {
|
||||||
|
inv.notifyCreation = invite["notify-creation"];
|
||||||
|
}
|
||||||
|
if ("profile" in invite) {
|
||||||
|
inv.profile = invite["profile"];
|
||||||
|
}
|
||||||
|
return inv;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setNotify = (el: HTMLElement): void => {
|
||||||
|
let send = {};
|
||||||
|
let code: string;
|
||||||
|
let notifyType: string;
|
||||||
|
if (el.id.includes("Expiry")) {
|
||||||
|
code = el.id.replace("_notifyExpiry", "");
|
||||||
|
notifyType = "notify-expiry";
|
||||||
|
} else if (el.id.includes("Creation")) {
|
||||||
|
code = el.id.replace("_notifyCreation", "");
|
||||||
|
notifyType = "notify-creation";
|
||||||
|
}
|
||||||
|
send[code] = {};
|
||||||
|
send[code][notifyType] = (el as HTMLInputElement).checked;
|
||||||
|
_post("/invites/notify", send, function (): void {
|
||||||
|
if (this.readyState == 4 && this.status != 200) {
|
||||||
|
(el as HTMLInputElement).checked = !(el as HTMLInputElement).checked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateInvite(invite: Invite): void {
|
||||||
|
document.getElementById(invite.code + "_expiry").textContent = invite.expiresIn;
|
||||||
|
const remainingUses: any = document.getElementById(CSS.escape(invite.code) + "_remainingUses");
|
||||||
|
if (remainingUses) {
|
||||||
|
remainingUses.textContent = `Remaining uses: ${invite.remainingUses}`;
|
||||||
|
}
|
||||||
|
document.getElementById(CSS.escape(invite.code) + "_usersCreated").innerHTML = genUsedBy(invite.usedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete invite from DOM
|
||||||
|
const hideInvite = (code: string): void => document.getElementById(CSS.escape(code)).remove();
|
||||||
|
|
||||||
|
// delete invite from jfa-go
|
||||||
|
window.deleteInvite = (code: string): void => _delete("/invites", { "code": code }, function (): void {
|
||||||
|
if (this.readyState == 4) {
|
||||||
|
generateInvites();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function generateInvites(empty?: boolean): void {
|
||||||
|
if (empty) {
|
||||||
|
document.getElementById('invites').textContent = '';
|
||||||
|
addItem(emptyInvite());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_get("/invites", null, function (): void {
|
||||||
|
if (this.readyState == 4) {
|
||||||
|
let data = this.response;
|
||||||
|
window.availableProfiles = data['profiles'];
|
||||||
|
const Profiles = document.getElementById('inviteProfile') as HTMLSelectElement;
|
||||||
|
let innerHTML = "";
|
||||||
|
for (let i = 0; i < window.availableProfiles.length; i++) {
|
||||||
|
const profile = window.availableProfiles[i];
|
||||||
|
innerHTML += `
|
||||||
|
<option value="${profile}" ${(i == 0) ? "selected" : ""}>${profile}</option>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
innerHTML += `
|
||||||
|
<option value="NoProfile" ${(window.availableProfiles.length == 0) ? "selected" : ""}>No Profile</option>
|
||||||
|
`;
|
||||||
|
Profiles.innerHTML = innerHTML;
|
||||||
|
if (data['invites'] == null || data['invites'].length == 0) {
|
||||||
|
document.getElementById('invites').textContent = '';
|
||||||
|
addItem(emptyInvite());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let items = document.getElementById('invites').children;
|
||||||
|
for (const i in data['invites']) {
|
||||||
|
let match = false;
|
||||||
|
const inv = parseInvite(data['invites'][i]);
|
||||||
|
for (const x in items) {
|
||||||
|
if (items[x].id == inv.code) {
|
||||||
|
match = true;
|
||||||
|
updateInvite(inv);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!match) {
|
||||||
|
addItem(inv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// second pass to check for expired invites
|
||||||
|
items = document.getElementById('invites').children;
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
let exists = false;
|
||||||
|
for (const x in data['invites']) {
|
||||||
|
if (items[i].id == data['invites'][x]['code']) {
|
||||||
|
exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exists) {
|
||||||
|
hideInvite(items[i].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addOptions = (length: number, el: HTMLSelectElement): void => {
|
||||||
|
for (let v = 0; v <= length; v++) {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.textContent = ""+v;
|
||||||
|
opt.value = ""+v;
|
||||||
|
el.appendChild(opt);
|
||||||
|
}
|
||||||
|
el.value = "0";
|
||||||
|
};
|
||||||
|
|
||||||
|
export function checkDuration(): void {
|
||||||
|
const boxVals: Array<number> = [+(document.getElementById("days") as HTMLSelectElement).value, +(document.getElementById("hours") as HTMLSelectElement).value, +(document.getElementById("minutes") as HTMLSelectElement).value];
|
||||||
|
const submit = document.getElementById('generateSubmit') as HTMLButtonElement;
|
||||||
|
if (boxVals.reduce((a: number, b: number): number => a + b) == 0) {
|
||||||
|
submit.disabled = true;
|
||||||
|
} else {
|
||||||
|
submit.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
164
ts/modules/settings.ts
Normal file
164
ts/modules/settings.ts
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import { _get, _post, _delete, rmAttr, addAttr } from "../modules/common.js";
|
||||||
|
import { Focus, Unfocus } from "../modules/admin.js";
|
||||||
|
|
||||||
|
interface Profile {
|
||||||
|
Admin: boolean;
|
||||||
|
LibraryAccess: string;
|
||||||
|
FromUser: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const populateProfiles = (noTable?: boolean): void => _get("/profiles", null, function (): void {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
const profileList = document.getElementById('profileList');
|
||||||
|
profileList.textContent = '';
|
||||||
|
window.availableProfiles = [this.response["default_profile"]];
|
||||||
|
for (let name in this.response["profiles"]) {
|
||||||
|
if (name != window.availableProfiles[0]) {
|
||||||
|
window.availableProfiles.push(name);
|
||||||
|
}
|
||||||
|
const reqProfile = this.response["profiles"][name];
|
||||||
|
if (!noTable && name != "default_profile") {
|
||||||
|
const profile: Profile = {
|
||||||
|
Admin: reqProfile["admin"],
|
||||||
|
LibraryAccess: reqProfile["libraries"],
|
||||||
|
FromUser: reqProfile["fromUser"]
|
||||||
|
};
|
||||||
|
profileList.innerHTML += `
|
||||||
|
<td nowrap="nowrap" class="align-middle"><strong>${name}</strong></td>
|
||||||
|
<td nowrap="nowrap" class="align-middle"><input class="${window.bs5 ? "form-check-input" : ""}" type="radio" name="defaultProfile" onclick="setDefaultProfile('${name}')" ${(name == window.availableProfiles[0]) ? "checked" : ""}></td>
|
||||||
|
<td nowrap="nowrap" class="align-middle">${profile.FromUser}</td>
|
||||||
|
<td nowrap="nowrap" class="align-middle">${profile.Admin ? "Yes" : "No"}</td>
|
||||||
|
<td nowrap="nowrap" class="align-middle">${profile.LibraryAccess}</td>
|
||||||
|
<td nowrap="nowrap" class="align-middle"><button class="btn btn-outline-danger" id="defaultProfile_${name}" onclick="deleteProfile('${name}')">Delete</button></td>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const openSettings = (settingsList: HTMLElement, settingsContent: HTMLElement, callback?: () => void): void => _get("/config", null, function (): void {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
settingsList.textContent = '';
|
||||||
|
window.config = this.response;
|
||||||
|
for (const i in window.config["order"]) {
|
||||||
|
const section: string = window.config["order"][i]
|
||||||
|
const sectionCollapse = document.createElement('div') as HTMLDivElement;
|
||||||
|
Unfocus(sectionCollapse);
|
||||||
|
sectionCollapse.id = section;
|
||||||
|
|
||||||
|
const title: string = window.config[section]["meta"]["name"];
|
||||||
|
const description: string = window.config[section]["meta"]["description"];
|
||||||
|
const entryListID: string = `${section}_entryList`;
|
||||||
|
// const footerID: string = `${section}_footer`;
|
||||||
|
|
||||||
|
sectionCollapse.innerHTML = `
|
||||||
|
<div class="card card-body">
|
||||||
|
<small class="text-muted">${description}</small>
|
||||||
|
<div class="${entryListID}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
for (const x in config[section]["order"]) {
|
||||||
|
const entry: string = config[section]["order"][x];
|
||||||
|
if (entry == "meta") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let entryName: string = window.config[section][entry]["name"];
|
||||||
|
let required = false;
|
||||||
|
if (window.config[section][entry]["required"]) {
|
||||||
|
entryName += ` <sup class="text-danger">*</sup>`;
|
||||||
|
required = true;
|
||||||
|
}
|
||||||
|
if (window.config[section][entry]["requires_restart"]) {
|
||||||
|
entryName += ` <sup class="text-danger">R</sup>`;
|
||||||
|
}
|
||||||
|
if ("description" in window.config[section][entry]) {
|
||||||
|
entryName +=`
|
||||||
|
<a class="text-muted" href="#" data-toggle="tooltip" data-placement="right" title="${window.config[section][entry]['description']}"><i class="fa fa-question-circle-o"></i></a>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
const entryValue: boolean | string = window.config[section][entry]["value"];
|
||||||
|
const entryType: string = window.config[section][entry]["type"];
|
||||||
|
const entryGroup = document.createElement('div');
|
||||||
|
if (entryType == "bool") {
|
||||||
|
entryGroup.classList.add("form-check");
|
||||||
|
entryGroup.innerHTML = `
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="${section}_${entry}" ${(entryValue as boolean) ? 'checked': ''} ${required ? 'required' : ''}>
|
||||||
|
<label class="form-check-label" for="${section}_${entry}">${entryName}</label>
|
||||||
|
`;
|
||||||
|
(entryGroup.querySelector('input[type=checkbox]') as HTMLInputElement).onclick = function (): void {
|
||||||
|
const me = this as HTMLInputElement;
|
||||||
|
for (const y in window.config["order"]) {
|
||||||
|
const sect: string = window.config["order"][y];
|
||||||
|
for (const z in window.config[sect]["order"]) {
|
||||||
|
const ent: string = window.config[sect]["order"][z];
|
||||||
|
if (`${sect}_${window.config[sect][ent]['depends_true']}` == me.id) {
|
||||||
|
(document.getElementById(`${sect}_${ent}`) as HTMLInputElement).disabled = !(me.checked);
|
||||||
|
} else if (`${sect}_${window.config[sect][ent]['depends_false']}` == me.id) {
|
||||||
|
(document.getElementById(`${sect}_${ent}`) as HTMLInputElement).disabled = me.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if ((entryType == 'text') || (entryType == 'email') || (entryType == 'password') || (entryType == 'number')) {
|
||||||
|
entryGroup.classList.add("form-group");
|
||||||
|
entryGroup.innerHTML = `
|
||||||
|
<label for="${section}_${entry}">${entryName}</label>
|
||||||
|
<input type="${entryType}" class="form-control" id="${section}_${entry}" aria-describedby="${entry}" value="${entryValue}" ${required ? 'required' : ''}>
|
||||||
|
`;
|
||||||
|
} else if (entryType == 'select') {
|
||||||
|
entryGroup.classList.add("form-group");
|
||||||
|
const entryOptions: Array<string> = window.config[section][entry]["options"];
|
||||||
|
let innerGroup = `
|
||||||
|
<label for="${section}_${entry}">${entryName}</label>
|
||||||
|
<select class="form-control" id="${section}_${entry}" ${required ? 'required' : ''}>
|
||||||
|
`;
|
||||||
|
for (const z in entryOptions) {
|
||||||
|
const entryOption = entryOptions[z];
|
||||||
|
let selected: boolean = (entryOption == entryValue);
|
||||||
|
innerGroup += `
|
||||||
|
<option value="${entryOption}" ${selected ? 'selected' : ''}>${entryOption}</option>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
innerGroup += `</select>`;
|
||||||
|
entryGroup.innerHTML = innerGroup;
|
||||||
|
}
|
||||||
|
sectionCollapse.getElementsByClassName(entryListID)[0].appendChild(entryGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsList.innerHTML += `
|
||||||
|
<button type="button" class="list-group-item list-group-item-action" id="${section}_button" onclick="showSetting('${section}')">${title}</button>
|
||||||
|
`;
|
||||||
|
settingsContent.appendChild(sectionCollapse);
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function showSetting(id: string, runBefore?: () => void): void {
|
||||||
|
const els = document.getElementById('settingsLeft').querySelectorAll("button[type=button]:not(.static)") as NodeListOf<HTMLButtonElement>;
|
||||||
|
for (let i = 0; i < els.length; i++) {
|
||||||
|
const el = els[i];
|
||||||
|
if (el.id != `${id}_button`) {
|
||||||
|
rmAttr(el, "active");
|
||||||
|
}
|
||||||
|
const sectEl = document.getElementById(el.id.replace("_button", ""));
|
||||||
|
if (sectEl.id != id) {
|
||||||
|
Unfocus(sectEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addAttr(document.getElementById(`${id}_button`), "active");
|
||||||
|
const section = document.getElementById(id);
|
||||||
|
if (runBefore) {
|
||||||
|
runBefore();
|
||||||
|
}
|
||||||
|
Focus(section);
|
||||||
|
if (screen.width <= 1100) {
|
||||||
|
// ugly
|
||||||
|
setTimeout((): void => section.scrollIntoView(<ScrollIntoViewOptions>{ block: "center", behavior: "smooth" }), 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,7 @@
|
|||||||
const ombiDefaultsModal = createModal('ombiDefaults');
|
import { _get, _post, _delete, rmAttr, addAttr } from "modules/common.js";
|
||||||
|
|
||||||
|
const ombiDefaultsModal = window.BS.newModal('ombiDefaults');
|
||||||
|
|
||||||
(document.getElementById('openOmbiDefaults') as HTMLButtonElement).onclick = function (): void {
|
(document.getElementById('openOmbiDefaults') as HTMLButtonElement).onclick = function (): void {
|
||||||
let button = this as HTMLButtonElement;
|
let button = this as HTMLButtonElement;
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
|
228
ts/settings.ts
228
ts/settings.ts
@ -1,9 +1,28 @@
|
|||||||
var config: Object = {};
|
import { _post, _get, _delete, rmAttr, addAttr } from "./modules/common.js";
|
||||||
var modifiedConfig: Object = {};
|
import { generateInvites } from "./modules/invites.js";
|
||||||
|
import { populateRadios } from "./modules/accounts.js";
|
||||||
|
import { Focus, Unfocus } from "./modules/admin.js";
|
||||||
|
import { showSetting, populateProfiles } from "./modules/settings.js";
|
||||||
|
|
||||||
|
interface aWindow extends Window {
|
||||||
|
setDefaultProfile(name: string): void;
|
||||||
|
deleteProfile(name: string): void;
|
||||||
|
createProfile(): void;
|
||||||
|
showSetting(id: string, runBefore?: () => void): void;
|
||||||
|
config: Object;
|
||||||
|
modifiedConfig: Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var window: aWindow;
|
||||||
|
|
||||||
|
window.config = {};
|
||||||
|
window.modifiedConfig = {};
|
||||||
|
|
||||||
|
window.showSetting = showSetting;
|
||||||
|
|
||||||
function sendConfig(restart?: boolean): void {
|
function sendConfig(restart?: boolean): void {
|
||||||
modifiedConfig["restart-program"] = restart;
|
window.modifiedConfig["restart-program"] = restart;
|
||||||
_post("/config", modifiedConfig, function (): void {
|
_post("/config", window.modifiedConfig, function (): void {
|
||||||
if (this.readyState == 4) {
|
if (this.readyState == 4) {
|
||||||
const save = document.getElementById("settingsSave") as HTMLButtonElement
|
const save = document.getElementById("settingsSave") as HTMLButtonElement
|
||||||
if (this.status == 200 || this.status == 204) {
|
if (this.status == 200 || this.status == 204) {
|
||||||
@ -19,159 +38,22 @@ function sendConfig(restart?: boolean): void {
|
|||||||
save.textContent = "Save";
|
save.textContent = "Save";
|
||||||
}
|
}
|
||||||
if (restart) {
|
if (restart) {
|
||||||
refreshModal.show();
|
window.Modals.refresh.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
(document.getElementById('openAbout') as HTMLButtonElement).onclick = (): void => {
|
(document.getElementById('openAbout') as HTMLButtonElement).onclick = (): void => {
|
||||||
aboutModal.show();
|
window.Modals.about.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
const openSettings = (settingsList: HTMLElement, settingsContent: HTMLElement, callback?: () => void): void => _get("/config", null, function (): void {
|
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
settingsList.textContent = '';
|
|
||||||
config = this.response;
|
|
||||||
for (const i in config["order"]) {
|
|
||||||
const section: string = config["order"][i]
|
|
||||||
const sectionCollapse = document.createElement('div') as HTMLDivElement;
|
|
||||||
Unfocus(sectionCollapse);
|
|
||||||
sectionCollapse.id = section;
|
|
||||||
|
|
||||||
const title: string = config[section]["meta"]["name"];
|
|
||||||
const description: string = config[section]["meta"]["description"];
|
|
||||||
const entryListID: string = `${section}_entryList`;
|
|
||||||
// const footerID: string = `${section}_footer`;
|
|
||||||
|
|
||||||
sectionCollapse.innerHTML = `
|
|
||||||
<div class="card card-body">
|
|
||||||
<small class="text-muted">${description}</small>
|
|
||||||
<div class="${entryListID}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
for (const x in config[section]["order"]) {
|
|
||||||
const entry: string = config[section]["order"][x];
|
|
||||||
if (entry == "meta") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let entryName: string = config[section][entry]["name"];
|
|
||||||
let required = false;
|
|
||||||
if (config[section][entry]["required"]) {
|
|
||||||
entryName += ` <sup class="text-danger">*</sup>`;
|
|
||||||
required = true;
|
|
||||||
}
|
|
||||||
if (config[section][entry]["requires_restart"]) {
|
|
||||||
entryName += ` <sup class="text-danger">R</sup>`;
|
|
||||||
}
|
|
||||||
if ("description" in config[section][entry]) {
|
|
||||||
entryName +=`
|
|
||||||
<a class="text-muted" href="#" data-toggle="tooltip" data-placement="right" title="${config[section][entry]['description']}"><i class="fa fa-question-circle-o"></i></a>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
const entryValue: boolean | string = config[section][entry]["value"];
|
|
||||||
const entryType: string = config[section][entry]["type"];
|
|
||||||
const entryGroup = document.createElement('div');
|
|
||||||
if (entryType == "bool") {
|
|
||||||
entryGroup.classList.add("form-check");
|
|
||||||
entryGroup.innerHTML = `
|
|
||||||
<input class="form-check-input" type="checkbox" value="" id="${section}_${entry}" ${(entryValue as boolean) ? 'checked': ''} ${required ? 'required' : ''}>
|
|
||||||
<label class="form-check-label" for="${section}_${entry}">${entryName}</label>
|
|
||||||
`;
|
|
||||||
(entryGroup.querySelector('input[type=checkbox]') as HTMLInputElement).onclick = function (): void {
|
|
||||||
const me = this as HTMLInputElement;
|
|
||||||
for (const y in config["order"]) {
|
|
||||||
const sect: string = config["order"][y];
|
|
||||||
for (const z in config[sect]["order"]) {
|
|
||||||
const ent: string = config[sect]["order"][z];
|
|
||||||
if (`${sect}_${config[sect][ent]['depends_true']}` == me.id) {
|
|
||||||
(document.getElementById(`${sect}_${ent}`) as HTMLInputElement).disabled = !(me.checked);
|
|
||||||
} else if (`${sect}_${config[sect][ent]['depends_false']}` == me.id) {
|
|
||||||
(document.getElementById(`${sect}_${ent}`) as HTMLInputElement).disabled = me.checked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else if ((entryType == 'text') || (entryType == 'email') || (entryType == 'password') || (entryType == 'number')) {
|
|
||||||
entryGroup.classList.add("form-group");
|
|
||||||
entryGroup.innerHTML = `
|
|
||||||
<label for="${section}_${entry}">${entryName}</label>
|
|
||||||
<input type="${entryType}" class="form-control" id="${section}_${entry}" aria-describedby="${entry}" value="${entryValue}" ${required ? 'required' : ''}>
|
|
||||||
`;
|
|
||||||
} else if (entryType == 'select') {
|
|
||||||
entryGroup.classList.add("form-group");
|
|
||||||
const entryOptions: Array<string> = config[section][entry]["options"];
|
|
||||||
let innerGroup = `
|
|
||||||
<label for="${section}_${entry}">${entryName}</label>
|
|
||||||
<select class="form-control" id="${section}_${entry}" ${required ? 'required' : ''}>
|
|
||||||
`;
|
|
||||||
for (const z in entryOptions) {
|
|
||||||
const entryOption = entryOptions[z];
|
|
||||||
let selected: boolean = (entryOption == entryValue);
|
|
||||||
innerGroup += `
|
|
||||||
<option value="${entryOption}" ${selected ? 'selected' : ''}>${entryOption}</option>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
innerGroup += `</select>`;
|
|
||||||
entryGroup.innerHTML = innerGroup;
|
|
||||||
}
|
|
||||||
sectionCollapse.getElementsByClassName(entryListID)[0].appendChild(entryGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsList.innerHTML += `
|
|
||||||
<button type="button" class="list-group-item list-group-item-action" id="${section}_button" onclick="showSetting('${section}')">${title}</button>
|
|
||||||
`;
|
|
||||||
settingsContent.appendChild(sectionCollapse);
|
|
||||||
}
|
|
||||||
if (callback) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interface Profile {
|
|
||||||
Admin: boolean;
|
|
||||||
LibraryAccess: string;
|
|
||||||
FromUser: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
(document.getElementById('profiles_button') as HTMLButtonElement).onclick = (): void => showSetting("profiles", populateProfiles);
|
(document.getElementById('profiles_button') as HTMLButtonElement).onclick = (): void => showSetting("profiles", populateProfiles);
|
||||||
|
|
||||||
const populateProfiles = (noTable?: boolean): void => _get("/profiles", null, function (): void {
|
window.setDefaultProfile = (name: string): void => _post("/profiles/default", { "name": name }, function (): void {
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
|
||||||
const profileList = document.getElementById('profileList');
|
|
||||||
profileList.textContent = '';
|
|
||||||
availableProfiles = [this.response["default_profile"]];
|
|
||||||
for (let name in this.response["profiles"]) {
|
|
||||||
if (name != availableProfiles[0]) {
|
|
||||||
availableProfiles.push(name);
|
|
||||||
}
|
|
||||||
const reqProfile = this.response["profiles"][name];
|
|
||||||
if (!noTable && name != "default_profile") {
|
|
||||||
const profile: Profile = {
|
|
||||||
Admin: reqProfile["admin"],
|
|
||||||
LibraryAccess: reqProfile["libraries"],
|
|
||||||
FromUser: reqProfile["fromUser"]
|
|
||||||
};
|
|
||||||
profileList.innerHTML += `
|
|
||||||
<td nowrap="nowrap" class="align-middle"><strong>${name}</strong></td>
|
|
||||||
<td nowrap="nowrap" class="align-middle"><input class="${(bsVersion == 5) ? "form-check-input" : ""}" type="radio" name="defaultProfile" onclick="setDefaultProfile('${name}')" ${(name == availableProfiles[0]) ? "checked" : ""}></td>
|
|
||||||
<td nowrap="nowrap" class="align-middle">${profile.FromUser}</td>
|
|
||||||
<td nowrap="nowrap" class="align-middle">${profile.Admin ? "Yes" : "No"}</td>
|
|
||||||
<td nowrap="nowrap" class="align-middle">${profile.LibraryAccess}</td>
|
|
||||||
<td nowrap="nowrap" class="align-middle"><button class="btn btn-outline-danger" id="defaultProfile_${name}" onclick="deleteProfile('${name}')">Delete</button></td>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const setDefaultProfile = (name: string): void => _post("/profiles/default", { "name": name }, function (): void {
|
|
||||||
if (this.readyState == 4) {
|
if (this.readyState == 4) {
|
||||||
if (this.status != 200) {
|
if (this.status != 200) {
|
||||||
(document.getElementById(`defaultProfile_${availableProfiles[0]}`) as HTMLInputElement).checked = true;
|
(document.getElementById(`defaultProfile_${window.availableProfiles[0]}`) as HTMLInputElement).checked = true;
|
||||||
(document.getElementById(`defaultProfile_${name}`) as HTMLInputElement).checked = false;
|
(document.getElementById(`defaultProfile_${name}`) as HTMLInputElement).checked = false;
|
||||||
} else {
|
} else {
|
||||||
generateInvites();
|
generateInvites();
|
||||||
@ -179,7 +61,7 @@ const setDefaultProfile = (name: string): void => _post("/profiles/default", { "
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteProfile = (name: string): void => _delete("/profiles", { "name": name }, function (): void {
|
window.deleteProfile = (name: string): void => _delete("/profiles", { "name": name }, function (): void {
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
populateProfiles();
|
populateProfiles();
|
||||||
}
|
}
|
||||||
@ -187,7 +69,7 @@ const deleteProfile = (name: string): void => _delete("/profiles", { "name": nam
|
|||||||
|
|
||||||
const createProfile = (): void => _get("/users", null, function (): void {
|
const createProfile = (): void => _get("/users", null, function (): void {
|
||||||
if (this.readyState == 4 && this.status == 200) {
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
jfUsers = this.response["users"];
|
window.jfUsers = this.response["users"];
|
||||||
populateRadios();
|
populateRadios();
|
||||||
const submitButton = document.getElementById('storeDefaults') as HTMLButtonElement;
|
const submitButton = document.getElementById('storeDefaults') as HTMLButtonElement;
|
||||||
submitButton.disabled = false;
|
submitButton.disabled = false;
|
||||||
@ -205,10 +87,12 @@ const createProfile = (): void => _get("/users", null, function (): void {
|
|||||||
Focus(document.getElementById('newProfileBox'));
|
Focus(document.getElementById('newProfileBox'));
|
||||||
(document.getElementById('newProfileName') as HTMLInputElement).value = '';
|
(document.getElementById('newProfileName') as HTMLInputElement).value = '';
|
||||||
Focus(document.getElementById('defaultUserRadiosBox'));
|
Focus(document.getElementById('defaultUserRadiosBox'));
|
||||||
userDefaultsModal.show();
|
window.Modals.userDefaults.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.createProfile = createProfile;
|
||||||
|
|
||||||
function storeProfile(): void {
|
function storeProfile(): void {
|
||||||
this.disabled = true;
|
this.disabled = true;
|
||||||
this.innerHTML =
|
this.innerHTML =
|
||||||
@ -239,7 +123,7 @@ function storeProfile(): void {
|
|||||||
addAttr(button, "btn-primary");
|
addAttr(button, "btn-primary");
|
||||||
rmAttr(button, "btn-success");
|
rmAttr(button, "btn-success");
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
userDefaultsModal.hide();
|
window.Modals.userDefaults.hide();
|
||||||
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
populateProfiles();
|
populateProfiles();
|
||||||
@ -265,41 +149,17 @@ function storeProfile(): void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSetting(id: string, runBefore?: () => void): void {
|
|
||||||
const els = document.getElementById('settingsLeft').querySelectorAll("button[type=button]:not(.static)") as NodeListOf<HTMLButtonElement>;
|
|
||||||
for (let i = 0; i < els.length; i++) {
|
|
||||||
const el = els[i];
|
|
||||||
if (el.id != `${id}_button`) {
|
|
||||||
rmAttr(el, "active");
|
|
||||||
}
|
|
||||||
const sectEl = document.getElementById(el.id.replace("_button", ""));
|
|
||||||
if (sectEl.id != id) {
|
|
||||||
Unfocus(sectEl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addAttr(document.getElementById(`${id}_button`), "active");
|
|
||||||
const section = document.getElementById(id);
|
|
||||||
if (runBefore) {
|
|
||||||
runBefore();
|
|
||||||
}
|
|
||||||
Focus(section);
|
|
||||||
if (screen.width <= 1100) {
|
|
||||||
// ugly
|
|
||||||
setTimeout((): void => section.scrollIntoView(<ScrollIntoViewOptions>{ block: "center", behavior: "smooth" }), 200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// (document.getElementById('openSettings') as HTMLButtonElement).onclick = (): void => openSettings(document.getElementById('settingsList'), document.getElementById('settingsList'), (): void => settingsModal.show());
|
// (document.getElementById('openSettings') as HTMLButtonElement).onclick = (): void => openSettings(document.getElementById('settingsList'), document.getElementById('settingsList'), (): void => settingsModal.show());
|
||||||
|
|
||||||
(document.getElementById('settingsSave') as HTMLButtonElement).onclick = function (): void {
|
(document.getElementById('settingsSave') as HTMLButtonElement).onclick = function (): void {
|
||||||
modifiedConfig = {};
|
window.modifiedConfig = {};
|
||||||
const save = this as HTMLButtonElement;
|
const save = this as HTMLButtonElement;
|
||||||
let restartSettingsChanged = false;
|
let restartSettingsChanged = false;
|
||||||
let settingsChanged = false;
|
let settingsChanged = false;
|
||||||
for (const i in config["order"]) {
|
for (const i in window.config["order"]) {
|
||||||
const section = config["order"][i];
|
const section = window.config["order"][i];
|
||||||
for (const x in config[section]["order"]) {
|
for (const x in window.config[section]["order"]) {
|
||||||
const entry = config[section]["order"][x];
|
const entry = window.config[section]["order"][x];
|
||||||
if (entry == "meta") {
|
if (entry == "meta") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -311,13 +171,13 @@ function showSetting(id: string, runBefore?: () => void): void {
|
|||||||
} else {
|
} else {
|
||||||
val = el.value.toString();
|
val = el.value.toString();
|
||||||
}
|
}
|
||||||
if (val != config[section][entry]["value"].toString()) {
|
if (val != window.config[section][entry]["value"].toString()) {
|
||||||
if (!(section in modifiedConfig)) {
|
if (!(section in window.modifiedConfig)) {
|
||||||
modifiedConfig[section] = {};
|
window.modifiedConfig[section] = {};
|
||||||
}
|
}
|
||||||
modifiedConfig[section][entry] = val;
|
window.modifiedConfig[section][entry] = val;
|
||||||
settingsChanged = true;
|
settingsChanged = true;
|
||||||
if (config[section][entry]["requires_restart"]) {
|
if (window.config[section][entry]["requires_restart"]) {
|
||||||
restartSettingsChanged = true;
|
restartSettingsChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,7 +193,7 @@ function showSetting(id: string, runBefore?: () => void): void {
|
|||||||
if (restartButton) {
|
if (restartButton) {
|
||||||
restartButton.onclick = (): void => sendConfig(true);
|
restartButton.onclick = (): void => sendConfig(true);
|
||||||
}
|
}
|
||||||
restartModal.show();
|
window.Modals.restart.show();
|
||||||
} else if (settingsChanged) {
|
} else if (settingsChanged) {
|
||||||
save.innerHTML = spinnerHTML;
|
save.innerHTML = spinnerHTML;
|
||||||
sendConfig();
|
sendConfig();
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"outDir": "../data/static",
|
"outDir": "../data/static",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"lib": ["dom", "es2017"],
|
"lib": ["dom", "es2017"],
|
||||||
"types": ["jquery"]
|
"typeRoots": ["./node_modules/@types", "./typings"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
61
ts/typings/d.ts
Normal file
61
ts/typings/d.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
declare interface ModalConstructor {
|
||||||
|
(id: string, find?: boolean): BSModal;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface BSModal {
|
||||||
|
el: HTMLDivElement;
|
||||||
|
modal: any;
|
||||||
|
show: () => void;
|
||||||
|
hide: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface Window {
|
||||||
|
getComputedStyle(element: HTMLElement, pseudoElt: HTMLElement): any;
|
||||||
|
bsVersion: number;
|
||||||
|
bs5: boolean;
|
||||||
|
BS: Bootstrap;
|
||||||
|
Modals: BSModals;
|
||||||
|
cssFile: string;
|
||||||
|
availableProfiles: Array<any>;
|
||||||
|
jfUsers: Array<Object>;
|
||||||
|
notifications_enabled: boolean;
|
||||||
|
token: string;
|
||||||
|
buttonWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface tooltipTrigger {
|
||||||
|
(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface Bootstrap {
|
||||||
|
newModal: ModalConstructor;
|
||||||
|
triggerTooltips: tooltipTrigger;
|
||||||
|
Compat?(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface BSModals {
|
||||||
|
login: BSModal;
|
||||||
|
userDefaults: BSModal;
|
||||||
|
users: BSModal;
|
||||||
|
restart: BSModal;
|
||||||
|
refresh: BSModal;
|
||||||
|
about: BSModal;
|
||||||
|
delete: BSModal;
|
||||||
|
newUser: BSModal;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Invite {
|
||||||
|
code?: string;
|
||||||
|
expiresIn?: string;
|
||||||
|
empty: boolean;
|
||||||
|
remainingUses?: string;
|
||||||
|
email?: string;
|
||||||
|
usedBy?: Array<Array<string>>;
|
||||||
|
created?: string;
|
||||||
|
notifyExpiry?: boolean;
|
||||||
|
notifyCreation?: boolean;
|
||||||
|
profile?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var config: Object;
|
||||||
|
declare var modifiedConfig: Object;
|
12
views.go
12
views.go
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -30,8 +31,10 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
// if app.checkInvite(code, false, "") {
|
// if app.checkInvite(code, false, "") {
|
||||||
if _, ok := app.storage.invites[code]; ok {
|
if _, ok := app.storage.invites[code]; ok {
|
||||||
email := app.storage.invites[code].Email
|
email := app.storage.invites[code].Email
|
||||||
gc.HTML(http.StatusOK, "form.html", gin.H{
|
if strings.Contains(email, "Failed") {
|
||||||
"bs5": app.config.Section("ui").Key("bs5").MustBool(false),
|
email = ""
|
||||||
|
}
|
||||||
|
gc.HTML(http.StatusOK, "form-loader.html", gin.H{
|
||||||
"cssFile": app.cssFile,
|
"cssFile": app.cssFile,
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
"helpMessage": app.config.Section("ui").Key("help_message").String(),
|
"helpMessage": app.config.Section("ui").Key("help_message").String(),
|
||||||
@ -40,7 +43,10 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
"validate": app.config.Section("password_validation").Key("enabled").MustBool(false),
|
"validate": app.config.Section("password_validation").Key("enabled").MustBool(false),
|
||||||
"requirements": app.validator.getCriteria(),
|
"requirements": app.validator.getCriteria(),
|
||||||
"email": email,
|
"email": email,
|
||||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
"settings": map[string]bool{
|
||||||
|
"bs5": app.config.Section("ui").Key("bs5").MustBool(false),
|
||||||
|
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
gc.HTML(404, "invalidCode.html", gin.H{
|
gc.HTML(404, "invalidCode.html", gin.H{
|
||||||
|
Loading…
Reference in New Issue
Block a user