diff --git a/.gitignore b/.gitignore index 3255bad..134852e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ scss/bs5/*.css* data/static/*.css data/static/*.js data/static/*.js.map +data/static/ts/ !data/static/setup.js data/config-base.json data/config-default.ini diff --git a/data/static/setup.js b/data/static/setup.js index 4f20b11..e9f5fc6 100644 --- a/data/static/setup.js +++ b/data/static/setup.js @@ -133,7 +133,7 @@ document.getElementById('jfTestButton').onclick = function() { nextButton.classList.add('disabled'); nextButton.setAttribute('aria-disabled', 'true'); var req = new XMLHttpRequest(); - req.open("POST", "/testJF", true); + req.open("POST", "/jellyfin/test", true); req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); req.responseType = 'json'; req.onreadystatechange = function() { @@ -260,7 +260,7 @@ document.getElementById('submitButton').onclick = function() { // Send it config["restart-program"] = true; var req = new XMLHttpRequest(); - req.open("POST", "/modifyConfig", true); + req.open("POST", "/config", true); req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); req.responseType = 'json'; req.onreadystatechange = function() { diff --git a/data/static/ts/accounts.ts b/data/static/ts/accounts.ts deleted file mode 100644 index 4d171cb..0000000 --- a/data/static/ts/accounts.ts +++ /dev/null @@ -1,348 +0,0 @@ -const checkCheckboxes = (): void => { - const defaultsButton = document.getElementById('accountsTabSetDefaults'); - const deleteButton = document.getElementById('accountsTabDelete'); - const checkboxes: NodeListOf = 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'; - } - } -} - -const validateEmail = (email: string): boolean => /\S+@\S+\.\S+/.test(email); - -function changeEmail(icon: HTMLElement, id: string): void { - const iconContent = icon.outerHTML; - icon.setAttribute('class', ''); - const entry = icon.nextElementSibling as HTMLInputElement; - const ogEmail = entry.value; - entry.readOnly = false; - entry.classList.remove('form-control-plaintext'); - entry.classList.add('form-control'); - if (ogEmail == "") { - entry.placeholder = 'Address'; - } - const tick = createEl(` - - `); - tick.onclick = (): void => { - const newEmail = entry.value; - if (!validateEmail(newEmail) || newEmail == ogEmail) { - return; - } - cross.remove(); - const spinner = createEl(` -
- Saving... -
- `); - tick.replaceWith(spinner); - let send = {}; - send[id] = newEmail; - _post("/modifyEmails", send, function (): void { - if (this.readyState == 4) { - if (this.status == 200 || this.status == 204) { - entry.nextElementSibling.remove(); - } else { - entry.value = ogEmail; - } - } - }); - icon.outerHTML = iconContent; - entry.readOnly = true; - entry.classList.remove('form-control'); - entry.classList.add('form-control-plaintext'); - entry.placeholder = ''; - }; - const cross = createEl(` - - `); - cross.onclick = (): void => { - tick.remove(); - cross.remove(); - icon.outerHTML = iconContent; - entry.readOnly = true; - entry.classList.remove('form-control'); - entry.classList.add('form-control-plaintext'); - entry.placeholder = ''; - entry.value = ogEmail; - }; - icon.parentNode.appendChild(tick); - icon.parentNode.appendChild(cross); -}; - -var jfUsers: Array; - -function populateUsers(): void { - const acList = document.getElementById('accountsList'); - acList.innerHTML = ` -
- Getting Users... - -
- `; - 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 = ` - - - `; - 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 ` - - ${username} - ${generateEmail(id, name, email)} - ${lastActive} - ${isAdmin} - `; - }; - - _get("/getUsers", 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 = ` - - `; - radioList.appendChild(radio); - } -} - -(document.getElementById('selectAll')).onclick = function (): void { - const checkboxes: NodeListOf = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]'); - for (let i = 0; i < checkboxes.length; i++) { - checkboxes[i].checked = (this).checked; - } - checkCheckboxes(); -}; - -(document.getElementById('deleteModalNotify')).onclick = function (): void { - const textbox: HTMLElement = document.getElementById('deleteModalReasonBox'); - if ((this).checked) { - Focus(textbox); - } else { - Unfocus(textbox); - } -}; - -(document.getElementById('accountsTabDelete')).onclick = function (): void { - const deleteButton = this as HTMLButtonElement; - const checkboxes: NodeListOf = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]:checked'); - let selected: Array = new Array(checkboxes.length); - for (let i = 0; i < checkboxes.length; i++) { - selected[i] = checkboxes[i].id.replace("select_", ""); - } - let title = " user"; - let msg = "Notify user"; - if (selected.length > 1) { - title += "s"; - msg += "s"; - } - title = `Delete ${selected.length} ${title}`; - msg += " of account deletion"; - - document.getElementById('deleteModalTitle').textContent = title; - const dmNotify = document.getElementById('deleteModalNotify') as HTMLInputElement; - dmNotify.checked = false; - document.getElementById('deleteModalNotifyLabel').textContent = msg; - const dmReason = document.getElementById('deleteModalReason') as HTMLTextAreaElement; - dmReason.value = ''; - Unfocus(document.getElementById('deleteModalReasonBox')); - const dmSend = document.getElementById('deleteModalSend') as HTMLButtonElement; - dmSend.textContent = 'Delete'; - dmSend.onclick = function (): void { - const button = this as HTMLButtonElement; - const send = { - 'users': selected, - 'notify': dmNotify.checked, - 'reason': dmReason.value - }; - _post("/deleteUser", send, function (): void { - if (this.readyState == 4) { - if (this.status == 500) { - if ("error" in this.reponse) { - button.textContent = 'Failed'; - } else { - button.textContent = 'Partial fail (check console)'; - console.log(this.response); - } - setTimeout((): void => { - Unfocus(deleteButton); - deleteModal.hide(); - }, 4000); - } else { - Unfocus(deleteButton); - deleteModal.hide() - } - populateUsers(); - checkCheckboxes(); - } - }); - }; - deleteModal.show(); -}; - -(document.getElementById('selectAll')).checked = false; - -(document.getElementById('accountsTabSetDefaults')).onclick = function (): void { - const checkboxes: NodeListOf = document.getElementById('accountsList').querySelectorAll('input[type=checkbox]:checked'); - let userIDs: Array = new Array(checkboxes.length); - for (let i = 0; i < checkboxes.length; i++){ - userIDs[i] = checkboxes[i].id.replace("select_", ""); - } - if (userIDs.length == 0) { - return; - } - populateRadios(); - let userString = 'user'; - if (userIDs.length > 1) { - userString += "s"; - } - populateProfiles(true); - const profileSelect = document.getElementById('profileSelect') as HTMLSelectElement; - profileSelect.textContent = ''; - for (let i = 0; i < availableProfiles.length; i++) { - profileSelect.innerHTML += ` - - `; - } - document.getElementById('defaultsTitle').textContent = `Apply settings to ${userIDs.length} ${userString}`; - document.getElementById('userDefaultsDescription').textContent = ` - Apply settings from an existing profile or source settings from a user. - `; - document.getElementById('storeHomescreenLabel').textContent = `Apply homescreen layout`; - Focus(document.getElementById('defaultsSourceSection')); - (document.getElementById('defaultsSource')).value = 'profile'; - Focus(document.getElementById('profileSelectBox')); - Unfocus(document.getElementById('defaultUserRadios')); - Unfocus(document.getElementById('newProfileBox')); - document.getElementById('storeDefaults').onclick = (): void => storeDefaults(userIDs); - userDefaultsModal.show(); -}; - -(document.getElementById('defaultsSource')).addEventListener('change', function (): void { - const radios = document.getElementById('defaultUserRadios'); - const profileBox = document.getElementById('profileSelectBox'); - if (this.value == 'profile') { - Unfocus(radios); - Focus(profileBox); - } else { - Unfocus(profileBox); - Focus(radios); - } -}); - -(document.getElementById('newUserCreate')).onclick = function (): void { - const button = this as HTMLButtonElement; - const ogText = button.textContent; - button.innerHTML = ` - Creating... - `; - const email: string = (document.getElementById('newUserEmail')).value; - var username: string = email; - if (document.getElementById('newUserName') != null) { - username = (document.getElementById('newUserName')).value; - } - const password: string = (document.getElementById('newUserPassword')).value; - if (!validateEmail(email) && email != "") { - return; - } - const send = { - 'username': username, - 'password': password, - 'email': email - }; - _post("/newUserAdmin", send, function (): void { - if (this.readyState == 4) { - rmAttr(button, 'btn-primary'); - if (this.status == 200) { - addAttr(button, 'btn-success'); - button.textContent = 'Success'; - setTimeout((): void => { - rmAttr(button, 'btn-success'); - addAttr(button, 'btn-primary'); - button.textContent = ogText; - newUserModal.hide(); - }, 1000); - populateUsers(); - } else { - addAttr(button, 'btn-danger'); - if ("error" in this.response) { - button.textContent = this.response["error"]; - } else { - button.textContent = 'Failed'; - } - setTimeout((): void => { - rmAttr(button, 'btn-danger'); - addAttr(button, 'btn-primary'); - button.textContent = ogText; - }, 2000); - populateUsers(); - } - } - }); -}; - -(document.getElementById('accountsTabAddUser')).onclick = function (): void { - (document.getElementById('newUserEmail')).value = ''; - (document.getElementById('newUserPassword')).value = ''; - if (document.getElementById('newUserName') != null) { - (document.getElementById('newUserName')).value = ''; - } - newUserModal.show(); -}; - - - - - - diff --git a/data/static/ts/admin.ts b/data/static/ts/admin.ts deleted file mode 100644 index cc5cad5..0000000 --- a/data/static/ts/admin.ts +++ /dev/null @@ -1,279 +0,0 @@ -interface Window { - token: string; -} - -// Set in admin.html -var cssFile: string; - -const _post = (url: string, data: Object, onreadystatechange: () => void): void => { - let req = new XMLHttpRequest(); - req.open("POST", url, true); - req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":")); - req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - req.onreadystatechange = onreadystatechange; - req.send(JSON.stringify(data)); -}; - -const _get = (url: string, data: Object, onreadystatechange: () => void): void => { - let req = new XMLHttpRequest(); - req.open("GET", url, true); - req.responseType = 'json'; - req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":")); - req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - req.onreadystatechange = onreadystatechange; - req.send(JSON.stringify(data)); -}; - -const rmAttr = (el: HTMLElement, attr: string): void => { - if (el.classList.contains(attr)) { - el.classList.remove(attr); - } -}; -const addAttr = (el: HTMLElement, attr: string): void => el.classList.add(attr); - -const Focus = (el: HTMLElement): void => rmAttr(el, 'unfocused'); -const Unfocus = (el: HTMLElement): void => addAttr(el, 'unfocused'); - -interface TabSwitcher { - els: Array; - tabButtons: Array; - focus: (el: number) => void; - invites: () => void; - accounts: () => void; - settings: () => void; -} - -const tabs: TabSwitcher = { - els: [document.getElementById('invitesTab') as HTMLDivElement, document.getElementById('accountsTab') as HTMLDivElement, document.getElementById('settingsTab') as HTMLDivElement], - tabButtons: [document.getElementById('invitesTabButton') as HTMLAnchorElement, document.getElementById('accountsTabButton') as HTMLAnchorElement, document.getElementById('settingsTabButton') as HTMLAnchorElement], - focus: (el: number): void => { - for (let i = 0; i < tabs.els.length; i++) { - if (i == el) { - Focus(tabs.els[i]); - addAttr(tabs.tabButtons[i], "active"); - } else { - Unfocus(tabs.els[i]); - rmAttr(tabs.tabButtons[i], "active"); - } - } - }, - invites: (): void => tabs.focus(0), - accounts: (): void => { - populateUsers(); - (document.getElementById('selectAll') as HTMLInputElement).checked = false; - checkCheckboxes(); - tabs.focus(1); - }, - settings: (): void => openSettings(document.getElementById('settingsSections'), document.getElementById('settingsContent'), (): void => { - triggerTooltips(); - showSetting("ui"); - tabs.focus(2); - }) -}; - -// for (let i = 0; i < tabs.els.length; i++) { -// tabs.tabButtons[i].onclick = (): void => tabs.focus(i); -// } -tabs.tabButtons[0].onclick = tabs.invites; -tabs.tabButtons[1].onclick = tabs.accounts; -tabs.tabButtons[2].onclick = tabs.settings; - - -tabs.invites(); - -// Predefined colors for the theme button. -var buttonColor: string = "custom"; -if (cssFile.includes("jf")) { - buttonColor = "rgb(255,255,255)"; -} else if (cssFile == ("bs" + bsVersion + ".css")) { - buttonColor = "rgb(16,16,16)"; -} - -if (buttonColor != "custom") { - const switchButton = document.createElement('button') as HTMLButtonElement; - switchButton.classList.add('btn', 'btn-secondary'); - switchButton.innerHTML = ` - Theme - - `; - switchButton.onclick = (): void => toggleCSS(document.getElementById('fakeButton')); - 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; - -window["token"] = ""; - -function toClipboard(str: string): void { - const el = document.createElement('textarea') as HTMLTextAreaElement; - el.value = str; - el.readOnly = true; - el.style.position = "absolute"; - el.style.left = "-9999px"; - document.body.appendChild(el); - const selected = document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false; - el.select(); - document.execCommand("copy"); - document.body.removeChild(el); - if (selected) { - document.getSelection().removeAllRanges(); - document.getSelection().addRange(selected); - } -} - -function login(username: string, password: string, modal: boolean, button?: HTMLButtonElement, run?: (arg0: number) => void): void { - const req = new XMLHttpRequest(); - req.responseType = 'json'; - req.open("GET", "/getToken", true); - req.setRequestHeader("Authorization", "Basic " + btoa(username + ":" + password)); - req.onreadystatechange = function (): void { - if (this.readyState == 4) { - if (this.status != 200) { - let errorMsg = this.response["error"]; - if (!errorMsg) { - errorMsg = "Unknown error"; - } - if (modal) { - button.disabled = false; - button.textContent = errorMsg; - addAttr(button, "btn-danger"); - rmAttr(button, "btn-primary"); - setTimeout((): void => { - addAttr(button, "btn-primary"); - rmAttr(button, "btn-danger"); - button.textContent = "Login"; - }, 4000); - } else { - loginModal.show(); - } - } else { - const data = this.response; - window.token = data["token"]; - generateInvites(); - setInterval((): void => generateInvites(), 60 * 1000); - addOptions(30, document.getElementById('days') as HTMLSelectElement); - addOptions(24, document.getElementById('hours') as HTMLSelectElement); - const minutes = document.getElementById('minutes') as HTMLSelectElement; - addOptions(59, minutes); - minutes.value = "30"; - checkDuration(); - if (modal) { - loginModal.hide(); - } - Focus(document.getElementById('logoutButton')); - } - if (run) { - run(+this.status); - } - } - }; - 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 { - window.token = ""; - const details = serializeForm('loginForm'); - const button = document.getElementById('loginSubmit') as HTMLButtonElement; - addAttr(button, "btn-primary"); - rmAttr(button, "btn-danger"); - button.disabled = true; - button.innerHTML = ` - - Loading...`; - login(details["username"], details["password"], true, button); - return false; -}; - -function storeDefaults(users: string | Array): void { - // not sure if this does anything, but w/e - this.disabled = true; - this.innerHTML = - '' + - '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("/applySettings", 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); - -login("", "", false, null, (status: number): void => { - if (!(status == 200 || status == 204)) { - loginModal.show(); - } -}); - -(document.getElementById('logoutButton') as HTMLButtonElement).onclick = function (): void { - _post("/logout", null, function (): boolean { - if (this.readyState == 4 && this.status == 200) { - window.token = ""; - location.reload(); - return false; - } - }); -}; - - diff --git a/data/static/ts/animation.ts b/data/static/ts/animation.ts deleted file mode 100644 index 8b5b8c5..0000000 --- a/data/static/ts/animation.ts +++ /dev/null @@ -1,79 +0,0 @@ -// Used for animation on theme change -const whichTransitionEvent = (): string => { - const el = document.createElement('fakeElement'); - const transitions = { - 'transition': 'transitionend', - 'OTransition': 'oTransitionEnd', - 'MozTransition': 'transitionend', - 'WebkitTransition': 'webkitTransitionEnd' - }; - for (const t in transitions) { - if (el.style[t] !== undefined) { - return transitions[t]; - } - } - return ''; -}; - -var transitionEndEvent = whichTransitionEvent(); - -// Toggles between light and dark themes -const _toggleCSS = (): void => { - const els: NodeListOf = document.querySelectorAll('link[rel="stylesheet"][type="text/css"]'); - let cssEl = 0; - let remove = false; - if (els.length != 1) { - cssEl = 1; - remove = true - } - let href: string = "bs" + bsVersion; - if (!els[cssEl].href.includes(href + "-jf")) { - href += "-jf"; - } - href += ".css"; - let newEl = els[cssEl].cloneNode(true) as HTMLLinkElement; - newEl.href = href; - els[cssEl].parentNode.insertBefore(newEl, els[cssEl].nextSibling); - if (remove) { - els[0].remove(); - } - document.cookie = "css=" + href; -} - -// Toggles between light and dark themes, but runs animation if window small enough. -var buttonWidth = 0; -const toggleCSS = (el: HTMLElement): void => { - const switchToColor = window.getComputedStyle(document.body, null).backgroundColor; - // Max page width for animation to take place - let maxWidth = 1500; - if (window.innerWidth < maxWidth) { - // Calculate minimum radius to cover screen - const radius = Math.sqrt(Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2)); - const currentRadius = el.getBoundingClientRect().width / 2; - const scale = radius / currentRadius; - buttonWidth = +window.getComputedStyle(el, null).width; - document.body.classList.remove('smooth-transition'); - el.style.transform = `scale(${scale})`; - el.style.color = switchToColor; - el.addEventListener(transitionEndEvent, function (): void { - if (this.style.transform.length != 0) { - _toggleCSS(); - this.style.removeProperty('transform'); - document.body.classList.add('smooth-transition'); - } - }, false); - } else { - _toggleCSS(); - el.style.color = switchToColor; - } -}; - -const rotateButton = (el: HTMLElement): void => { - if (el.classList.contains("rotated")) { - rmAttr(el, "rotated") - addAttr(el, "not-rotated"); - } else { - rmAttr(el, "not-rotated"); - addAttr(el, "rotated"); - } -}; diff --git a/data/static/ts/bs4.ts b/data/static/ts/bs4.ts deleted file mode 100644 index 61131e0..0000000 --- a/data/static/ts/bs4.ts +++ /dev/null @@ -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(); - }); -} - diff --git a/data/static/ts/bs5.ts b/data/static/ts/bs5.ts deleted file mode 100644 index 5dc69cd..0000000 --- a/data/static/ts/bs5.ts +++ /dev/null @@ -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); - }); -} - diff --git a/data/static/ts/invites.ts b/data/static/ts/invites.ts deleted file mode 100644 index 0b2b16c..0000000 --- a/data/static/ts/invites.ts +++ /dev/null @@ -1,378 +0,0 @@ -// Actually defined by templating in admin.html, this is just to avoid errors from tsc. -var notifications_enabled: any; - -interface Invite { - code?: string; - expiresIn?: string; - empty: boolean; - remainingUses?: string; - email?: string; - usedBy?: Array>; - created?: string; - notifyExpiry?: boolean; - notifyCreation?: boolean; - profile?: string; -} - -const emptyInvite = (): Invite => { return { code: "None", empty: true } as Invite; } - -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("/setNotify", send, function (): void { - if (this.readyState == 4 && this.status != 200) { - (el as HTMLInputElement).checked = !(el as HTMLInputElement).checked; - } - }); -} - -function genUsedBy(usedBy: Array>): string { - let uB = ""; - if (usedBy && usedBy.length != 0) { - uB = ` -
    -
  • Users created:
  • - `; - for (const i in usedBy) { - uB += ` -
  • -
    ${usedBy[i][0]}
    -
    ${usedBy[i][1]}
    -
  • - `; - } - uB += `
` - } - 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 = `None`; - if (invite.empty) { - item.innerHTML = ` -
- ${innerHTML} -
- `; - container.appendChild(item); - links.appendChild(container); - return; - } - link = window.location.href.split('#')[0] + "invite/" + invite.code; - innerHTML = ` -
- ${invite.code.replace(/-/g, '-')} - - `; - if (invite.email) { - let email = invite.email; - if (!invite.email.includes("Failed to send to")) { - email = `Sent to ${email}`; - } - innerHTML += ` - ${email} - `; - } - innerHTML += ` -
-
- ${invite.expiresIn} -
- - -
-
- `; - - item.innerHTML = innerHTML; - container.appendChild(item); - - let profiles = ` - - `; - - let dateCreated: string; - if (invite.created) { - dateCreated = `
  • Created: ${invite.created}
  • `; - } - - let middle: string; - if (notifications_enabled) { - middle = ` -
    -
      - Notify on: -
    • - - -
    • -
    • - - -
    • -
    -
    - `; - } - - 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 = ` -
    -
    -
      -
    • - ${profiles} -
    • - ${dateCreated} -
    • Remaining uses: ${invite.remainingUses}
    • -
    -
    - ${middle} -
    - ${right} -
    -
    - `; - - 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 => _post("/deleteInvite", { "code": code }, function (): void { - if (this.readyState == 4) { - generateInvites(); - } -}); - -function generateInvites(empty?: boolean): void { - if (empty) { - document.getElementById('invites').textContent = ''; - addItem(emptyInvite()); - return; - } - _get("/getInvites", 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 += ` - - `; - } - innerHTML += ` - - `; - 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 { - const send_to_address: Array = [document.getElementById('send_to_address') as HTMLInputElement, document.getElementById('send_to_address_enabled') as HTMLInputElement]; - if (send_to_address[0] != null) { - send_to_address[0].disabled = !send_to_address[1].checked; - } - const multiUseEnabled = document.getElementById('multiUseEnabled') as HTMLInputElement; - const multiUseCount = document.getElementById('multiUseCount') as HTMLInputElement; - const noUseLimit = document.getElementById('noUseLimit') as HTMLInputElement; - multiUseCount.disabled = !multiUseEnabled.checked; - noUseLimit.checked = false; - noUseLimit.disabled = !multiUseEnabled.checked; -} - -fixCheckboxes(); - -(document.getElementById('inviteForm') as HTMLFormElement).onsubmit = function (): boolean { - const button = document.getElementById('generateSubmit') as HTMLButtonElement; - button.disabled = true; - button.innerHTML = ` - - Loading...`; - let send = serializeForm('inviteForm'); - send["remaining-uses"] = +send["remaining-uses"]; - if (!send['multiple-uses'] || send['no-limit']) { - delete send['remaining-uses']; - } - if (send["profile"] == "NoProfile") { - send["profile"] = ""; - } - const sendToAddress: any = document.getElementById('send_to_address'); - const sendToAddressEnabled: any = document.getElementById('send_to_address_enabled'); - if (sendToAddress && sendToAddressEnabled) { - send['email'] = send['send_to_address']; - delete send['send_to_address']; - delete send['send_to_address_enabled']; - } - console.log(send); - _post("/generateInvite", send, function (): void { - if (this.readyState == 4) { - button.textContent = 'Generate'; - button.disabled = false; - generateInvites(); - } - }); - return false; -}; - -triggerTooltips(); - -function setProfile(select: HTMLSelectElement): void { - if (!select.value) { - return; - } - let val = select.value; - if (select.value == "NoProfile") { - val = "" - } - const invite = select.id.replace("profile_", ""); - const send = { - "invite": invite, - "profile": val - }; - _post("/setProfile", send, function (): void { - if (this.readyState == 4 && this.status != 200) { - generateInvites(); - } - }); -} - -function checkDuration(): void { - const boxVals: Array = [+(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 = ["days", "hours", "minutes"]; -for (const i in nE) { - document.getElementById(nE[i]).addEventListener("change", checkDuration); -} diff --git a/data/static/ts/ombi.ts b/data/static/ts/ombi.ts deleted file mode 100644 index 792de69..0000000 --- a/data/static/ts/ombi.ts +++ /dev/null @@ -1,81 +0,0 @@ -const ombiDefaultsModal = createModal('ombiDefaults'); -(document.getElementById('openOmbiDefaults') as HTMLButtonElement).onclick = function (): void { - let button = this as HTMLButtonElement; - button.disabled = true; - const ogHTML = button.innerHTML; - button.innerHTML = - '' + - 'Loading...'; - _get("/getOmbiUsers", null, function (): void { - if (this.readyState == 4) { - if (this.status == 200) { - const users = this.response['users']; - const radioList = document.getElementById('ombiUserRadios'); - radioList.textContent = ''; - let first = true; - for (const i in users) { - const user = users[i]; - const radio = document.createElement('div') as HTMLDivElement; - radio.classList.add('form-check'); - let checked = ''; - if (first) { - checked = 'checked'; - first = false; - } - radio.innerHTML = ` - - - `; - radioList.appendChild(radio); - } - button.disabled = false; - button.innerHTML = ogHTML; - const submitButton = document.getElementById('storeOmbiDefaults') as HTMLButtonElement; - submitButton.disabled = false; - submitButton.textContent = 'Submit'; - addAttr(submitButton, "btn-primary"); - rmAttr(submitButton, "btn-success"); - rmAttr(submitButton, "btn-danger"); - ombiDefaultsModal.show(); - } - } - }); -}; - -(document.getElementById('storeOmbiDefaults') as HTMLButtonElement).onclick = function (): void { - let button = this as HTMLButtonElement; - button.disabled = true; - button.innerHTML = - '' + - 'Loading...'; - const radio = document.querySelector('input[name=ombiRadios]:checked') as HTMLInputElement; - const data = { - "id": radio.id.replace("ombiDefault_", "") - }; - _post("/setOmbiDefaults", 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 => ombiDefaultsModal.hide(), 1000); - } else { - button.textContent = "Failed"; - rmAttr(button, "btn-primary"); - addAttr(button, "btn-danger"); - setTimeout((): void => { - button.textContent = "Submit"; - addAttr(button, "btn-primary"); - rmAttr(button, "btn-danger"); - button.disabled = false; - }, 1000); - } - } - }); -}; - - - - diff --git a/data/static/ts/serialize.ts b/data/static/ts/serialize.ts deleted file mode 100644 index 4f8c165..0000000 --- a/data/static/ts/serialize.ts +++ /dev/null @@ -1,35 +0,0 @@ -function serializeForm(id: string): Object { - const form = document.getElementById(id) as HTMLFormElement; - let formData = {}; - for (let i = 0; i < form.elements.length; i++) { - const el = form.elements[i]; - if ((el as HTMLInputElement).type == "submit") { - continue; - } - let name = (el as HTMLInputElement).name; - if (!name) { - name = el.id; - } - switch ((el as HTMLInputElement).type) { - case "checkbox": - formData[name] = (el as HTMLInputElement).checked; - break; - case "text": - case "password": - case "email": - case "number": - formData[name] = (el as HTMLInputElement).value; - break; - case "select-one": - case "select": - let val: string = (el as HTMLSelectElement).value.toString(); - if (!isNaN(val as any)) { - formData[name] = +val; - } else { - formData[name] = val; - } - break; - } - } - return formData; -} diff --git a/data/static/ts/settings.ts b/data/static/ts/settings.ts deleted file mode 100644 index 3e8266e..0000000 --- a/data/static/ts/settings.ts +++ /dev/null @@ -1,344 +0,0 @@ -var config: Object = {}; -var modifiedConfig: Object = {}; - -function sendConfig(restart?: boolean): void { - modifiedConfig["restart-program"] = restart; - _post("/modifyConfig", modifiedConfig, function (): void { - if (this.readyState == 4) { - const save = document.getElementById("settingsSave") as HTMLButtonElement - if (this.status == 200 || this.status == 204) { - save.textContent = "Success"; - addAttr(save, "btn-success"); - rmAttr(save, "btn-primary"); - setTimeout((): void => { - save.textContent = "Save"; - addAttr(save, "btn-primary"); - rmAttr(save, "btn-success"); - }, 1000); - } else { - save.textContent = "Save"; - } - if (restart) { - refreshModal.show(); - } - } - }); -} - -(document.getElementById('openAbout') as HTMLButtonElement).onclick = (): void => { - aboutModal.show(); -}; - -const openSettings = (settingsList: HTMLElement, settingsContent: HTMLElement, callback?: () => void): void => _get("/getConfig", 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 = ` -
    - ${description} -
    -
    -
    - `; - - 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 += ` *`; - required = true; - } - if (config[section][entry]["requires_restart"]) { - entryName += ` R`; - } - if ("description" in config[section][entry]) { - entryName +=` - - `; - } - 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 = ` - - - `; - (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 = ` - - - `; - } else if (entryType == 'select') { - entryGroup.classList.add("form-group"); - const entryOptions: Array = config[section][entry]["options"]; - let innerGroup = ` - - `; - entryGroup.innerHTML = innerGroup; - } - sectionCollapse.getElementsByClassName(entryListID)[0].appendChild(entryGroup); - } - - settingsList.innerHTML += ` - - `; - 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); - -const populateProfiles = (noTable?: boolean): void => _get("/getProfiles", null, 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) { - if (name != availableProfiles[0]) { - availableProfiles.push(name); - } - if (!noTable && name != "default_profile") { - const profile: Profile = { - Admin: this.response[name]["admin"], - LibraryAccess: this.response[name]["libraries"], - FromUser: this.response[name]["fromUser"] - }; - profileList.innerHTML += ` - ${name} - - ${profile.FromUser} - ${profile.Admin ? "Yes" : "No"} - ${profile.LibraryAccess} - - `; - } - } - } -}); - -const setDefaultProfile = (name: string): void => _post("/setDefaultProfile", { "name": name }, function (): void { - if (this.readyState == 4) { - if (this.status != 200) { - (document.getElementById(`defaultProfile_${availableProfiles[0]}`) as HTMLInputElement).checked = true; - (document.getElementById(`defaultProfile_${name}`) as HTMLInputElement).checked = false; - } else { - generateInvites(); - } - } -}); - -const deleteProfile = (name: string): void => _post("/deleteProfile", { "name": name }, function (): void { - if (this.readyState == 4 && this.status == 200) { - populateProfiles(); - } -}); - -const createProfile = (): void => _get("/getUsers", null, function (): void { - if (this.readyState == 4 && this.status == 200) { - jfUsers = this.response["users"]; - populateRadios(); - const submitButton = document.getElementById('storeDefaults') as HTMLButtonElement; - submitButton.disabled = false; - submitButton.textContent = 'Create'; - addAttr(submitButton, "btn-primary"); - rmAttr(submitButton, "btn-danger"); - rmAttr(submitButton, "btn-success"); - document.getElementById('defaultsTitle').textContent = `Create Profile`; - document.getElementById('userDefaultsDescription').textContent = ` - Create an account and configure it to your liking, then choose it from below to store the settings as a profile. Profiles can be specified per invite, so that any new user on that invite will have the settings applied.`; - document.getElementById('storeHomescreenLabel').textContent = `Store homescreen layout`; - (document.getElementById('defaultsSource') as HTMLSelectElement).value = 'fromUser'; - document.getElementById('defaultsSourceSection').classList.add('unfocused'); - (document.getElementById('storeDefaults') as HTMLButtonElement).onclick = storeProfile; - Focus(document.getElementById('newProfileBox')); - (document.getElementById('newProfileName') as HTMLInputElement).value = ''; - Focus(document.getElementById('defaultUserRadios')); - userDefaultsModal.show(); - } -}); - -function storeProfile(): void { - this.disabled = true; - this.innerHTML = - '' + - 'Loading...'; - const button = document.getElementById('storeDefaults') as HTMLButtonElement; - const radio = document.querySelector('input[name=defaultRadios]:checked') as HTMLInputElement - const name = (document.getElementById('newProfileName') as HTMLInputElement).value; - let id = radio.id.replace("default_", ""); - let data = { - "name": name, - "id": id, - "homescreen": false - } - if ((document.getElementById('storeDefaultHomescreen') as HTMLInputElement).checked) { - data["homescreen"] = true; - } - _post("/createProfile", 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 = "Create"; - addAttr(button, "btn-primary"); - rmAttr(button, "btn-success"); - button.disabled = false; - userDefaultsModal.hide(); - - }, 1000); - populateProfiles(); - generateInvites(); - } 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 = "Create"; - addAttr(button, "btn-primary"); - rmAttr(button, "btn-danger"); - button.disabled = false; - }, 1000); - } - } - }); -} - -function showSetting(id: string, runBefore?: () => void): void { - const els = document.getElementById('settingsLeft').querySelectorAll("button[type=button]:not(.static)") as NodeListOf; - 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({ block: "center", behavior: "smooth" }), 200); - } -} - -// (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 { - modifiedConfig = {}; - const save = this as HTMLButtonElement; - let restartSettingsChanged = false; - let settingsChanged = false; - for (const i in config["order"]) { - const section = config["order"][i]; - for (const x in config[section]["order"]) { - const entry = config[section]["order"][x]; - if (entry == "meta") { - continue; - } - let val: string; - const entryID = `${section}_${entry}`; - const el = document.getElementById(entryID) as HTMLInputElement; - if (el.type == "checkbox") { - val = el.checked.toString(); - } else { - val = el.value.toString(); - } - if (val != config[section][entry]["value"].toString()) { - if (!(section in modifiedConfig)) { - modifiedConfig[section] = {}; - } - modifiedConfig[section][entry] = val; - settingsChanged = true; - if (config[section][entry]["requires_restart"]) { - restartSettingsChanged = true; - } - } - } - } - const spinnerHTML = ` - - Loading...`; - if (restartSettingsChanged) { - save.innerHTML = spinnerHTML; - (document.getElementById('applyRestarts') as HTMLButtonElement).onclick = (): void => sendConfig(); - const restartButton = document.getElementById('applyAndRestart') as HTMLButtonElement; - if (restartButton) { - restartButton.onclick = (): void => sendConfig(true); - } - restartModal.show(); - } else if (settingsChanged) { - save.innerHTML = spinnerHTML; - sendConfig(); - } -}; - -(document.getElementById('restartModalCancel') as HTMLButtonElement).onclick = (): void => { - document.getElementById('settingsSave').textContent = "Save"; -}; diff --git a/data/static/ts/tsconfig.json b/data/static/ts/tsconfig.json deleted file mode 100644 index b380a8b..0000000 --- a/data/static/ts/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "outDir": "../data/static", - "target": "es6", - "lib": ["dom", "es2017"], - "types": ["jquery"] - } -} diff --git a/data/templates/admin.html b/data/templates/admin.html index e65536f..7fac4aa 100644 --- a/data/templates/admin.html +++ b/data/templates/admin.html @@ -463,7 +463,8 @@ -