diff --git a/server/api.py b/server/api.py index ab7651a..ddc8b51 100644 --- a/server/api.py +++ b/server/api.py @@ -2,7 +2,7 @@ import threading from wsgiref.simple_server import make_server, WSGIRequestHandler from operator import itemgetter from departures import Departure -from bottle import route, error, run, get, ServerAdapter, static_file +from bottle import route, error, run, get, ServerAdapter, static_file, request class Server(ServerAdapter): server = None @@ -34,13 +34,42 @@ class API: return {'stops': main.config["stops"]} @get("/static/") - def index(file: str): + def static(file: str): return static_file(file, root="static") @get("/") - def static(): + def index(): return static_file("index.html", root="static") + @get("/admin") + def admin(): + return static_file("admin.html", root="static") + + @route('/admin/devices', method='POST') + def admin_devices(): + if main.config["admin"]["secret"] != request.forms.get('secret'): + return {'error': "unauthorized"} + return {'devices': main.controller.json()} + + @route('/admin/devices/resend', method='POST') + def admin_devices_resend(): + if main.config["admin"]["secret"] != request.forms.get('secret'): + return {'error': "unauthorized"} + for i, d in enumerate(main.controller.devices): + if d.stop_id and str(i) == request.forms.get('index'): + for dep in Departure.get(d.stop_id): + dep.resend(d.id) + return {'success': True} + + @route('/admin/devices/clear', method='POST') + def admin_devices_clear(): + if main.config["admin"]["secret"] != request.forms.get('secret'): + return {'error': "unauthorized"} + for i, d in enumerate(main.controller.devices): + if str(i) == request.forms.get('index'): + d.clear() + return {'success': True} + @error(404) def error404(err): return '' diff --git a/server/static/admin.css b/server/static/admin.css new file mode 100644 index 0000000..9c34165 --- /dev/null +++ b/server/static/admin.css @@ -0,0 +1,12 @@ +.devices {display: flex; flex-direction: column; gap: 10px;} +.devices > * {background-color: var(--alt-bg); border-radius: 8px; box-shadow: 0 2px 10px 0 #0005; border: 1px solid var(--border-color); overflow: hidden;} + +.devices .header {cursor: pointer; padding: 15px; display: grid; grid-template-columns: 1fr max-content; user-select: none;} +.devices .id {font-weight: 700;} +.devices .settings {padding: 15px; border-top: 1px solid var(--border-color2); background: var(--alt-bg2); display: none;} +.devices .settings.visible {display: block;} + +.devices .actions {display: flex; gap: 10px; flex-wrap: wrap; align-items: center;} +.devices .actions::before {content: "Rychlé akce:"; font-weight: 500; font-size: 90%; opacity: .5; text-transform: uppercase;} + +.login form input {margin-bottom: 15px; display: block;} \ No newline at end of file diff --git a/server/static/admin.html b/server/static/admin.html new file mode 100644 index 0000000..a93de46 --- /dev/null +++ b/server/static/admin.html @@ -0,0 +1,39 @@ + + + + Administrace + + + + + + +
+
+

Zařízení

+
+
+
+
{{ d.id }}
+
{{ stops[d.stop_id] ? stops[d.stop_id].name : "Nenastaveno" }}
+
+
+
+ + +
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/server/static/admin.js b/server/static/admin.js new file mode 100644 index 0000000..4d7385f --- /dev/null +++ b/server/static/admin.js @@ -0,0 +1,57 @@ + +const { createApp } = Vue; +let app = createApp({ + data() { + return { + // logged: false, + // secret: "", + logged: true, + secret: "TajneHeslo", + stops: {}, + devices: [] + } + }, + methods: { + async update() { + let devices = await api("/admin/devices", {secret: this.secret}); + let stops = await api("/stops"); + if(devices.error) { + alert("Neplatné heslo!"); + return; + } + this.devices = devices.devices; + this.stops = stops.stops; + this.logged = true; + }, + async clear(index) { + await api("/admin/devices/clear", {index, secret: this.secret}); + }, + async resend(index) { + await api("/admin/devices/resend", {index, secret: this.secret}); + } + } +}).mount('#app'); +async function api(url, data) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if(this.readyState !== 4) return; + if(!this.responseText) reject(this); + try { + resolve(JSON.parse(this.responseText)); + } catch(e) { + resolve(null); + } + }; + xhr.open(data ? "POST" : "GET", url, true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + var str = []; + for (var key in data) { + if (data.hasOwnProperty(key)) { + str.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key])) + } + } + xhr.send(str.join("&")); + }); +} +app.update(); \ No newline at end of file diff --git a/server/static/departure-list.js b/server/static/departure-list.js index b3d5add..438cea4 100644 --- a/server/static/departure-list.js +++ b/server/static/departure-list.js @@ -7,34 +7,50 @@ let app = createApp({ interval: null, stop_id: null, stops: [], - departures: [] + departures: [], + filtered_departures: [], + filter: false } }, methods: { async set() { - localStorage.setItem("favstop", app.$data.stop_id); + localStorage.setItem("favstop", this.stop_id); this.update(); }, async update() { - if(app.$data.stop_id) app.$data.departures = (await api("/departures/"+app.$data.stop_id)).departures; + if(!this.stop_id) return; + let res = await api("/departures/"+this.stop_id); + let filtered_out = []; + this.departures = []; + this.filtered_departures = []; + for(let i=0; i { - app.$data.top = document.querySelector('html').scrollTop; + this.top = document.querySelector('html').scrollTop; }); - app.$data.interval = setInterval(app.update, 5000); - app.update(); + this.interval = setInterval(this.update, 5000); + this.update(); } } }).mount('#app'); diff --git a/server/static/index.html b/server/static/index.html index 92cb1f3..f5ccaf9 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -10,15 +10,25 @@
@@ -26,13 +36,17 @@
poslední zastávka
zpoždění
odjezd
-