function toggleVisibility(idImg, idDiv) { var div = document.getElementById(idDiv); var img = document.getElementById(idImg); if (div.style.display === "none") { div.style.display = "flex"; img.src = "res/dark/hide.png"; } else { div.style.display = "none"; img.src = "res/dark/show.png"; } } class APIScanner { static api; static settings; static scanned; constructor(parent) { APIScanner.api = parent; this.qr = null; this.cameras = []; } Start(id, subroute, parser) { APIScanner.settings = { subroute: subroute, parser: parser, id: id }; APIScanner.scanned = {primary: {}, secondary: {}}; for (let i in parser) { if (i!=subroute) { APIScanner.scanned.secondary[parser[i]] = null; } } // Find already parsed IDs const subData = APIScanner.api.activeRoute.resourcesIndex[id].data.SUB[parser[subroute]]; for (let i in subData) { APIScanner.scanned.primary[subData[i].ID] = true; } this.qr = new QrScanner( document.getElementById("qrscanner_video"), APIScanner.parse, {returnDetailedScanResult: true, highlightScanRegion: true, highlightCodeOutline: true} ); if (QrScanner.hasCamera()) { let _this = this; QrScanner.listCameras(true) .then(result => _this.cameras = result) .catch(error => alert(error || 'Camera detection failed.')); } this.cameraIndex = 0; document.getElementById("qrscanner_text").innerHTML = ""; this.qr.start(); } Stop() { this.qr.stop(); this.qr.destroy(); this.qr = null; } SwitchCamera() { if (this.qr!==null && QrScanner.hasCamera()) { ++this.cameraIndex; if (this.cameraIndex>=this.cameras.length) { this.cameraIndex = 0; } this.qr.setCamera(this.cameras[this.cameraIndex].id); } } static parse(result) { let scan = result.data.split("/"); if (scan.length==3) { let route = scan[1]; let subid = scan[2]; // Is it a route we should parse? if (typeof(APIScanner.settings.parser[route])!="undefined") { const marker = APIScanner.settings.parser[route]; // Is it the primary route and if yes, has this ID been parsed before? if (route==APIScanner.settings.subroute && typeof(APIScanner.scanned.primary[subid])=="undefined") { APIScanner.scanned.primary[subid] = true; let index = -1; const options = APIScanner.api.activeRoute.options[marker]; for (let i in options) { if (options[i].ID==subid) { index = i; break; } } // Has the ID been found? if (index>=0) { document.getElementById("qrscanner_text").innerHTML += options[index].NAME + "
"; APIScanner.api.Request("POST", APIScanner.scanned.secondary, APIScanner.settings.id, APIScanner.settings.subroute, subid); } } else if(route!=APIScanner.settings.subroute) { // Is it a secondary field? APIScanner.scanned.secondary[marker] = subid; } } } } } class APIResource // This holds ONE table record { constructor(route, group, data) { this.route = route; this.group = group; this.ID = data.ID; group.resourcesIndex[this.ID] = this; route.resourcesIndex[this.ID] = this; this.data = data; this.show = false; // TODO: This SHOULD be determined by the client through comparing of data with privileges and not be preprocessed by server. Should it? this.localAdmin = data.admin; } Render(includingBox = false) { var data = this.data; data.SHOWBUTTON = this.show; data.SHOWCONTENT = this.show; data.SELECTBOX = ""; var ret = ""; return (includingBox ? "
" : "") + this.route.tpl.Render(data, "LIST", (this.route.admin || this.localAdmin ? ":ADMIN" : "")) + (includingBox ? "
" : ""); } } class APIGroup { constructor(route, id, resources) { this.route = route; this.ID = id; this.resources = []; // List of APIResources this.resourcesIndex = {}; // resources indexed by ID this.show = true; this.route.groupsIndex[id] = this; for (var i in resources) { this.Add(resources[i]); } } Add(resource) { this.resources.push(new APIResource(this.route, this, resource)); } Delete(id) { var index = this.route.findIndexInEntries(id, this.resources); delete this.resources[index]; this.resources.splice(index, 1); } Clear() { for (var i in this.resources) { delete this.resources[i]; } this.resources = []; this.resourcesIndex = {}; } Render() { var ret = "
" + this.route.tpl.Render({"GROUP": this.ID}, "GROUP_BEGIN", ""); for (var k in this.resources) { ret+= this.resources[k].Render(true); } ret+= this.route.tpl.Render({"GROUP": this.ID}, "GROUP_END", "") + "
"; return ret; } } class APIFilter // This holds ONE filter element { constructor(data, type) { this.Set(data, type); } Get() { return {and: this.and, field: this.field, op: this.op, value: this.value}; } Set(data, type) { this.and = typeof(data.and)!="undefined" ? data.and : "and"; this.field = typeof(data.field)!="undefined" ? data.field : "none"; this.op = typeof(data.op)!="undefined" ? data.op : ""; this.value = typeof(data.value)!="undefined" ? data.value : ("Multiple"==type ? [] : ""); } } class APIRoute // This holds a list of resources { constructor(parent, route, info) { this.parent = parent; this.route = route; this.isPage = info.isPage; this.mainFields = info.mainFields; this.filter = []; this.tplFile = info.template; this.tpl = null; this.image = typeof(info.image)!="undefined" ? info.image : null; this.name = info.name; this.admin = false; this.options = []; this.selected = []; this.dropzones = {}; this.groups = []; // List of APIResourceGroups this.groupsIndex = {}; this.resourcesIndex = {}; // Index of APIResources by ID linking to entries inside their respective group } Add() { // Get JSON var send = {}; for (var i in this.mainFields) { if (this.mainFields[i].add) { var e = document.getElementById("ADD/"+i); if (e!=null) { if (this.mainFields[i].type=="Multiple") { // Multiple values can be selected send[i] = Array.from(e.selectedOptions).map(el=>el.value); } else if (this.mainFields[i].type=="Checkbox") { send[i] = e.checked; } else if (e.value=="__NULL__") { // Null is selected send[i] = null; } else { // Only one value can be selected/input send[i] = e.value; } } else if (this.mainFields[i].type=="DateTime") { var eDate = document.getElementById("ADD/"+i+"/DATE"); var eTime = document.getElementById("ADD/"+i+"/TIME"); if (null!=eDate && null!=eTime) { send[i] = eDate.value + " " + eTime.value; } } } } this.parent.Request("POST", send); } Save(id) { // Get JSON var json = this.findEntry(id).data.MAIN; var send = {}; var element = document.getElementById("LIST/"+id); element.className = "list_entry"; for (var i in this.mainFields) { if (this.mainFields[i].edit) { var e = document.getElementById("LIST/"+id+"/"+i); if (e!=null) { if (this.mainFields[i].type=="Multiple") { // Multiple values can be selected send[i] = Array.from(e.selectedOptions).map(el=>el.value); } else if (this.mainFields[i].type=="Checkbox") { send[i] = e.checked; } else if (e.value=="__NULL__") { // Null is selected send[i] = null; } else { // Only one value can be selected/input send[i] = e.value; } } else if (this.mainFields[i].type=="DateTime") { var eDate = document.getElementById("LIST/"+id+"/"+i+"/DATE"); var eTime = document.getElementById("LIST/"+id+"/"+i+"/TIME"); if (null!=eDate && null!=eTime) { send[i] = eDate.value + " " + eTime.value; } } } } this.parent.Request("PATCH", send, id); } Edit(id) { var _this = this; var entry = this.findEntry(id); var element = document.getElementById("LIST/"+id); element.innerHTML = this.tpl.Render(entry.data, "LIST", ":EDIT"); element.className = "list_entry list_edit"; this.renderHelper(id); } Cancel(id) { var entry = this.findEntry(id); var element = document.getElementById("LIST/"+id); element.innerHTML = this.tpl.Render(entry.data, "LIST", ":ADMIN"); element.className = "list_entry"; } SubShowAdd(id, markers) { var entry = this.findEntry(id); let marker; var data = {"ID": id}; if (Array.isArray(markers)) { marker = markers[0]; for (let i=1; iel.value); } else if (this.mainFields[data.field].type=="Checkbox") { data.value = eValue.checked; } else if (eValue.value=="__NULL__") { // Null is selected data.value = null; } else { // Only one value can be selected/input data.value = eValue.value; } } } if ("undefined"!=typeof(this.filter[id])) { this.filter[id].Set(data, "none"!=data.field ? this.mainFields[data.field].type : "none"); } this.parent.Request("GET"); } SelectChange(id, state) { var selectedIndex = this.selected.indexOf(id); if (state && 0>selectedIndex) { this.selected.push(id); } else if (!state && -1" + this.renderEntry(this.resourcesIndex[responseJson.content.ID]) + ""; var elList = document.getElementById("GROUP/" + responseJson.content.GROUP); elList.insertAdjacentHTML("beforeend", entryHtml); } else if (method=="GET") { // Update everything this.admin = responseJson.admin; this.options = responseJson.options; for (var k in this.filter) { delete this.filter[k]; } this.selected = []; if ("undefined"!=typeof(responseJson.selected)) { this.selected = responseJson.selected; } this.filter = []; for (var l in responseJson.filter) { this.filter.push(new APIFilter(responseJson.filter[l], "none"!=responseJson.filter[l].field ? this.mainFields[responseJson.filter[l].field].type : "none")); } for (var iG in this.groups) { this.groups[iG].Clear(); delete this.groups[iG]; } this.groups = []; this.groupsIndex = {}; for (var groupID in responseJson.content) { this.groups.push(new APIGroup(this, responseJson.content[groupID].ID, responseJson.content[groupID].ENTRIES)); } if (this.tplFile!=null) { this.reloadTemplate(this.tplFile); } } } findIndexInEntries(id, data) { for (var i in data) { if (data[i].ID==id) { return i; } } return null; } findEntry(id) { if (id in this.resourcesIndex) { return this.resourcesIndex[id]; } return null; } reloadTemplate() { var _this = this; var request = new XMLHttpRequest(); request.open("GET", this.tplFile, true); request.setRequestHeader("Accept", "text/html"); request.onreadystatechange = function() { if (request.readyState != 4 || request.status != 200) return; _this.tpl = new TemplateEngine(request.responseText, _this.options); _this.render(); }; request.send(); } renderEntry(entry) { var data = entry.data; data.SHOWBUTTON = entry.show; data.SHOWCONTENT = entry.show; data.SELECTBOX = ""; return this.tpl.Render(entry.data, "LIST", (this.admin || entry.localAdmin ? ":ADMIN" : "")); } getFilter() { var filter = []; for (var i in this.filter) { filter.push(this.filter[i].Get()); } return filter; } renderFilterEntry(id) { var html = ""; var data = this.filter[id].Get(); // Render and/or combiner if (id>0) { html+= ""; } else { html+= ""; } // Render Field selector html+= ""; if ("undefined"!=typeof(this.mainFields[data.field])) { // Render operator // Render value input switch (this.mainFields[data.field].type) { case "Checkbox": html+= "ist "; html+= ""; break; case "Multiple": html+= "" +""; break; case "Select": html+= "" +""; break; case "String": html+= "" +""; break; case "Integer": html+= "" +""; break; case "DateTime": html+= ""; var datetimesplit = data.value.split(" "); html+= ""; html+= ""; break; } } // Render deletor html+= ""; return html; } renderFilter() { var ret = ""; } render() { var metaHtml = "
" + this.tpl.Render(this.meta, "META", "") + "
"; var entriesHtml = ""; for (var j in this.groups) { entriesHtml+= this.groups[j].Render(); } var addHtml = (this.admin ? "
" + this.tpl.Render(this.options, "ADD.MAIN", "") + "
" : ""); document.getElementById("content").innerHTML = metaHtml + addHtml + entriesHtml; //this.renderHelper(); } renderHelper(id = null) { var _this = this; if (null!==id && !!document.getElementById("dropzone_"+id)) { this.dropzones[id] = new Dropzone("form#dropzone_"+id, { url: document.getElementById("dropzone_"+id).action, //url: this.route + "/" + id + "/Bilder", createImageThumbnails: false, headers: { "Authorization": "Bearer " + _this.parent.jwt, "Accept": "application/json" }, dictDefaultMessage: "Datei Drop " }).on("sending", function(file, xhr, formData) { _this.Cancel(id); _this.parent.showMessages(["Datei wird hochgeladen! Dies kann je nach Größe eine Weile dauern."]); formData.append("secToken", _this.parent.secToken); }).on("complete", function(file) { if (file.xhr.status==201) { _this.parent.showMessages(["Datei erfolgreich hochgeladen!"]); } var json = _this.parent.parseResponse(file.xhr.responseText); _this.parent.parseJson(json, file.xhr.status); _this.parseContent(json, "GET", id); }); } } } class APIManager { constructor() { this.jwt = this.getCookie("jwt"); this.emulate = null; this.secToken = this.getCookie("secToken"); this.loggedIn = null; this.root = "https://app.fw-innenstadt.de"; this.scanner = new APIScanner(this); this.routes = {"/index.php/": new APIRoute(this, "/index.php/", { link: "/index.php/", name: "Start", image: "/wappen48.png", template: "/res/main.html", isPage: true, marker: "START", mainFields: []})}; this.path = this.getCookie("path"); if (this.path=="") { this.path = "/index.php/"; this.activeRoute = this.routes[this.path]; } this.Request("GET"); } Request(method, json = {}, id = null, sub = null, subid = null) { var _this = this; var request = new XMLHttpRequest(); var path = this.path + (id!=null ? "/" + id + (sub!=null ? "/" + sub + (subid!=null ? "/"+subid : "") : "") : ""); var accept = "application/json"; var loadElement = document.getElementById("loadImage"); if (null!=loadElement) { loadElement.innerHTML = ""; } json.secToken = typeof(this.secToken)!="undefined" ? this.secToken : ""; if ("undefined"!=typeof(this.activeRoute)) { var hasParam = false; // Append filter if exists if (this.activeRoute.filter.length>0) { path += "?filter="+encodeURI(JSON.stringify(this.activeRoute.getFilter())); hasParam = true; } // Append selection if (this.activeRoute.selected.length>0) { path += (hasParam ? "&" : "?")+"selected="+encodeURI(JSON.stringify(this.activeRoute.selected)); hasParam = true; } // Append print request if (typeof(json.Print)!="undefined") { path += (hasParam ? "&" : "?")+"print="+json.Print; accept = "text/html"; hasParam = true; } // Append user emulation if (this.emulate!=null) { path += (hasParam ? "&" : "?")+"emulate="+this.emulate; hasParam = true; } } request.open(method, path, true); if (this.jwt!="") { request.setRequestHeader("Authorization", "Bearer " + this.jwt); } if (method!="GET") { request.setRequestHeader("Content-Type", "application/json"); } request.setRequestHeader("Accept", accept); request.onreadystatechange = function() { if (request.readyState != 4) return; var loadElement = document.getElementById("loadImage"); if (null!=loadElement) { loadElement.innerHTML = ""; } if ("text/html"==accept) { if (200==request.status) { var windowUrl = 'about:blank'; var uniqueName = new Date(); var windowName = 'Print' + uniqueName.getTime(); var printWindow = window.open(windowUrl, windowName, 'left=50000,top=50000,width=0,height=0'); printWindow.document.write(""); printWindow.document.write(request.responseText); printWindow.document.write(""); printWindow.document.close(); printWindow.focus(); } else { _this.showMessages(["Es konnte kein Ausdruck abgerufen werden!"]); } } else if ("application/json"==accept) { var responseJson = _this.parseResponse(request.responseText); _this.parseJson(responseJson, request.status); if (request.status>=200 && request.status<300) { if (typeof(_this.activeRoute)!="undefined") { _this.activeRoute.parseContent(responseJson, method, id, sub); } } } }; request.send(JSON.stringify(json)); } Open(path) { if (typeof(this.routes[path])!="undefined") { this.path = path; this.setCookie("path", path); this.activeRoute = this.routes[path]; this.Request("GET"); } else if ("/index.php/Logout"==path) { this.path = path; this.setCookie("path", "/index.php/"); this.activeRoute = this.routes["/index.php/"]; this.Request("GET"); } } Login() { var button = document.getElementById("loginButton"); button.disabled = true; button.className = "login disabled"; var json = { "login": document.getElementById("loginUser").value, "password": document.getElementById("loginPassword").value }; this.path = "/index.php/"; this.activeRoute = this.routes[this.path]; this.setCookie("path", this.path); this.Request("POST", json); } /* "Private" functions */ registerRoutes(routes) { for (var i in routes) { if (typeof(this.routes[routes[i].link])=="undefined") { this.routes[routes[i].link] = new APIRoute(this, routes[i].link, routes[i]); } } if (typeof(this.activeRoute)=="undefined") { this.activeRoute = this.routes[this.path]; } this.renderNavigation(); } renderNavigation() { let nav = document.getElementById("navigation"); let selectnav = document.getElementById("navigation_select"); nav.innerHTML = ""; selectnav.innerHTML = ""; for (let i in this.routes) { if (this.routes[i].isPage) { let selected = this.routes[i]==this.activeRoute ? " selected" : ""; selectnav.innerHTML+= ""; let cssClass = this.routes[i]==this.activeRoute ? " class='current'" : ""; if (this.routes[i].image!=null) { nav.innerHTML+= "
  • "; } else { nav.innerHTML+= "
  • " + this.routes[i].name + "
  • "; } } } nav.innerHTML+= "
  • "; selectnav.innerHTML+= ""; } parseResponse(responseText) { var json = {}; try { json = JSON.parse(responseText); } catch (SyntaxError) { this.showMessages(["Parsen der Antwort gescheitert. Es folgt die Antwort!", responseText]); } return json; } parseJson(json, status) { if (typeof(json.status.newjwt)!="undefined" && json.status.newjwt!=null) { this.setCookie("jwt", json.status.newjwt); this.jwt = json.status.newjwt; } if (typeof(json.status.secToken)!="undefined" && json.status.secToken!=null) { this.setCookie("secToken", json.status.secToken); this.secToken = json.status.secToken; } if (typeof(json.status.loggedIn)!="undefined" && json.status.loggedIn) { if (json.status.loggedIn!=this.loggedIn) { this.changeLoginScreen(true); } if (status>=300) { this.showMessages(["Die Anfrage wurde mit Code " + status + " abgelehnt.
    (" + this.translateHttpStatus(status) + ")"]); } } else { this.changeLoginScreen(false); } if (typeof(json.messages)!="undefined") { this.showMessages(json.messages); } if (typeof(json.pages)!="undefined") { this.registerRoutes(json.pages); } } translateHttpStatus(status) { switch (status) { case 200: return "OK"; case 201: return "Erstellt"; case 400: return "Fehlerhafte Anfrage"; case 401: return "Anmeldung erforderlich"; case 403: return "Keine Berechtigung"; case 404: return "Nicht gefunden"; case 500: return "Fehler auf Server"; default: return "" + status + " = Unbekannt"; } } changeLoginScreen(newStatus) { var s = document.getElementById('header').style; if (this.loggedIn==null) { if (!newStatus) { s.display = "block"; s.opacity = 1; } } else if (newStatus) { (function fadeOut(){(s.opacity-=0.1)<0?s.display="none":setTimeout(fadeOut,40)})(); } else { var button = document.getElementById("loginButton"); button.disabled = false; button.className = "login"; s.display = "block"; (function fadeIn(){(s.opacity-=-0.1)>1?s.opacity=1:setTimeout(fadeIn,40)})(); } this.loggedIn = newStatus; document.getElementById("navigation").className = (this.loggedIn ? "" : "hidden"); document.getElementById("content").className = (this.loggedIn ? "" : "hidden"); } showMessages(messages) { for (var i in messages) { Toastify({ text: messages[i], duration: 3000, close: true, gravity: "top", // `top` or `bottom` position: "right", // `left`, `center` or `right` backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)", stopOnFocus: true, // Prevents dismissing of toast on hover }).showToast(); } } setCookie(cname, cvalue, exdays) { var d = new Date(); d.setTime(d.getTime() + (exdays*24*60*60*1000)); var expires = "expires="+ d.toUTCString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;SameSite=Strict;Secure"; } getCookie(cname){ var name = cname + "="; var decodedCookie = decodeURIComponent(document.cookie); var ca = decodedCookie.split(';'); for(var i = 0; i