web_kbn improvements

This commit is contained in:
Evgeny Zinoviev 2024-02-19 04:45:08 +03:00
parent 847ee95d12
commit 952e41d594
6 changed files with 104 additions and 53 deletions

View File

@ -39,51 +39,52 @@ class WebKbnConfig(AppConfigUnit):
} }
common_static_files = [ # files marked with + at the beginning are included by default
'bootstrap.min.css', common_static_files = {
'bootstrap.bundle.min.js', '+bootstrap.min.css': 1,
'polyfills.js', '+bootstrap.bundle.min.js': 1,
'app.js', '+polyfills.js': 1,
'app.css' '+app.js': 6,
] '+app.css': 6,
static_version = 4 'hls.js': 1
}
routes = web.RouteTableDef() routes = web.RouteTableDef()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
lang_context_var = ContextVar('lang', default=Translation.DEFAULT_LANGUAGE) lang_context_var = ContextVar('lang', default=Translation.DEFAULT_LANGUAGE)
def get_js_link(file, version=static_version) -> str: def get_js_link(file, version) -> str:
if is_development_mode(): if is_development_mode():
version = int(time.time()) version = int(time.time())
if version: file += f'?version={version}'
file += f'?version={version}'
return f'<script src="{config.app_config["assets_public_path"]}/{file}" type="text/javascript"></script>' return f'<script src="{config.app_config["assets_public_path"]}/{file}" type="text/javascript"></script>'
def get_css_link(file, version=static_version) -> str: def get_css_link(file, version) -> str:
if is_development_mode(): if is_development_mode():
version = int(time.time()) version = int(time.time())
if version: file += f'?version={version}'
file += f'?version={version}'
return f'<link rel="stylesheet" type="text/css" href="{config.app_config["assets_public_path"]}/{file}">' return f'<link rel="stylesheet" type="text/css" href="{config.app_config["assets_public_path"]}/{file}">'
def get_head_static(files=None) -> str: def get_head_static(additional_files=None) -> str:
buf = StringIO() buf = StringIO()
if files is None: if additional_files is None:
files = [] additional_files = []
for file in common_static_files + files:
try: for file, version in common_static_files.items():
q_ind = file.index('?') enabled_by_default = file.startswith('+')
v = file[q_ind+1:] if not enabled_by_default and file not in additional_files:
file = file[:file.index('?')] continue
except ValueError:
pass if enabled_by_default:
file = file[1:]
if file.endswith('.js'): if file.endswith('.js'):
buf.write(get_js_link(file)) buf.write(get_js_link(file, version))
else: else:
buf.write(get_css_link(file)) buf.write(get_css_link(file, version))
return buf.getvalue() return buf.getvalue()
@ -252,6 +253,13 @@ async def index0(req: web.Request):
@routes.get('/main.cgi') @routes.get('/main.cgi')
async def index(req: web.Request): async def index(req: web.Request):
tabs = ['zones', 'list']
tab = req.query.get('tab', None)
if tab and (tab not in tabs or tab == tabs[0]):
raise web.HTTPFound('/main.cgi')
if tab is None:
tab = tabs[0]
ctx = {} ctx = {}
for k in 'inverter', 'sensors': for k in 'inverter', 'sensors':
ctx[f'{k}_grafana_url'] = config.app_config[f'{k}_grafana_url'] ctx[f'{k}_grafana_url'] = config.app_config[f'{k}_grafana_url']
@ -261,6 +269,8 @@ async def index(req: web.Request):
ctx['allcams'] = cc.get_all_cam_names() ctx['allcams'] = cc.get_all_cam_names()
ctx['lang_enum'] = Language ctx['lang_enum'] = Language
ctx['lang_selected'] = lang_context_var.get() ctx['lang_selected'] = lang_context_var.get()
ctx['tab_selected'] = tab
ctx['tabs'] = tabs
return await render(req, 'index', return await render(req, 'index',
title=lang('sitename'), title=lang('sitename'),
@ -419,7 +429,7 @@ async def cams(req: web.Request):
if not cc.has_camera(int(cam)): if not cc.has_camera(int(cam)):
raise ValueError('invalid camera id') raise ValueError('invalid camera id')
cams = [int(cam)] cams = [int(cam)]
mode = {'type': 'single', 'cam': cam} mode = {'type': 'single', 'cam': int(cam)}
elif zone is not None: elif zone is not None:
if not cc.has_zone(zone): if not cc.has_zone(zone):
@ -428,7 +438,8 @@ async def cams(req: web.Request):
mode = {'type': 'zone', 'zone': zone} mode = {'type': 'zone', 'zone': zone}
else: else:
raise web.HTTPBadRequest(text='no camera id or zone found') cams = cc.get_all_cam_names()
mode = {'type': 'all'}
js_config = { js_config = {
'host': config.app_config['cam_hls_host'], 'host': config.app_config['cam_hls_host'],

View File

@ -4,7 +4,7 @@ import html
from enum import Enum from enum import Enum
from aiohttp import web from aiohttp import web
from aiohttp.web import HTTPFound from aiohttp.web import HTTPFound, HTTPMovedPermanently, HTTPException
from aiohttp.web_exceptions import HTTPNotFound from aiohttp.web_exceptions import HTTPNotFound
from ..util import stringify, format_tb, Addr from ..util import stringify, format_tb, Addr
from ..config import is_development_mode from ..config import is_development_mode
@ -55,9 +55,17 @@ async def errors_handler_middleware(request, handler):
code=404 code=404
) )
except HTTPFound as exc: except (HTTPFound, HTTPMovedPermanently) as exc:
raise exc raise exc
except HTTPException as exc:
_logger.exception(exc)
return _render_error(
error_type=exc.reason,
error_message=exc.text,
traceback=format_tb(exc)
)
except Exception as exc: except Exception as exc:
_logger.exception(exc) _logger.exception(exc)
return _render_error( return _render_error(

View File

@ -209,4 +209,14 @@ a.camzone {
right: 8px; right: 8px;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
}
.cams_list_group {}
.cams_list_group .list-group-item:first-child {
border-top-width: 0;
border-radius: 0;
}
.cams_list_group .list-group-item .icon-right {
position: relative;
top: -1px;
} }

View File

@ -103,22 +103,22 @@ function removeClass(el, name) {
function indexInit() { function indexInit() {
// language selector // language selector
var langSelect = document.getElementById('lang'); const langSelect = document.getElementById('lang');
langSelect.addEventListener('change', function() { langSelect.addEventListener('change', function() {
var selectedLang = this.value; const selectedLang = this.value;
document.cookie = "lang=" + selectedLang + ";path=/"; document.cookie = "lang=" + selectedLang + ";path=/";
window.location.reload(); window.location.reload();
}); });
// camera blocks // camera blocks
var blocks = ['zones', 'list']; let blocks = ['zones', 'list'];
for (var i = 0; i < blocks.length; i++) { for (let i = 0; i < blocks.length; i++) {
var button = ge('cam_'+blocks[i]+'_btn'); const button = ge('cam_'+blocks[i]+'_btn');
button.addEventListener('click', function(e) { button.addEventListener('click', function(e) {
var selected = e.target.getAttribute('data-id'); const selected = e.target.getAttribute('data-id');
for (var j = 0; j < blocks.length; j++) { for (let j = 0; j < blocks.length; j++) {
var button = ge('cam_'+blocks[j]+'_btn'); const button = ge('cam_'+blocks[j]+'_btn');
var content = ge('cam_'+blocks[j]); const content = ge('cam_'+blocks[j]);
if (blocks[j] === selected) { if (blocks[j] === selected) {
addClass(button, 'active'); addClass(button, 'active');
content.style.display = ''; content.style.display = '';
@ -127,6 +127,13 @@ function indexInit() {
content.style.display = 'none'; content.style.display = 'none';
} }
} }
if (window.history !== undefined) {
let uri = '/main.cgi'
if (selected !== blocks[0])
uri += '?tab=' + encodeURIComponent(selected)
window.history.replaceState(null, '', uri)
}
}); });
} }
} }

View File

@ -1,13 +1,19 @@
{% extends "base.j2" %} {% extends "base.j2" %}
{% block content %} {% block content %}
{{ breadcrumbs([{'text': "cams"|lang}]) }}
{#<nav>#} {% if mode.type == 'all' %}
{# <div class="nav nav-tabs" id="nav-tab">#} {{ breadcrumbs([{'text': "cams"|lang}]) }}
{# <a href="/cams/{{ camera_param ? camera_param~"/" : "" }}" class="text-decoration-none"><button class="nav-link{% if tab == 'low' %} active{% endif %}" type="button">Low-res</button></a>#} {% elif mode.type == 'zone' %}
{# <a href="/cams/{{ camera_param ? camera_param~"/" : "" }}?high=1" class="text-decoration-none"><button class="nav-link{% if tab == 'high' %} active{% endif %}" type="button">High-res</button></a>#} {{ breadcrumbs([
{# </div>#} {'link': '/cams.cgi', 'text': "cams"|lang},
{#</nav>#} {'text': mode.zone|lang('ipcam_zones')}
]) }}
{% elif mode.type == 'single' %}
{{ breadcrumbs([
{'link': '/cams.cgi', 'text': "cams"|lang},
{'text': mode.cam|lang('ipcam')}
]) }}
{% endif %}
<div id="videos" class="camfeeds"></div> <div id="videos" class="camfeeds"></div>

View File

@ -37,24 +37,33 @@
<nav class="mt-4"> <nav class="mt-4">
<div class="nav nav-tabs" id="nav-tab"> <div class="nav nav-tabs" id="nav-tab">
<button class="nav-link active" type="button" id="cam_zones_btn" data-id="zones">{{ "cams_by_zone"|lang }}</button> {% for tab in tabs %}
<button class="nav-link" type="button" id="cam_list_btn" data-id="list">{{ "cams_list"|lang }}</button> <button class="nav-link{% if tab == tab_selected %} active{% endif %}" type="button" id="cam_{{ tab }}_btn" data-id="{{ tab }}">{{ ("cams_by_"~tab)|lang }}</button>
{% endfor %}
</div> </div>
</nav> </nav>
<div class="camzones" id="cam_zones"> <div class="camzones" id="cam_zones"{% if tab_selected != 'zones' %} style="display: none"{% endif %}>
{% for zone in camzones %} {% for zone in camzones %}
<a href="/cams.cgi?zone={{ zone }}" class="camzone"> <a href="/cams.cgi?zone={{ zone }}" class="camzone">
<div class="camzone_text">{{ zone|lang('ipcam_zones') }}</div> <div class="camzone_text">{{ zone|lang('ipcam_zones') }}</div>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
<ul class="list-group list-group-flush" id="cam_list" style="display: none">
<div class="list-group cams_list_group" id="cam_list"{% if tab_selected != 'list' %} style="display: none"{% endif %}>
{% for id in allcams %} {% for id in allcams %}
<li class="list-group-item"><a href="/cams.cgi?id={{ id }}">{{ id|lang('ipcam') }}</a></li> <a href="/cams.cgi?id={{ id }}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
{{ id|lang('ipcam') }}
<span class="icon-right">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671"/>
</svg>
</span> <!-- Bootstrap Icon -->
</a>
{% endfor %} {% endfor %}
{# <li class="list-group-item"><a href="/cams/stat/">Статистика</a></li>#} </div>
</ul>
</div> </div>
{% endblock %} {% endblock %}