большое обновление фронта
This commit is contained in:
parent
4c94a7271a
commit
a1f86691a4
@ -19,5 +19,7 @@ from . import views
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.view_index, name='index'),
|
path('', views.view_index, name='index'),
|
||||||
|
path('fetch/stats', views.view_stats, name='fetch-stats'),
|
||||||
|
path('fetch/tank-chart', views.view_tank_chart, name='fetch-tank-chart'),
|
||||||
# path('methods/<str:method_name>', views.call_method, name='call_method')
|
# path('methods/<str:method_name>', views.call_method, name='call_method')
|
||||||
]
|
]
|
||||||
|
@ -1,8 +1,30 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.db.models import Manager
|
from django.db.models import Manager
|
||||||
|
|
||||||
# Create your views here.
|
# только для тестирования!
|
||||||
|
import requests
|
||||||
|
|
||||||
|
TEST_BASE_FETCH = "https://test.wawaa.ru/dev-fetch.php"
|
||||||
|
|
||||||
|
|
||||||
def view_index(request):
|
def view_index(request):
|
||||||
return render(request, 'index.html')
|
return render(request, 'index.html')
|
||||||
|
|
||||||
|
|
||||||
|
def view_stats(request):
|
||||||
|
# только для тестирования!
|
||||||
|
res = requests.get(TEST_BASE_FETCH + "?stats", headers={'Authorization': os.getenv("TEST_AUTH")})
|
||||||
|
response = HttpResponse(res.content)
|
||||||
|
response.headers["Content-type"] = response.headers["Content-type"]
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def view_tank_chart(request):
|
||||||
|
# только для тестирования!
|
||||||
|
res = requests.get(TEST_BASE_FETCH + "?tank_chart", headers={'Authorization': os.getenv("TEST_AUTH")})
|
||||||
|
response = HttpResponse(res.content)
|
||||||
|
response.headers["Content-type"] = response.headers["Content-type"]
|
||||||
|
return response
|
||||||
|
@ -28,7 +28,7 @@ PROJECT_ROOT = os.path.dirname(__file__)
|
|||||||
|
|
||||||
SECRET_KEY = os.getenv('DJANGO_SECRET')
|
SECRET_KEY = os.getenv('DJANGO_SECRET')
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['localhost']
|
ALLOWED_HOSTS = ['localhost', '10.8.0.2']
|
||||||
# CSRF_TRUSTED_ORIGINS = ['https://ospaz.wawaa.ru']
|
# CSRF_TRUSTED_ORIGINS = ['https://ospaz.wawaa.ru']
|
||||||
|
|
||||||
# HTTPS settings https://docs.djangoproject.com/en/5.0/topics/security/
|
# HTTPS settings https://docs.djangoproject.com/en/5.0/topics/security/
|
||||||
|
@ -16,10 +16,8 @@ Including another URLconf
|
|||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
from django.contrib.staticfiles.views import serve
|
from django.contrib.staticfiles.views import serve
|
||||||
|
|
||||||
import index.urls
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include('index.urls')),
|
path('', include('index.urls')),
|
||||||
|
@ -1,27 +1,26 @@
|
|||||||
/* TODO исправить стили, тут верхней навигации вообще нет */
|
|
||||||
|
|
||||||
|
|
||||||
/* ========== THEME ========== */
|
/* ========== THEME ========== */
|
||||||
body {
|
body {
|
||||||
--text-color: #111;
|
--text-color: #262626;
|
||||||
--brand-color: #231765;
|
--text-color2: #3d3d3d;
|
||||||
--bkg-color-blue: #0066e3;
|
|
||||||
|
|
||||||
--bkg-color: #fff;
|
--brand-bg: #EDF3FE;
|
||||||
--bkg-color2: #ccc;
|
--brand-text: #5488F7;
|
||||||
--bkg-color3: #aaa;
|
|
||||||
|
--bg-color: #FEFEFE;
|
||||||
|
--bg-selected: #F1F1F1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
/* defaults to dark theme */
|
/* defaults to dark theme */
|
||||||
body {
|
body {
|
||||||
--text-color: #eee;
|
--text-color: #eee;
|
||||||
--brand-color: #654dea;
|
--text-color2: #bbb;
|
||||||
--bkg-color-blue: #003aac;
|
|
||||||
|
|
||||||
--bkg-color: #121212;
|
--brand-bg: #393E50;
|
||||||
--bkg-color2: #202020;
|
--brand-text: #5F93F3;
|
||||||
--bkg-color3: #353435;
|
|
||||||
|
--bg-color: #2d2c33;
|
||||||
|
--bg-selected: #424248;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,50 +29,28 @@ body {
|
|||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
*, *::before, *::after {
|
||||||
background: var(--bkg-color);
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
body {
|
||||||
text-align: center;
|
background: var(--bg-color);
|
||||||
margin: 1em 3em;
|
margin: 0; /* браузеры зачем-то ставят свое значение */
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
margin: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== MAIN STYLES ========== */
|
/* ========== MAIN STYLES ========== */
|
||||||
|
|
||||||
#header-wrapper {
|
header > h1 {
|
||||||
display: flex;
|
text-align: center;
|
||||||
margin: 1em;
|
background-color: var(--brand-bg);
|
||||||
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header-wrapper * {
|
header * {
|
||||||
color: var(--brand-color);
|
color: var(--brand-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
header > * {
|
|
||||||
margin: auto 0.5em;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
header > div > * {
|
|
||||||
display: block;
|
|
||||||
margin: 0.1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#logo-text {
|
|
||||||
font-weight: bolder;
|
|
||||||
font-size: xx-large;
|
|
||||||
}
|
|
||||||
|
|
||||||
#logo-image {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:se="http://svg-edit.googlecode.com" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="85" height="80" style=""> <title>favicon</title>
|
<svg xmlns="http://www.w3.org/2000/svg" width="85" height="85" style=""><title>favicon</title>
|
||||||
<rect id="backgroundrect" width="100%" height="100%" x="0" y="0" fill-opacity="0.0" stroke="none" style="" class=""/> <g class="currentLayer" style=""><title>Layer 1</title><rect fill="#4a90d6" stroke="#222222" stroke-width="2" stroke-linejoin="round" stroke-dashoffset="" fill-rule="nonzero" id="svg_38" x="77.39605712890625" y="107.25747680664062" width="7" height="0" style="color: rgb(0, 0, 0);" class=""/><path fill="#4a90d6" stroke="#222222" stroke-width="2" stroke-linejoin="round" stroke-dashoffset="" fill-rule="nonzero" marker-start="" marker-mid="" marker-end="" id="svg_42" d="M4.913043206854184,73.1181132105101 L23.991979764689916,39.72997423429874 L43.07091632252451,73.1181132105101 L4.913043206854184,73.1181132105101 z" style="color: rgb(0, 0, 0);" class=""/><path fill="#4a90d6" stroke="#222222" stroke-width="2" stroke-linejoin="round" stroke-dashoffset="" fill-rule="nonzero" marker-start="" marker-mid="" marker-end="" d="M43.558975285215695,73.08701234133133 L62.63791184305143,39.698873365119965 L81.71684840088602,73.08701234133133 L43.558975285215695,73.08701234133133 z" style="color: rgb(0, 0, 0);" class="" id="svg_47"/><path fill="#4a90d6" stroke="#222222" stroke-width="2" stroke-linejoin="round" stroke-dashoffset="" fill-rule="nonzero" marker-start="" marker-mid="" marker-end="" d="M24.25514847628625,39.17074458868393 L43.33408503412198,5.782605612472565 L62.41302159195658,39.17074458868393 L24.25514847628625,39.17074458868393 z" style="color: rgb(0, 0, 0);" class="" id="svg_46"/><path fill="#4a90d6" stroke="#222222" stroke-width="2" stroke-linejoin="round" stroke-dashoffset="" fill-rule="nonzero" marker-start="" marker-mid="" marker-end="" id="svg_48" d="M33.91763093443063,55.92877816603246 L43.29425964116126,40.29240521591228 L52.6708883478924,55.92877816603246 L33.91763093443063,55.92877816603246 z" style="color: rgb(0, 0, 0);" class=""/></g></svg>
|
<rect id="backgroundrect" width="100%" height="100%" x="0" y="0" fill-opacity="0.0" stroke="none" style="" class=""/> <g class="currentLayer" style=""><title>Layer 1</title><rect fill="#4a90d6" stroke="#222222" stroke-width="2" stroke-linejoin="round" stroke-dashoffset="" fill-rule="nonzero" id="svg_38" x="77.39605712890625" y="107.25747680664062" width="7" height="0" style="color: rgb(0, 0, 0);" class=""/><path fill="#4a90d6" stroke="#222222" stroke-width="2" stroke-linejoin="round" stroke-dashoffset="" fill-rule="nonzero" marker-start="" marker-mid="" marker-end="" id="svg_42" d="M4.913043206854184,73.1181132105101 L23.991979764689916,39.72997423429874 L43.07091632252451,73.1181132105101 L4.913043206854184,73.1181132105101 z" style="color: rgb(0, 0, 0);" class=""/><path fill="#4a90d6" stroke="#222222" stroke-width="2" stroke-linejoin="round" stroke-dashoffset="" fill-rule="nonzero" marker-start="" marker-mid="" marker-end="" d="M43.558975285215695,73.08701234133133 L62.63791184305143,39.698873365119965 L81.71684840088602,73.08701234133133 L43.558975285215695,73.08701234133133 z" style="color: rgb(0, 0, 0);" class="" id="svg_47"/><path fill="#4a90d6" stroke="#222222" stroke-width="2" stroke-linejoin="round" stroke-dashoffset="" fill-rule="nonzero" marker-start="" marker-mid="" marker-end="" d="M24.25514847628625,39.17074458868393 L43.33408503412198,5.782605612472565 L62.41302159195658,39.17074458868393 L24.25514847628625,39.17074458868393 z" style="color: rgb(0, 0, 0);" class="" id="svg_46"/><path fill="#4a90d6" stroke="#222222" stroke-width="2" stroke-linejoin="round" stroke-dashoffset="" fill-rule="nonzero" marker-start="" marker-mid="" marker-end="" id="svg_48" d="M33.91763093443063,55.92877816603246 L43.29425964116126,40.29240521591228 L52.6708883478924,55.92877816603246 L33.91763093443063,55.92877816603246 z" style="color: rgb(0, 0, 0);" class=""/></g></svg>
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.0 KiB |
160
static/js/index-main.js
Normal file
160
static/js/index-main.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
// скрипт страницы index, на которой отображаются графики
|
||||||
|
|
||||||
|
// интервал обновления статуса в миллисекундах
|
||||||
|
const RELOAD_STATS_INTERVAL = 10000
|
||||||
|
|
||||||
|
// интервал обновления графика - 1% показываемого времени (2 недели)
|
||||||
|
// const RELOAD_CHART_INTERVAL = (3600 * 1000 * 24 * 14) / 100
|
||||||
|
// const RELOAD_CHART_INTERVAL = (3600 * 1000 * 24) / 100
|
||||||
|
const RELOAD_CHART_INTERVAL = 30000
|
||||||
|
|
||||||
|
const DATETIME_FORMAT = "DD MMM YYYY HH:mm:ss"
|
||||||
|
|
||||||
|
function unpackBits(num, desc) {
|
||||||
|
let out = ""
|
||||||
|
for (let i = 0; i < desc.length; i++) {
|
||||||
|
if (desc[i] !== null) {
|
||||||
|
if ((num & (1 << i)) !== 0) {
|
||||||
|
// бит установлен
|
||||||
|
if (out.length === 0) {
|
||||||
|
out = desc[i]
|
||||||
|
} else {
|
||||||
|
out += " + "
|
||||||
|
out += desc[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция для линейной аппроксимации набора точек. Для заданных точек находит коэфицент a из уравнения y=ax+b.
|
||||||
|
* Уравнение подбирается при помощи метода наименьших квадратов.
|
||||||
|
* @param dataset набор точек в виде [[timestamp, val], [timestamp, val], ...], где timestamp - значение по X, val - значение по Y
|
||||||
|
* @returns {number}, коэфицент a
|
||||||
|
*/
|
||||||
|
function approximateWithTimestamps(dataset) {
|
||||||
|
|
||||||
|
// для точных расчетов нужно сместить timestamp
|
||||||
|
const timestamp_offset = dataset[0][0]
|
||||||
|
|
||||||
|
// сумма (x[i] * y[i])
|
||||||
|
let sum_x_mul_y = 0
|
||||||
|
for (let i = 0; i < dataset.length; i++) {
|
||||||
|
sum_x_mul_y += (dataset[i][0] - timestamp_offset) * dataset[i][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// сумма всех x[i]
|
||||||
|
let sum_x = 0;
|
||||||
|
for (let i = 0; i < dataset.length; i++) {
|
||||||
|
sum_x += dataset[i][0] - timestamp_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
// сумма всех y[i]
|
||||||
|
let sum_y = 0;
|
||||||
|
for (let i = 0; i < dataset.length; i++) {
|
||||||
|
sum_y += dataset[i][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// сумма всех x[i]^2
|
||||||
|
let sum_x_mul_x = 0;
|
||||||
|
for (let i = 0; i < dataset.length; i++) {
|
||||||
|
sum_x_mul_x += Math.pow(dataset[i][0] - timestamp_offset, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// вычисление коэфицента a из формулы y=ax+b
|
||||||
|
// нам нужен только он
|
||||||
|
return (dataset.length * sum_x_mul_y - sum_x * sum_y) / (sum_x_mul_x - Math.pow(sum_x, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeRequest(url) {
|
||||||
|
let response = await fetch(url)
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log('fetch(' + url + ') failed. Status Code: ' + response.status);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadAllData() {
|
||||||
|
return await makeRequest('/fetch/all')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadChartData() {
|
||||||
|
return (await makeRequest('/fetch/tank-chart'))['tank_chart']
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLastUpdates() {
|
||||||
|
return (await makeRequest('/fetch/stats'))['stats']
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateStatus() {
|
||||||
|
let dataset = await loadLastUpdates()
|
||||||
|
|
||||||
|
// последнее обновление
|
||||||
|
document.getElementById("tank-last-update").innerHTML = moment(new Date(dataset['tank']['last_update'] * 1000)).format(DATETIME_FORMAT)
|
||||||
|
document.getElementById("pump-last-update").innerHTML = moment(new Date(dataset['pump']['last_update'] * 1000)).format(DATETIME_FORMAT)
|
||||||
|
|
||||||
|
//<p>Уровень воды <span id="tank-level-dir"></span></p>
|
||||||
|
const last_radar_values = dataset['tank']['last_radar_values']
|
||||||
|
if (last_radar_values.length === 0) {
|
||||||
|
document.getElementById("tank-level-dir").innerHTML = "(?)";
|
||||||
|
} else {
|
||||||
|
document.getElementById("tank-level-dir").innerHTML = approximateWithTimestamps(last_radar_values) < 0 ? '↘' : '↗';
|
||||||
|
}
|
||||||
|
|
||||||
|
//<p>Текущий уровень воды: <span id="tank-level-now"></span>%</p>
|
||||||
|
document.getElementById("tank-level-now").innerHTML = dataset['tank']['last_percentage']
|
||||||
|
|
||||||
|
//<p>Текущее значение с радара: <span id="tank-raw-now"></span></p>
|
||||||
|
document.getElementById("tank-raw-now").innerHTML = dataset['tank']['last_radar']
|
||||||
|
|
||||||
|
//<p>Статус: <span id="tank-status"></span></p>
|
||||||
|
const shur_status_bits = ['нужна вода', 'поплавок нижний', 'поплавок верхний', 'поплавок аварийный']
|
||||||
|
document.getElementById("tank-status").innerHTML = unpackBits(dataset['tank']['status_reg'], shur_status_bits) + " (" + dataset['tank']['status_reg'] + ")";
|
||||||
|
|
||||||
|
//<p>Частота ПЧ: <span id="pump-vfd-freq"></span>Гц</p>
|
||||||
|
document.getElementById("pump-vfd-freq").innerHTML = dataset['pump']['vfd_freq']
|
||||||
|
|
||||||
|
//<p>Ток ПЧ: <span id="pump-vfd-curr"></span>А</p>
|
||||||
|
document.getElementById("pump-vfd-curr").innerHTML = dataset['pump']['vfd_curr']
|
||||||
|
|
||||||
|
//<p>Ошибка ПЧ: <span id="pump-vfd-error"></span></p>
|
||||||
|
document.getElementById("pump-vfd-error").innerHTML = dataset['pump']['vfd_err']
|
||||||
|
|
||||||
|
//<p>Регистр аварий: <span id="pump-alarms"></span></p>
|
||||||
|
document.getElementById("pump-alarms").innerHTML = dataset['pump']['alarms']
|
||||||
|
|
||||||
|
//<p>Состояние КА: <span id="pump-stage"></span></p>
|
||||||
|
document.getElementById("pump-stage").innerHTML = dataset['pump']['pump_stage']
|
||||||
|
|
||||||
|
//<p>Текущий расход: <span id="pump-flow-meter"></span>м³</p>
|
||||||
|
document.getElementById("pump-flow-meter").innerHTML = dataset['pump']['flow_meter']
|
||||||
|
|
||||||
|
}
|
||||||
|
// состояния конченого автомата
|
||||||
|
// $states = [
|
||||||
|
// 0 => "отключен",
|
||||||
|
// 2 => "инициализация: установка задвижек в начальное состояние",
|
||||||
|
// 21 => "инициализация: закрытие задвижек 23.5 и 23.6",
|
||||||
|
// 31 => "инициализация: открытие задвижек 23.5 и 23.6",
|
||||||
|
// 99 => "<span class=\"value-bad\">авария</span>",
|
||||||
|
//
|
||||||
|
// 100 => "ожидание сигнала на перекачку воды",
|
||||||
|
//
|
||||||
|
// 102 => "запуск: открытие задвижки 23.6",
|
||||||
|
// 110 => "запуск: ожидание сигнала от датчика уровня поз.36",
|
||||||
|
// 121 => "запуск: открытие задвижек насоса",
|
||||||
|
// 131 => "запуск: закрытие задвижки 23.6",
|
||||||
|
// 141 => "запуск: пуск ПЧ",
|
||||||
|
//
|
||||||
|
// 200 => "<span class=\"value-good\">работает</span>",
|
||||||
|
//
|
||||||
|
// 202 => "остановка: закрытие задвижек 23.3 и 23.4",
|
||||||
|
// 211 => "остановка: ожидание остановки ПЧ",
|
||||||
|
// 221 => "остановка: перевод запорной арматуры в исходное состояние",
|
||||||
|
// 231 => "остановка: работа компрессора",
|
||||||
|
// 235 => "остановка: сброс конденсата клапанами 25.*"
|
||||||
|
// ];
|
@ -7,6 +7,7 @@
|
|||||||
<title> {% block title %} Мониторинг резервуара {% endblock %} </title>
|
<title> {% block title %} Мониторинг резервуара {% endblock %} </title>
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
|
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
|
||||||
|
{% block head %} {% endblock %}
|
||||||
{% block styles %} {% endblock %}
|
{% block styles %} {% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -1,164 +1,137 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<script src="{% static 'js/chart-4.4.1.umd.js' %}"></script>
|
||||||
|
<script src="{% static 'js/moment-2.js' %}"></script>
|
||||||
|
<script src="{% static 'js/chartjs-adapter-moment.js' %}"></script>
|
||||||
|
|
||||||
|
<script src="{% static 'js/index-main.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
<style>
|
<style>
|
||||||
|
.content-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
.content-block {
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-good {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-bad {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<h1 class="page-header"> Мониторинг водозаборного узла </h1>
|
<h1> Мониторинг водозаборного узла </h1>
|
||||||
{% if user.is_superuser %}
|
{% if user.is_superuser %}
|
||||||
<a href="/admin">Админка</a>
|
<a href="/admin">Админка</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<script>
|
|
||||||
function getAccessToken(new_value) {
|
|
||||||
if (new_value === undefined || new_value === null || new_value === "") {
|
|
||||||
let res = localStorage.getItem("access_token")
|
|
||||||
if (res === null) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
} else {
|
|
||||||
console.log(`Storing ${new_value} as token`)
|
|
||||||
localStorage.setItem("access_token", new_value)
|
|
||||||
document.getElementById('current_access_token').innerText = new_value
|
|
||||||
return new_value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function sendRequest(method, params) {
|
|
||||||
let url = `/methods/${method}`
|
|
||||||
if (params !== undefined && params !== null) {
|
|
||||||
url += "?" + new URLSearchParams(params)
|
|
||||||
}
|
|
||||||
return await fetch(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeRequest(view, method, inputs) {
|
<div>
|
||||||
let params = {}
|
<div class="content-wrapper">
|
||||||
for (let k in inputs) {
|
<div class="content-block">
|
||||||
let element = document.getElementById(inputs[k])
|
<h2>Состояние насосной станции</h2>
|
||||||
const name = element.name
|
<p>Последнее обновление: <span id="pump-last-update"></span></p>
|
||||||
let val = element.value
|
<p>Частота ПЧ: <span id="pump-vfd-freq"></span>Гц</p>
|
||||||
if (name === "access_token") {
|
<p>Ток ПЧ: <span id="pump-vfd-curr"></span>А</p>
|
||||||
val = getAccessToken(val)
|
<p>Ошибка ПЧ: <span id="pump-vfd-error"></span></p>
|
||||||
}
|
<p>Регистр аварий: <span id="pump-alarms"></span></p>
|
||||||
if (val.length > 0)
|
<p>Состояние КА: <span id="pump-stage"></span></p>
|
||||||
params[name] = val
|
<p>Текущий расход: <span id="pump-flow-meter"></span>м³</p>
|
||||||
}
|
|
||||||
let res = await sendRequest(method, params)
|
|
||||||
const text = await res.text()
|
|
||||||
document.getElementById(view).innerText = text
|
|
||||||
|
|
||||||
// чтобы запоминался токен
|
|
||||||
try {
|
|
||||||
let j = JSON.parse(text)
|
|
||||||
getAccessToken(j["response"]["access_token"])
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% for method in api_methods %}
|
|
||||||
<div class="method-div">
|
|
||||||
<details>
|
|
||||||
<summary>{{ method.name }}</summary>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3>Описание</h3>
|
|
||||||
<p>
|
|
||||||
{{ method.doc | safe }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Параметры</h3>
|
|
||||||
{% if method.params %}
|
|
||||||
<div class="table-wrapper"><table>
|
|
||||||
<tr>
|
|
||||||
<th>Название</th>
|
|
||||||
<th>Тип</th>
|
|
||||||
<th>Описание</th>
|
|
||||||
<th>Обязательный</th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% for param in method.params %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ param.name }}</td>
|
|
||||||
<td>{{ param.type }}</td>
|
|
||||||
<td>{{ param.description | safe }}</td>
|
|
||||||
<td>{{ param.required }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table></div>
|
|
||||||
{% else %}
|
|
||||||
<p>
|
|
||||||
Этот метод не принимает параметров.
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<h3>Результат</h3>
|
|
||||||
<p>
|
|
||||||
{{ method.returns | safe }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Ссылка на метод (без параметров): <a href="/methods/{{ method.name }}">{{ method.name }}</a>
|
|
||||||
</p>
|
|
||||||
<details>
|
|
||||||
<summary>Конструктор</summary>
|
|
||||||
<div class="constructor-wrapper" id="view-{{ method.name }}">
|
|
||||||
<div class="constructor-fields">
|
|
||||||
<div style="">
|
|
||||||
<h3>Параметры</h3>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if method.params %}
|
|
||||||
{% for param in method.params %}
|
|
||||||
<div class="constructor-param">
|
|
||||||
<label for="param-{{ method.name }}-{{ param.name }}">{{ param.name }}</label>
|
|
||||||
<input type="text" name="{{ param.name }}" id="param-{{ method.name }}-{{ param.name }}">
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<div class="constructor-param">
|
|
||||||
<p>
|
|
||||||
Этот метод не принимает параметров.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="constructor-param">
|
|
||||||
<button onclick="makeRequest('result-{{ method.name }}', '{{ method.name }}',
|
|
||||||
[{% for param in method.params %}'param-{{ method.name }}-{{ param.name }}', {% endfor %}])">Выполнить</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="constructor-result">
|
|
||||||
<h3>Результат</h3>
|
|
||||||
<hr>
|
|
||||||
<pre id="result-{{ method.name }}"></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</details>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
<div class="content-block">
|
||||||
|
<h2>Состояние резервуара</h2>
|
||||||
<div style="text-align: center; background: var(--bkg-color2); margin: 0; margin-top: 3em; padding: 2em; overflow-wrap: break-word;">
|
<p>Последнее обновление: <span id="tank-last-update"></span></p>
|
||||||
Перейти в <a href="/admin">админку</a>.
|
<p>Текущий уровень воды: <span id="tank-level-now"></span>%</p>
|
||||||
<div>
|
<p>Текущее значение с радара: <span id="tank-raw-now"></span></p>
|
||||||
Текущий токен: <i id="current_access_token"></i><br><a onclick="localStorage.clear(); document.getElementById('current_access_token').innerText = ''">Сбросить</a>
|
<p>Уровень воды <span id="tank-level-dir"></span></p>
|
||||||
|
<p>Статус: <span id="tank-status"></span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="content-block chart">
|
||||||
|
<h2> Уровень воды в резервуаре, %</h2>
|
||||||
|
<div id="canvas-wrapper"><canvas id="water_level"></canvas></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.onload = (event) => {
|
moment.locale('ru')
|
||||||
const at = localStorage.getItem("access_token")
|
|
||||||
if (at !== null) {
|
let bodyStyles = window.getComputedStyle(document.body);
|
||||||
document.getElementById('current_access_token').innerText = at
|
|
||||||
|
const chart_dataset = {
|
||||||
|
label: "Резервуар",
|
||||||
|
color: bodyStyles.getPropertyValue('--brand-text'),
|
||||||
|
data: [],
|
||||||
|
pointRadius: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Chart.defaults.color = bodyStyles.getPropertyValue('--text-color2');
|
||||||
|
|
||||||
|
const chart = new Chart("water_level", {
|
||||||
|
type: "line",
|
||||||
|
data: {
|
||||||
|
datasets: [chart_dataset]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
legend: { display: false },
|
||||||
|
scales: {
|
||||||
|
y: { min: 0, max: 100 },
|
||||||
|
x: {
|
||||||
|
type: 'time',
|
||||||
|
time: {
|
||||||
|
unit: 'day'
|
||||||
|
},
|
||||||
|
scaleLabel: {
|
||||||
|
labelString: 'Timestamp'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
</script>
|
});
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", (event) => {
|
||||||
|
// запуск обновления статистики
|
||||||
|
updateStatus().then(() => {
|
||||||
|
setInterval(() => {
|
||||||
|
updateStatus().then()
|
||||||
|
}, RELOAD_STATS_INTERVAL)
|
||||||
|
})
|
||||||
|
|
||||||
|
function updateChart(data) {
|
||||||
|
chart_dataset.data = data.map((e) => {
|
||||||
|
return {x: new Date(e[0] * 1000), y: e[1]}
|
||||||
|
})
|
||||||
|
chart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadChartData().then((data) => {
|
||||||
|
updateChart(data)
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
loadChartData().then((d) => {
|
||||||
|
updateChart(d)
|
||||||
|
})
|
||||||
|
}, RELOAD_CHART_INTERVAL)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user