Erster Commit
This commit is contained in:
@@ -1,3 +1,95 @@
|
|||||||
# plweb
|
# PlusLifeWeb
|
||||||
|
|
||||||
|
Dieses kleine PHP-Projekt bietet die Möglichkeit, Tests, die mit einem
|
||||||
|
[Pluslife-Gerät](https://altruan.de/pages/pluslife-pcr) und dem
|
||||||
|
[Pluslife Analyzer](https://virus.sucks/) durchgeführt wurden, zu speichern
|
||||||
|
und später nochmals anzuschauen.
|
||||||
|
|
||||||
|
Außerdem können Benachrichtigungen via Webhook an [Home Assistant](https://www.home-assistant.io/)
|
||||||
|
geschickt werden, um diese weiter zu verarbeiten.
|
||||||
|
|
||||||
|
Hierfür wurden folgende Komponenten benutzt:
|
||||||
|
|
||||||
|
* [Pluslife Analyzer Displayer von sistason](https://github.com/sistason/pluslife_analyzer_displayer/tree/main):
|
||||||
|
Dieses Projekt war der Ausgangspunkt für PlusLifeWeb
|
||||||
|
* jQuery
|
||||||
|
* Bootstrap
|
||||||
|
* Twig
|
||||||
|
* Chart.js
|
||||||
|
* PostgreSQL als Datenbank
|
||||||
|
|
||||||
|
**Wichtig:**
|
||||||
|
Es wird dringend davon abgeraten, PlusLifeWeb öffentlich zugänglich zu machen, da keinerlei Authentifizierung
|
||||||
|
implementiert ist! Jede Person, die PlusLifeWeb aufrufen kann, kann die Testergebnisse anschauen und auch
|
||||||
|
neue per Webhook hinzufügen!
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
PlusLifeWeb wurde unter Devuan 12 "Bookworm" entwickelt und getestet. Daher sollte es auch unter
|
||||||
|
Debian 12 funktionieren.
|
||||||
|
|
||||||
|
Grundsätzlich wird ein Webserver, z.B. Apache, benötigt.
|
||||||
|
Die Verbindung zum Webserver muß per HTTPS möglich sein, damit der Webhook von virus.sucks funktioniert.
|
||||||
|
|
||||||
|
Folgende PHP-Pakete werden benötigt:
|
||||||
|
|
||||||
|
* php-pgsql
|
||||||
|
* php-twig
|
||||||
|
* php-json
|
||||||
|
* php-curl
|
||||||
|
|
||||||
|
Zur Installation genügt es, die Dateien in einem Webserver-Verzeichnis abzulegen, in dem PHP funktioniert.
|
||||||
|
|
||||||
|
### Datenbank
|
||||||
|
|
||||||
|
In PostgreSQL wird eine Datenbanktabelle für die Tests benötigt.
|
||||||
|
|
||||||
|
Die Datenbank wird als Benutzer *postgres* angelegt (Als root: `su - postgres`).
|
||||||
|
|
||||||
|
```
|
||||||
|
# createdb plweb
|
||||||
|
```
|
||||||
|
|
||||||
|
Anschließend, weiterhin als Benutzer postgres, das Kommando `psql` aufrufen:
|
||||||
|
|
||||||
|
```
|
||||||
|
# psql plweb
|
||||||
|
```
|
||||||
|
|
||||||
|
Mit folgenden SQL-Kommandos wird dann die Tabelle sowie ein Benutzer angelegt:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE tests (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
timestamp TIMESTAMP NOT NULL,
|
||||||
|
data JSONB
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE USER plweb WITH ENCRYPTED PASSWORD 'plweb';
|
||||||
|
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE PLWEB TO plweb;
|
||||||
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA PUBLIC TO plweb;
|
||||||
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA PUBLIC TO plweb;
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Paßwort ist hier nur *plweb* - es sollte besser ein anderes benutzt werden.
|
||||||
|
|
||||||
|
### Konfiguration
|
||||||
|
|
||||||
|
In der Datei `config.php` können folgende Parameter eingestellt werden:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$hawebhook = "http://homeassistant:8123/webhook/plweb";
|
||||||
|
$dbconnstring = "host=localhost dbname=plweb user=plweb password=plweb";
|
||||||
|
$test_url = "https://example.com/plweb/test.php?";
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Variable `hawebhook` enthält die URL des Home Assistant-Webhooks, so wie dieser
|
||||||
|
von PlusLifeWeb aus (serverseitig) aufgerufen wird.
|
||||||
|
|
||||||
|
Die Datenbankkonfiguration ist in `dbconnstring` enthalten. Hier ggf. das Paßwort anpassen.
|
||||||
|
|
||||||
|
Die `test_url` zeigt zum Script test.php der PlusLifeWeb-Installation.
|
||||||
|
Diese URL wird, zusammen mit der ID des fertigen Tests, als Benachrichtigung an Home
|
||||||
|
Assistant übertragen.
|
||||||
|
|
||||||
PlusLifeWeb
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$hawebhook = "http://homeassistant:8123/webhook/plweb"; // Webhook-URL von Home Assistant
|
||||||
|
$dbconnstring = "host=localhost dbname=plweb user=plweb password=plweb";
|
||||||
|
$test_url = "https://example.com/plweb/test.php?";
|
||||||
|
|
||||||
|
?>
|
||||||
Vendored
+6
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
```
|
||||||
|
alias: Webhook Pluslife
|
||||||
|
mode: single
|
||||||
|
triggers:
|
||||||
|
- trigger: webhook
|
||||||
|
allowed_methods:
|
||||||
|
- POST
|
||||||
|
local_only: true
|
||||||
|
webhook_id: plweb
|
||||||
|
conditions: []
|
||||||
|
actions:
|
||||||
|
- if:
|
||||||
|
- condition: template
|
||||||
|
value_template: "{% if trigger.json.url is defined %}true{% endif %}"
|
||||||
|
then:
|
||||||
|
- action: notify.familie
|
||||||
|
metadata: {}
|
||||||
|
data:
|
||||||
|
message: |-
|
||||||
|
{{ trigger.json.message }}
|
||||||
|
Klicken, um den Test anzusehen.
|
||||||
|
data:
|
||||||
|
clickAction: "{{ trigger.json.url }}"
|
||||||
|
group: plweb
|
||||||
|
title: PlusLifeWeb
|
||||||
|
else:
|
||||||
|
- action: notify.familie
|
||||||
|
metadata: {}
|
||||||
|
data:
|
||||||
|
message: "{{ trigger.json.message }}"
|
||||||
|
title: PlusLifeWeb
|
||||||
|
data:
|
||||||
|
group: plweb
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An example CORS-compliant method. It will allow any GET, POST, or OPTIONS requests from any
|
||||||
|
* origin.
|
||||||
|
*
|
||||||
|
* In a production environment, you probably want to be more restrictive, but this gives you
|
||||||
|
* the general idea of what is involved. For the nitty-gritty low-down, read:
|
||||||
|
*
|
||||||
|
* - https://developer.mozilla.org/en/HTTP_access_control
|
||||||
|
* - https://fetch.spec.whatwg.org/#http-cors-protocol
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function cors() {
|
||||||
|
// Allow from any origin
|
||||||
|
header("Access-Control-Allow-Origin: *");
|
||||||
|
header('Access-Control-Max-Age: 86400'); // cache for 1 day
|
||||||
|
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
|
||||||
|
header("Access-Control-Request-Headers: content-type");
|
||||||
|
// Access-Control headers are received during OPTIONS requests
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||||
|
// header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
|
||||||
|
// header("Access-Control-Request-Headers: content-type");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function return_success() {
|
||||||
|
return json_encode(['status' => 'success', 'message' => 'OK']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function return_error($code, $msg) {
|
||||||
|
http_response_code($code);
|
||||||
|
return json_encode(['status' => 'error', 'message' => $msg]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function twig_error($twig, $code, $msg) {
|
||||||
|
http_response_code($code);
|
||||||
|
return $twig->render('error.html.twig', [ 'error' => $msg ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'Twig/autoload.php';
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
|
||||||
|
use Twig\Environment;
|
||||||
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
|
||||||
|
$loader = new FilesystemLoader(__DIR__ . '/templates');
|
||||||
|
$twig = new Environment($loader);
|
||||||
|
|
||||||
|
// --------------
|
||||||
|
|
||||||
|
$dbconn = pg_connect($dbconnstring)
|
||||||
|
or die(twig_error($twig, 'Kann Verbindung zur Datenbank nicht herstellen.'));
|
||||||
|
|
||||||
|
$query = "SELECT id,timestamp FROM tests ORDER BY timestamp DESC";
|
||||||
|
$result = pg_query($dbconn, $query)
|
||||||
|
or die(twig_error($twig, 'Datenbankfehler: ' . pg_last_error()));
|
||||||
|
|
||||||
|
$tests = pg_fetch_all($result);
|
||||||
|
pg_close($dbconn);
|
||||||
|
|
||||||
|
echo $twig->render('index.html.twig', [ 'tests' => $tests ]);
|
||||||
|
|
||||||
|
?>
|
||||||
Vendored
+7
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+2
File diff suppressed because one or more lines are too long
+115
@@ -0,0 +1,115 @@
|
|||||||
|
var chart;
|
||||||
|
var channel_colors = {
|
||||||
|
"0": {"color": "#a6cee3", "name": "Channel 1"},
|
||||||
|
"1": {"color": "#1f78b4", "name": "Channel 2"},
|
||||||
|
"2": {"color": "#b2df8a", "name": "Channel 3"},
|
||||||
|
"3": {"color": "#33a02c", "name": "Control Channel (4)"},
|
||||||
|
"4": {"color": "#fb9a99", "name": "Channel 5"},
|
||||||
|
"5": {"color": "#e31a1c", "name": "Channel 6"},
|
||||||
|
"6": {"color": "#fdbf6f", "name": "Channel 7"}
|
||||||
|
};
|
||||||
|
|
||||||
|
var channel_time_data = {
|
||||||
|
"0": new Map(),
|
||||||
|
"1": new Map(),
|
||||||
|
"2": new Map(),
|
||||||
|
"3": new Map(),
|
||||||
|
"4": new Map(),
|
||||||
|
"5": new Map(),
|
||||||
|
"6": new Map()
|
||||||
|
};
|
||||||
|
|
||||||
|
var labels = [];
|
||||||
|
var datasets = {};
|
||||||
|
for (const [channel, time_values] of Object.entries(channel_time_data)) {
|
||||||
|
datasets[channel] = {
|
||||||
|
label: channel_colors[channel].name,
|
||||||
|
borderColor: channel_colors[channel].color,
|
||||||
|
fill: false,
|
||||||
|
cubicInterpolationMode: 'monotone',
|
||||||
|
tension: 0.4,
|
||||||
|
channel: channel,
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
//TODO: gaps?
|
||||||
|
for (const [time, value] of time_values.entries()) {
|
||||||
|
if (! labels.includes(time)) labels.push(time);
|
||||||
|
datasets[channel].data.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: Object.values(datasets)
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
aspectRatio: 2,
|
||||||
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
labels: {
|
||||||
|
boxWidth: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
display: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: "Test Time [min:sec]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
display: true,
|
||||||
|
//suggestedMin: -10,
|
||||||
|
//suggestedMax: 200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
point: {
|
||||||
|
radius: 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function update_chart(obj){
|
||||||
|
for (const [channel, data] of Object.entries(obj)) {
|
||||||
|
for (var [time, value] of Object.entries(data)) {
|
||||||
|
time = Number(time); // js json keys are always strings...
|
||||||
|
channel_time_data[channel].set(time, value);
|
||||||
|
|
||||||
|
if (! chart.data.labels.includes(time)) {
|
||||||
|
chart.data.labels.push(time);
|
||||||
|
var progress_html = document.getElementById('status').getElementsByTagName('progress')[0];
|
||||||
|
|
||||||
|
time_remaining = 35*60 - time;
|
||||||
|
minutes_remaining = (time_remaining/60).toFixed(0);
|
||||||
|
seconds_remaining = (time_remaining%60);
|
||||||
|
human_readable_remaining = `${minutes_remaining}:${seconds_remaining} min`;
|
||||||
|
|
||||||
|
progress_html.innerHTML = human_readable_remaining;
|
||||||
|
progress_html.value = time;
|
||||||
|
document.getElementById('timeremaining').innerHTML = `${human_readable_remaining} remaining`;
|
||||||
|
}
|
||||||
|
|
||||||
|
chart.data.datasets.forEach((dataset) => {
|
||||||
|
if (dataset.channel == channel)
|
||||||
|
dataset.data.push(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (obj) chart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
chart = new Chart(document.getElementById('data'), config);
|
||||||
|
})();
|
||||||
|
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
function get_human_readable_time(time){
|
||||||
|
minutes = (time > 60) ? Math.floor(time/60) : 0;
|
||||||
|
seconds = (time%60).toFixed(0);
|
||||||
|
return `${minutes}:${seconds}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function result_text(result) {
|
||||||
|
const result_enum = {1: 'Negative', 2: 'Positive', 3: 'Invalid'};
|
||||||
|
if (Number.isInteger(result)) return result_enum[Number(result)];
|
||||||
|
else return result.toLowerCase().replace(/\b\w/g, s => s.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function result_color(result) {
|
||||||
|
const result_color = {1: 'success', 2: 'danger', 3: 'dark'};
|
||||||
|
|
||||||
|
if (Number.isInteger(result)) return result_color[Number(result)];
|
||||||
|
else {
|
||||||
|
switch (result.toLowerCase()) {
|
||||||
|
case "negative":
|
||||||
|
return result_color[1];
|
||||||
|
break;
|
||||||
|
case "positive":
|
||||||
|
return result_color[2];
|
||||||
|
break;
|
||||||
|
default: // includes invalid
|
||||||
|
return result_color[3];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_and_show_pluslife_result(overall_result, channel_results){
|
||||||
|
$("#testresult").append("Pluslife says: " + result_text(overall_result));
|
||||||
|
|
||||||
|
channel_results_html = '';
|
||||||
|
for (const [channel, data] of Object.entries(channel_colors)) {
|
||||||
|
var result = result_text(channel_results[Number(channel)]);
|
||||||
|
var color = result_color(channel_results[Number(channel)]);
|
||||||
|
if (channel == "3"){
|
||||||
|
result = (result == "Positive") ? "Detected" : "Not Detected";
|
||||||
|
color = (result == "Detected") ? "primary" : color;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel_results_html += `<span class="badge rounded-pill text-bg-${color} me-1">${channel}: ${result.slice(0,3)}</span>`;
|
||||||
|
}
|
||||||
|
$("#testresult_channels").append(channel_results_html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_chart(timestamp, overall_result, result_channels, sampledata){
|
||||||
|
$("#testdate").append(new Date(timestamp).toUTCString());
|
||||||
|
|
||||||
|
if (overall_result || result_channels)
|
||||||
|
parse_and_show_pluslife_result(overall_result, result_channels);
|
||||||
|
|
||||||
|
document.getElementById('datacontainer').hidden = false;
|
||||||
|
|
||||||
|
chart.data.labels.length = 0;
|
||||||
|
chart.data.datasets.forEach((dataset) => {
|
||||||
|
dataset.data.length = 0;
|
||||||
|
});
|
||||||
|
var offset = -1;
|
||||||
|
var data_index = 0;
|
||||||
|
var filled_array = Array(1000).fill(-1);
|
||||||
|
sampledata.forEach((sample) => {
|
||||||
|
var human_readable_time = get_human_readable_time(Math.floor(sample.samplingTime/10));
|
||||||
|
if (! chart.data.labels.includes(human_readable_time)){
|
||||||
|
chart.data.labels.push(human_readable_time);
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
data_index = offset*7 + sample.startingChannel;
|
||||||
|
filled_array[data_index] = sample.firstChannelResult/64;
|
||||||
|
|
||||||
|
chart.data.datasets.forEach((dataset) => {
|
||||||
|
if (dataset.channel == sample.startingChannel)
|
||||||
|
dataset.data.push(sample.firstChannelResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
chart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function show_error(text) {
|
||||||
|
console.log(text);
|
||||||
|
$("#error").append(text);
|
||||||
|
$("#error").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
if (data != null && typeof data == 'object') {
|
||||||
|
update_chart(Date.parse(json.test.data.temperatureSamples[0].time),
|
||||||
|
json.test.result.detectionResult,
|
||||||
|
json.test.result.channelResults,
|
||||||
|
json.test.data.samples);
|
||||||
|
} else {
|
||||||
|
show_error("Konnte Testdaten nicht laden");
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
|
||||||
|
<!-- <link rel="stylesheet" type="text/css" href="css/styles.css"> -->
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav class="navbar navbar-dark sticky-top bg-primary shadow mb-4 justify-content-center">
|
||||||
|
<div class="container-fliud">
|
||||||
|
<a class="navbar-brand" href="index.php">PlusLifeWeb</a>
|
||||||
|
{% if block("header") is defined %}
|
||||||
|
<span class="navbar-text">{% block header %}{% endblock %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<div>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<p/>
|
||||||
|
|
||||||
|
<script src="js/jquery.min.js"></script>
|
||||||
|
<script src="js/bootstrap.min.js"></script>
|
||||||
|
{% block js %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}PlusLifeWeb - Fehler{% endblock %}
|
||||||
|
{% block header %}Fehler{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="bg-light p-3 rounded mt-3">
|
||||||
|
<pre class="mb-0">
|
||||||
|
{{ error }}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}PlusLifeWeb{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% if tests is not empty %}
|
||||||
|
<div>Test auswählen:</div>
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
{% for test in tests %}
|
||||||
|
<tr><td><a href="test.php?id={{ test.id }}">{{ test.timestamp }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<div class="bg-light p-3 rounded mt-3">
|
||||||
|
<pre class="mb-0">Keine Tests gefunden.</pre>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
{% block js %}
|
||||||
|
<script>
|
||||||
|
var json = {{ test|raw }};
|
||||||
|
</script>
|
||||||
|
<script src="js/chart.umd.js"></script>
|
||||||
|
<script src="js/pluslife.js"></script>
|
||||||
|
<script src="js/plweb.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}PlusLifeWeb Test Analyse{% endblock %}
|
||||||
|
{% block header %}Test Analyse{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="datacontainer" hidden="hidden">
|
||||||
|
<div class="row justify-content-center chart-container" style="position: relative; min-height: 400px; max-height:600px;">
|
||||||
|
<canvas id="data"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<strong>Datum: </strong><span id="testdate"></span><br />
|
||||||
|
<strong>Ergebnis: </strong><span id="testresult"></span><br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col justify-content-center" id="testresult_channels"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col">
|
||||||
|
<span id="error" style="color: red;display: none;"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'Twig/autoload.php';
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
|
||||||
|
use Twig\Environment;
|
||||||
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
|
||||||
|
$loader = new FilesystemLoader(__DIR__ . '/templates');
|
||||||
|
$twig = new Environment($loader);
|
||||||
|
|
||||||
|
// --------------
|
||||||
|
|
||||||
|
if (!isset($_GET['id']))
|
||||||
|
die(twig_error($twig, 400, "Keine ID angegeben."));
|
||||||
|
|
||||||
|
$id = (int)$_GET['id'];
|
||||||
|
|
||||||
|
$dbconn = pg_connect($dbconnstring)
|
||||||
|
or die(twig_error($twig, 500, 'Datenbankfehler: ' . pg_last_error()));
|
||||||
|
|
||||||
|
$query = "SELECT data FROM tests WHERE id=" . $id;
|
||||||
|
$result = pg_query($dbconn, $query)
|
||||||
|
or die(twig_error($twig, 'Datenbankfehler: ' . pg_last_error()));
|
||||||
|
|
||||||
|
$json = pg_fetch_result($result, 'data')
|
||||||
|
or die(twig_error($twig, 404, 'Test nicht gefunden.'));
|
||||||
|
|
||||||
|
pg_close($dbconn);
|
||||||
|
|
||||||
|
echo $twig->render('test.html.twig', [ 'test' => $json ]);
|
||||||
|
?>
|
||||||
|
|
||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
+2
File diff suppressed because one or more lines are too long
+72
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
include 'config.php';
|
||||||
|
include 'functions.php';
|
||||||
|
|
||||||
|
cors();
|
||||||
|
|
||||||
|
// Read the raw POST data
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true)
|
||||||
|
or die(return_error(400, 'Invalid data'));
|
||||||
|
|
||||||
|
switch ($data['event']) {
|
||||||
|
case "DEVICE_READY":
|
||||||
|
case "ALREADY_TESTING":
|
||||||
|
case "TEST_STARTED":
|
||||||
|
case "CONTINUE_TEST":
|
||||||
|
echo return_success();
|
||||||
|
send_notification($data['event']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "NEW_DATA":
|
||||||
|
echo return_success();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "TEST_FINISHED":
|
||||||
|
test_finished();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
die(return_error(400, 'Unknown event ' . $data['event']));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
function send_notification($msg, $url = null) {
|
||||||
|
global $hawebhook;
|
||||||
|
|
||||||
|
$post = [ 'message' => $msg ];
|
||||||
|
if ($url != null) $post['url'] = $url;
|
||||||
|
|
||||||
|
$ch = curl_init($hawebhook);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post));
|
||||||
|
$rc = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_finished() {
|
||||||
|
global $dbconnstring;
|
||||||
|
global $data;
|
||||||
|
global $test_url;
|
||||||
|
|
||||||
|
$dbconn = pg_connect($dbconnstring)
|
||||||
|
or die(return_error(500, 'connect Datenbankfehler: ' . pg_last_error()));
|
||||||
|
|
||||||
|
$query = "INSERT INTO tests VALUES (DEFAULT, $1, $2) RETURNING id";
|
||||||
|
$values = [ date('Y-m-d H:i:s'), json_encode($data) ];
|
||||||
|
|
||||||
|
$rc = pg_query_params($dbconn, $query, $values);
|
||||||
|
|
||||||
|
if ($rc) {
|
||||||
|
$id = pg_fetch_result($rc, 0, 0);
|
||||||
|
echo return_success();
|
||||||
|
send_notification("Test fertig. ID: " . $id, $test_url + $id);
|
||||||
|
} else {
|
||||||
|
echo return_error(500, 'insert Datenbankfehler: ' . pg_last_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
pg_close($dbconn);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
Reference in New Issue
Block a user