Update scopes tree dynamically

This patch adds a javascript function to update the scope tree (on/off)
state.

This javacript function is called every second, does a call to the
new backend endpoint `/scopes/status` and updates the tree classes
depending on the current data.

The new `/scopes/status` endpoint just returns the scopes tree as json.

This patch also adds an icon in the tree leafs, a filled green circle
when the state is `on`, and a empty red circle when the state is `off`.

There's also a new javascript function to unfold all collapses in the
scope tree.
multi-ogserver
Daniel García Moreno 2021-07-07 10:27:59 +02:00 committed by OpenGnSys Support Team
parent af938c3280
commit 4e519590af
3 changed files with 79 additions and 3 deletions

View File

@ -0,0 +1,54 @@
const Endpoint = '/scopes/status';
const Interval = 1000;
let updateTimeoutId = null;
function updateScopeState() {
if (updateTimeoutId) {
clearTimeout(updateTimeoutId);
}
updateTimeoutId = setTimeout(() => {
updateTimeoutId = null;
fetch(Endpoint)
.then(response => response.json())
.then((data) => {
updateScopes(data.scope);
})
.catch((error) => { console.error(error); })
.finally(() => {
updateScopeState();
});
}, Interval);
}
function updateScopes(scopes) {
scopes.forEach((scope) => {
if (scope.state) {
const scopeId = `${scope.name}_${scope.id}`;
const scopeEl = document.querySelector(`#${scopeId}`);
const stateCls = ['state--on', 'state--off'];
scopeEl.classList.remove(...stateCls);
const stateClass = `state--${scope.state}`;
scopeEl.classList.add(stateClass);
const iconEl = document.querySelector(`#${scopeId} .nav-icon`);
const iconCls = ['fas', 'far', 'text-danger', 'text-success'];
iconEl.classList.remove(...iconCls);
let newIconCls = [];
if (scope.state === 'on') {
newIconCls.push('fas', 'text-success');
} else {
newIconCls.push('far', 'text-danger');
}
iconEl.classList.add(...newIconCls);
}
if (scope.scope) {
// This is a level so we should update all childs
updateScopes(scope.scope);
}
});
}
function unfoldAll() {
$('#scopes .collapse').collapse('show');
}

View File

@ -20,15 +20,28 @@
{% macro scopes_tree_collapse(scopes) -%}
<ul class="nav flex-column nav-pills">
<ul id="scopes" class="nav flex-column nav-pills">
{{ scopes_tree_collapse_level(scopes["scope"], 1) }}
</ul>
<script>
// Launch the javascript on document ready, so all the global functions exists
// in the scope
document.addEventListener('readystatechange', () => {
if (document.readyState === 'complete') {
updateScopeState();
unfoldAll();
}
});
</script>
{% endmacro %}
{% macro scopes_tree_collapse_level(scopes, i) -%}
{% for scope in scopes %}
<li class="nav-item {% if scope["state"] %}state--{{scope["state"] | lower}}{% endif %}">
<li
id="{{ scope["name"] }}_{{ scope["id"] }}"
class="nav-item {% if scope["state"] %}state--{{scope["state"] | lower}}{% endif %}"
>
{% if " ".join(scope["ip"]) %}
<input class="form-check-input" type="checkbox" form="scopesForm"
value="{{ " ".join(scope["ip"]) }}"
@ -37,6 +50,9 @@
{% endif %}
<a class="nav-link {% if not scope["scope"] %}disabled{% endif %}" href="#level{{i}}-{{loop.index}}"
{% if scope["scope"] %}data-toggle="collapse"{% endif %}>
{% if not scope["scope"] %}
<i class="nav-icon fa-circle {% if scope['state'] == 'on' %}fas text-success{% else %}far text-danger{% endif %}"></i>
{% endif %}
{{ scope["name"] }}
</a>
{% if scope["scope"] %}

View File

@ -113,7 +113,7 @@ def add_state_and_ips(scope, clients, ips):
if client:
scope['state'] = client['state']
else:
scope['state'] = 'OFF'
scope['state'] = 'off'
scope['ip'] = [scope['ip']]
scope['selected'] = set(scope['ip']).issubset(ips)
else:
@ -191,6 +191,12 @@ def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/scopes/status')
@login_required
def scopes_status():
scopes, _clients = get_scopes()
return jsonify(scopes)
@app.route('/scopes/')
@login_required
def scopes():