mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 17:10:10 +00:00
Rework typescript to use modules
web UI now uses modules, and relies less on bodge to make things work. Also fixes an issue where invites where "failed to send to xx" appeared in invite form.
This commit is contained in:
parent
2d6b1717db
commit
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
|
||||||
|
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>
|
||||||
|
|
||||||
|
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,83 +71,7 @@ function changeEmail(icon: HTMLElement, id: string): void {
|
|||||||
icon.parentNode.appendChild(cross);
|
icon.parentNode.appendChild(cross);
|
||||||
};
|
};
|
||||||
|
|
||||||
var jfUsers: Array<Object>;
|
console.log("bruh");
|
||||||
|
|
||||||
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]');
|
||||||
@ -217,18 +133,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 +166,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 +182,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 +227,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 +254,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