diff options
-rwxr-xr-x | bin/web_kbn.py | 67 | ||||
-rw-r--r-- | include/py/homekit/http/http.py | 12 | ||||
-rw-r--r-- | web/kbn_assets/app.css | 10 | ||||
-rw-r--r-- | web/kbn_assets/app.js | 25 | ||||
-rw-r--r-- | web/kbn_templates/cams.j2 | 20 | ||||
-rw-r--r-- | web/kbn_templates/index.j2 | 23 |
6 files changed, 104 insertions, 53 deletions
diff --git a/bin/web_kbn.py b/bin/web_kbn.py index 96b6461..f891675 100755 --- a/bin/web_kbn.py +++ b/bin/web_kbn.py @@ -39,51 +39,52 @@ class WebKbnConfig(AppConfigUnit): } -common_static_files = [ - 'bootstrap.min.css', - 'bootstrap.bundle.min.js', - 'polyfills.js', - 'app.js', - 'app.css' -] -static_version = 4 +# files marked with + at the beginning are included by default +common_static_files = { + '+bootstrap.min.css': 1, + '+bootstrap.bundle.min.js': 1, + '+polyfills.js': 1, + '+app.js': 6, + '+app.css': 6, + 'hls.js': 1 +} routes = web.RouteTableDef() logger = logging.getLogger(__name__) 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(): 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>' -def get_css_link(file, version=static_version) -> str: +def get_css_link(file, version) -> str: if is_development_mode(): 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}">' -def get_head_static(files=None) -> str: +def get_head_static(additional_files=None) -> str: buf = StringIO() - if files is None: - files = [] - for file in common_static_files + files: - try: - q_ind = file.index('?') - v = file[q_ind+1:] - file = file[:file.index('?')] - except ValueError: - pass + if additional_files is None: + additional_files = [] + + for file, version in common_static_files.items(): + enabled_by_default = file.startswith('+') + if not enabled_by_default and file not in additional_files: + continue + + if enabled_by_default: + file = file[1:] if file.endswith('.js'): - buf.write(get_js_link(file)) + buf.write(get_js_link(file, version)) else: - buf.write(get_css_link(file)) + buf.write(get_css_link(file, version)) + return buf.getvalue() @@ -252,6 +253,13 @@ async def index0(req: web.Request): @routes.get('/main.cgi') 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 = {} for k in 'inverter', 'sensors': 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['lang_enum'] = Language ctx['lang_selected'] = lang_context_var.get() + ctx['tab_selected'] = tab + ctx['tabs'] = tabs return await render(req, 'index', title=lang('sitename'), @@ -419,7 +429,7 @@ async def cams(req: web.Request): if not cc.has_camera(int(cam)): raise ValueError('invalid camera id') cams = [int(cam)] - mode = {'type': 'single', 'cam': cam} + mode = {'type': 'single', 'cam': int(cam)} elif zone is not None: if not cc.has_zone(zone): @@ -428,7 +438,8 @@ async def cams(req: web.Request): mode = {'type': 'zone', 'zone': zone} else: - raise web.HTTPBadRequest(text='no camera id or zone found') + cams = cc.get_all_cam_names() + mode = {'type': 'all'} js_config = { 'host': config.app_config['cam_hls_host'], diff --git a/include/py/homekit/http/http.py b/include/py/homekit/http/http.py index b1d33d3..867fca7 100644 --- a/include/py/homekit/http/http.py +++ b/include/py/homekit/http/http.py @@ -4,7 +4,7 @@ import html from enum import Enum from aiohttp import web -from aiohttp.web import HTTPFound +from aiohttp.web import HTTPFound, HTTPMovedPermanently, HTTPException from aiohttp.web_exceptions import HTTPNotFound from ..util import stringify, format_tb, Addr from ..config import is_development_mode @@ -55,9 +55,17 @@ async def errors_handler_middleware(request, handler): code=404 ) - except HTTPFound as exc: + except (HTTPFound, HTTPMovedPermanently) as 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: _logger.exception(exc) return _render_error( diff --git a/web/kbn_assets/app.css b/web/kbn_assets/app.css index 8eec2f1..6b3bc11 100644 --- a/web/kbn_assets/app.css +++ b/web/kbn_assets/app.css @@ -209,4 +209,14 @@ a.camzone { right: 8px; text-overflow: ellipsis; 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; }
\ No newline at end of file diff --git a/web/kbn_assets/app.js b/web/kbn_assets/app.js index 0be801d..62e2575 100644 --- a/web/kbn_assets/app.js +++ b/web/kbn_assets/app.js @@ -103,22 +103,22 @@ function removeClass(el, name) { function indexInit() { // language selector - var langSelect = document.getElementById('lang'); + const langSelect = document.getElementById('lang'); langSelect.addEventListener('change', function() { - var selectedLang = this.value; + const selectedLang = this.value; document.cookie = "lang=" + selectedLang + ";path=/"; window.location.reload(); }); // camera blocks - var blocks = ['zones', 'list']; - for (var i = 0; i < blocks.length; i++) { - var button = ge('cam_'+blocks[i]+'_btn'); + let blocks = ['zones', 'list']; + for (let i = 0; i < blocks.length; i++) { + const button = ge('cam_'+blocks[i]+'_btn'); button.addEventListener('click', function(e) { - var selected = e.target.getAttribute('data-id'); - for (var j = 0; j < blocks.length; j++) { - var button = ge('cam_'+blocks[j]+'_btn'); - var content = ge('cam_'+blocks[j]); + const selected = e.target.getAttribute('data-id'); + for (let j = 0; j < blocks.length; j++) { + const button = ge('cam_'+blocks[j]+'_btn'); + const content = ge('cam_'+blocks[j]); if (blocks[j] === selected) { addClass(button, 'active'); content.style.display = ''; @@ -127,6 +127,13 @@ function indexInit() { 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) + } }); } } diff --git a/web/kbn_templates/cams.j2 b/web/kbn_templates/cams.j2 index 6459f90..361b86a 100644 --- a/web/kbn_templates/cams.j2 +++ b/web/kbn_templates/cams.j2 @@ -1,13 +1,19 @@ {% extends "base.j2" %} {% block content %} -{{ breadcrumbs([{'text': "cams"|lang}]) }} -{#<nav>#} -{# <div class="nav nav-tabs" id="nav-tab">#} -{# <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>#} -{# <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>#} -{# </div>#} -{#</nav>#} +{% if mode.type == 'all' %} + {{ breadcrumbs([{'text': "cams"|lang}]) }} +{% elif mode.type == 'zone' %} + {{ breadcrumbs([ + {'link': '/cams.cgi', 'text': "cams"|lang}, + {'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> diff --git a/web/kbn_templates/index.j2 b/web/kbn_templates/index.j2 index 7f69823..9d683e0 100644 --- a/web/kbn_templates/index.j2 +++ b/web/kbn_templates/index.j2 @@ -37,24 +37,33 @@ <nav class="mt-4"> <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> - <button class="nav-link" type="button" id="cam_list_btn" data-id="list">{{ "cams_list"|lang }}</button> + {% for tab in tabs %} + <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> </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 %} <a href="/cams.cgi?zone={{ zone }}" class="camzone"> <div class="camzone_text">{{ zone|lang('ipcam_zones') }}</div> </a> {% endfor %} </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 %} - <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 %} -{# <li class="list-group-item"><a href="/cams/stat/">Статистика</a></li>#} - </ul> + </div> + </div> {% endblock %} |