Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Table refresh with ntie #87

Merged
merged 35 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
cdce887
Init UpdateHandler on successful login
podliashanyk Mar 24, 2024
b62e237
Store ids of currently known events
podliashanyk Mar 24, 2024
fd66f41
Add method for communicating with UpdateHandler
podliashanyk Mar 24, 2024
e8001e5
Create new component for table poll (refresh) via UpdateHandler
podliashanyk Mar 24, 2024
b9aa649
Add a separate function for table refresh logic
podliashanyk May 23, 2024
e26220b
Switch to polling table with UpdateHandler
podliashanyk May 23, 2024
b6efd1c
Rename poll to refresh in config
podliashanyk May 23, 2024
b397fa7
Handle RetryError when fetching events and event details
podliashanyk May 23, 2024
3fd1dd9
Handle RetryError on ntie events update
podliashanyk May 23, 2024
9b86829
Always include expanded and selected state for table event dict
podliashanyk May 23, 2024
174feb0
Switch to using extended table event dict in relevant endpoints
podliashanyk May 23, 2024
ddfd3d7
Switch to extended create_table_event instead of create_poll_event
podliashanyk May 23, 2024
904ce38
Remove redundant create_polled_event helper
podliashanyk May 23, 2024
16f513b
Differentiate between extended and simple event format on get eventlist
podliashanyk May 23, 2024
cb58703
Remove poll_current_events helper
podliashanyk May 23, 2024
f649e3b
Always consider event row state when creating table event
podliashanyk May 23, 2024
072462c
Always consider event row state when rendering row
podliashanyk May 23, 2024
f3230fc
Drop using poll events components on bulk update
podliashanyk May 23, 2024
d0e6be1
Remove poll event components
podliashanyk May 23, 2024
9f4ea40
Drop all poll terminology in ntie table refresh logic
podliashanyk May 23, 2024
b652a24
Polish names in refresh helper and endpoint
podliashanyk May 23, 2024
e060c77
Reuse existing event rows component for events added on refresh
podliashanyk May 24, 2024
1c8ee7e
Move removed event row to a separate component
podliashanyk May 24, 2024
2c2725a
Move modified event row to a separate component
podliashanyk May 24, 2024
fe38ddf
Remove redundant block definition in table refresh response
podliashanyk May 24, 2024
06778fb
Connect to UpdateHandler
podliashanyk May 24, 2024
9111cbd
Synchronize requests on body using HTMX's drop strategy
podliashanyk May 24, 2024
1ddfc0b
Change sync strategy to queueing requests
podliashanyk May 28, 2024
8f98df0
Remove redundant list length checks in templates
podliashanyk May 28, 2024
059329e
Update src/howitz/endpoints.py
podliashanyk May 28, 2024
dd22052
Decrease default refresh interval and update example config
podliashanyk May 28, 2024
d4b69b2
Bump zinolib version requirement
podliashanyk May 29, 2024
0bbb74e
Consider autoremove config option when initializing UpdateHandler
podliashanyk May 31, 2024
70f726c
Merge branch 'main' into table-refresh-with-ntie
podliashanyk Jun 3, 2024
b0dbecd
Update frozen requirement list
podliashanyk Jun 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ All config options can be overruled by environment variables. Prefix with
options. It is also possible to override logging by setting "HOWITZ_LOGGING" to
a string of a python dict but we do not recommend it, use a config file instead.

Poll interval for events table can be changed by adding for example ``poll_interval = 30`` to
the ``[howitz]``-section or setting the environment variable ``HOWITZ_POLL_INTERVAL`` to a new value.
Poll interval values represented seconds and must be integers. The default value is ``60`` seconds.
Refresh interval for events table can be changed by adding for example ``refresh_interval = 10`` to
the ``[howitz]``-section or setting the environment variable ``HOWITZ_REFRESH_INTERVAL`` to a new value.
Refresh interval values represented seconds and must be integers. The default value is ``5`` seconds.

Debugging can be turned on either by adding ``DEBUG = true`` to the
``[flask]``-section or setting the environment variable ``HOWITZ_DEBUG`` to ``1``.
Expand Down
2 changes: 1 addition & 1 deletion dev-howitz.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ DEBUG = true
[howitz]
storage = "./howitz.sqlite3"
devmode = true
poll_interval = 30
refresh_interval = 10
timezone='LOCAL'

[zino.connections.default]
Expand Down
2 changes: 1 addition & 1 deletion requirements-frozen.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ werkzeug==2.3.8
# flask
# flask-login
# howitz (pyproject.toml)
zinolib==1.0.1
zinolib==1.0.4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something is weird with this. pyproject.toml says zinolib>=1.1.0, so why is zinolib 1.0.4 in the frozen requirements list?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we forgot to update it probably.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With zinolib==1.0.4 I can't even get Howitz to run, with 1.1.0 this NTIE PR seems to work just fine for me :)

# via howitz (pyproject.toml)
1 change: 0 additions & 1 deletion src/howitz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from howitz.users.commands import user_cli
from zinolib.controllers.zino1 import Zino1EventManager


__all__ = ["create_app"]


Expand Down
4 changes: 2 additions & 2 deletions src/howitz/config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ class DevStorageConfig(StorageConfig):

class HowitzConfig(ServerConfig, StorageConfig):
devmode: bool = Literal[False]
poll_interval: int = 60
refresh_interval: int = 5
timezone: str = DEFAULT_TIMEZONE


class DevHowitzConfig(DevServerConfig, DevStorageConfig):
devmode: bool = Literal[True]
poll_interval: int = 30
refresh_interval: int = 5
timezone: str = DEFAULT_TIMEZONE
149 changes: 96 additions & 53 deletions src/howitz/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from datetime import datetime, timezone

from werkzeug.exceptions import BadRequest
from zinolib.controllers.zino1 import Zino1EventManager, RetryError, EventClosedError
from zinolib.controllers.zino1 import Zino1EventManager, RetryError, EventClosedError, UpdateHandler
from zinolib.event_types import Event, AdmState, PortState, BFDState, ReachabilityState, LogEntry, HistoryEntry
from zinolib.compat import StrEnum
from zinolib.ritz import NotConnectedError, AuthenticationError
Expand Down Expand Up @@ -55,6 +55,10 @@ def auth_handler(username, password):
current_app.logger.info('Authenticated in Zino %s', current_app.event_manager.is_authenticated)

if current_app.event_manager.is_authenticated: # is zino authenticated
current_app.updater = UpdateHandler(current_app.event_manager, autoremove=current_app.zino_config.autoremove)
current_app.updater.connect()
current_app.logger.debug('UpdateHandler %s', current_app.updater)

current_app.logger.debug('User is Zino authenticated %s', current_app.event_manager.is_authenticated)
current_app.logger.debug('HOWITZ CONFIG %s', current_app.howitz_config)
login_user(user, remember=True)
Expand All @@ -63,6 +67,7 @@ def auth_handler(username, password):
session["expanded_events"] = {}
session["errors"] = {}
session["not_connected_counter"] = 0
session["event_ids"] = []
return user

raise AuthenticationError('Unexpected error on Zino authentication')
Expand All @@ -79,12 +84,20 @@ def logout_handler():
session.pop('selected_events', [])
session.pop('errors', {})
session.pop('not_connected_counter', 0)
session.pop('event_ids', [])
current_app.logger.info("Logged out successfully.")


def get_current_events():
try:
current_app.event_manager.get_events()
except RetryError as retryErr: # Intermittent error in Zino
current_app.logger.exception('RetryError when fetching current events %s', retryErr)
try:
current_app.event_manager.get_events()
except RetryError as retryErr: # Intermittent error in Zino
current_app.logger.exception('RetryError when fetching current events after retry, %s', retryErr)
raise
except NotConnectedError as notConnErr:
if session["not_connected_counter"] > 1: # This error is not intermittent - increase counter and handle
current_app.logger.exception('Recurrent NotConnectedError %s', notConnErr)
Expand All @@ -104,47 +117,71 @@ def get_current_events():
events[k].updated,
), reverse=True)}

# Save current events' IDs
session["event_ids"] = list(events_sorted.keys())
session.modified = True

table_events = []
for c in events_sorted.values():
table_events.append(create_table_event(c))
table_events.append(create_table_event(c, expanded=str(c.id) in session["expanded_events"],
selected=str(c.id) in session["selected_events"]))

current_app.logger.debug('TABLE EVENTS %s', table_events[0])

return table_events


def poll_current_events():
try:
current_app.event_manager.get_events()
except NotConnectedError as notConnErr:
if session["not_connected_counter"] > 1: # This error is not intermittent - increase counter and handle
current_app.logger.exception('Recurrent NotConnectedError %s', notConnErr)
session["not_connected_counter"] += 1
raise
else: # This error is intermittent - increase counter and retry
current_app.logger.exception('Intermittent NotConnectedError %s', notConnErr)
session["not_connected_counter"] += 1
current_app.event_manager.get_events()
pass

events = current_app.event_manager.events
def update_events():
updated_ids = set()

events_sorted = {k: events[k] for k in sorted(events,
key=lambda k: (
0 if events[k].adm_state == AdmState.IGNORED else 1,
events[k].updated,
), reverse=True)}
while True:
try:
updated = current_app.updater.get_event_update()
except RetryError as retryErr: # Intermittent error in Zino
current_app.logger.exception('RetryError when NTIE refreshing current events %s', retryErr)
try:
updated = current_app.updater.get_event_update()
except RetryError as retryErr: # Intermittent error in Zino
current_app.logger.exception('RetryError when NTIE refreshing current events after retry, %s', retryErr)
raise
if not updated:
break
updated_ids.add(updated)

return updated_ids


def refresh_current_events():
event_ids = update_events()
current_app.logger.debug('UPDATED EVENT IDS %s', event_ids)

removed_events = []
modified_events = []
added_events = []
removed = current_app.event_manager.removed_ids
existing = session["event_ids"]
for i in event_ids:
if i in removed:
removed_events.append(i)
existing.remove(i)
elif i not in existing:
c = current_app.event_manager.create_event_from_id(int(i))
added_events.append(create_table_event(c, expanded=False, selected=False))
existing.insert(0, int(i))
else:
c = current_app.event_manager.create_event_from_id(int(i))
modified_events.append(create_table_event(c,
expanded=str(c.id) in session["expanded_events"],
selected=str(c.id) in session["selected_events"]))

poll_events = []
for c in events_sorted.values():
poll_events.append(create_polled_event(create_table_event(c), expanded=str(c.id) in session["expanded_events"],
selected=str(c.id) in session["selected_events"]))
session["event_ids"] = existing
session.modified = True

return poll_events
return removed_events, modified_events, added_events


# todo remove all use of helpers from curitz
def create_table_event(event):
def create_table_event(event, expanded=False, selected=False):
common = {}

try:
Expand All @@ -164,23 +201,18 @@ def create_table_event(event):
raise

common.update(vars(event))

return common


def create_polled_event(table_event, expanded=False, selected=False):
poll_event = {
"event": table_event
table_event = {
"event": common
}
if expanded:
poll_event["event_attr"], poll_event["event_logs"], poll_event["event_history"], poll_event["event_msgs"] = (
get_event_details(int(table_event["id"])))
poll_event["expanded"] = expanded
table_event["event_attr"], table_event["event_logs"], table_event["event_history"], table_event["event_msgs"] = (
get_event_details(int(event.id)))
table_event["expanded"] = expanded

if selected:
poll_event["selected"] = selected
table_event["selected"] = selected

return poll_event
return table_event


# fixme implementation copied from curitz
Expand Down Expand Up @@ -208,7 +240,11 @@ def get_event_attributes(id, res_format=dict):
event = current_app.event_manager.create_event_from_id(int(id))
except RetryError as retryErr: # Intermittent error in Zino
current_app.logger.exception('RetryError when fetching event attributes %s', retryErr)
raise
try:
event = current_app.event_manager.create_event_from_id(int(id))
except RetryError as retryErr: # Intermittent error in Zino
current_app.logger.exception('RetryError when fetching event attributes after retry, %s', retryErr)
raise

event_dict = vars(event)
attr_list = [f"{k}:{v}" for k, v in event_dict.items()]
Expand Down Expand Up @@ -250,7 +286,12 @@ def get_event_details(id):
format_dt_event_attrs(event_attr)
except RetryError as retryErr: # Intermittent error in Zino
current_app.logger.exception('RetryError when fetching event details %s', retryErr)
raise
try:
event_attr = vars(current_app.event_manager.create_event_from_id(int(id)))
format_dt_event_attrs(event_attr)
except RetryError as retryErr: # Intermittent error in Zino
current_app.logger.exception('RetryError when fetching event details after retry, %s', retryErr)
raise

event_logs = current_app.event_manager.get_log_for_id(int(id))
event_history = current_app.event_manager.get_history_for_id(int(id))
Expand All @@ -275,7 +316,8 @@ def footer():
elif not tz == DEFAULT_TIMEZONE: # Fall back to default if invalid value is provided
tz = f"{DEFAULT_TIMEZONE} (default)"

return render_template('/components/footer/footer-info.html', poll_interval=current_app.howitz_config["poll_interval"],
return render_template('/components/footer/footer-info.html',
refresh_interval=current_app.howitz_config["refresh_interval"],
timezone=tz)


Expand Down Expand Up @@ -328,7 +370,7 @@ def auth():
@main.route('/events-table.html')
def events_table():
return render_template('/components/table/events-table.html',
poll_interval=current_app.howitz_config["poll_interval"])
refresh_interval=current_app.howitz_config["refresh_interval"])


@main.route('/get_events')
Expand All @@ -338,11 +380,12 @@ def get_events():
return render_template('/components/table/event-rows.html', event_list=table_events)


@main.route('/poll_events')
def poll_events():
poll_events_list = poll_current_events()
@main.route('/refresh_events')
def refresh_events():
removed_events, modified_events, added_events = refresh_current_events()

return render_template('/components/poll/poll-rows.html', poll_event_list=poll_events_list)
return render_template('/responses/updated-rows.html', modified_event_list=modified_events,
removed_event_list=removed_events, added_event_list=added_events)


@main.route('/events/<event_id>/expand_row', methods=["GET"])
Expand All @@ -367,7 +410,7 @@ def expand_event_row(event_id):
except RetryError as retryErr: # Intermittent error in Zino
current_app.logger.exception('RetryError on row expand after retry, %s', retryErr)
raise
event = create_table_event(eventobj)
event = create_table_event(eventobj)["event"]

return render_template('/components/row/expanded-row.html', event=event, id=event_id, event_attr=event_attr,
event_logs=event_logs,
Expand Down Expand Up @@ -396,7 +439,7 @@ def collapse_event_row(event_id):
except RetryError as retryErr: # Intermittent error in Zino
current_app.logger.exception('RetryError on row collapse %s', retryErr)
raise
event = create_table_event(eventobj)
event = create_table_event(eventobj)["event"]

return render_template('/responses/collapse-row.html', event=event, id=event_id,
is_selected=str(event_id) in selected_events)
Expand Down Expand Up @@ -425,7 +468,7 @@ def update_event_status(event_id):
add_history_res = current_app.event_manager.add_history_entry_for_id(event_id, new_history)

event_attr, event_logs, event_history, event_msgs = get_event_details(event_id)
event = create_table_event(current_app.event_manager.create_event_from_id(event_id))
event = create_table_event(current_app.event_manager.create_event_from_id(event_id))["event"]

return render_template('/responses/update-event-response.html', event=event, id=event_id, event_attr=event_attr,
event_logs=event_logs,
Expand Down Expand Up @@ -466,8 +509,8 @@ def bulk_update_events_status():
current_app.logger.debug("SELECTED EVENTS %s", session["selected_events"])

# Rerender whole events table
poll_events_list = poll_current_events() # Calling poll events method is needed to preserve info about which events are expanded
return render_template('/responses/bulk-update-events-status.html', poll_event_list=poll_events_list)
event_list = get_current_events()
return render_template('/responses/bulk-update-events-status.html', event_list=event_list)


@main.route('/show_update_status_modal', methods=['GET'])
Expand Down
36 changes: 14 additions & 22 deletions src/howitz/templates/components/accordion/event-messages.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,24 @@
<ul
id="ol-event-{{ id }}"
class="relative border-l border-gray-200 dark:border-gray-700">
{% for msg in event_msgs %}
<li class="mb-3 ml-4">

{% if event_msgs|length>0 %}
<div class="absolute w-3 h-3 rounded-full mt-1.5 -left-1.5 border border-white dark:border-gray-900 {% if msg.user %} bg-orange-950 dark:bg-orange-400 {% else %} bg-blue-950 dark:bg-blue-400 {% endif %}"></div>
<h3 class="mb-1 text-xs font-normal leading-none text-gray-400">
{{ msg.user }}
</h3>
<div
class="flex gap-0 flex-wrap"
>
<time class="mr-2 text-sm font-semibold text-white shrink-0">{{ msg.date }}</time>
<p class="mb-2 text-sm font-normal text-gray-400 break-words">{{ msg.log }}</p>
</div>

{% for msg in event_msgs %}
</li>


<li class="mb-3 ml-4">

<div class="absolute w-3 h-3 rounded-full mt-1.5 -left-1.5 border border-white dark:border-gray-900 {% if msg.user %} bg-orange-950 dark:bg-orange-400 {% else %} bg-blue-950 dark:bg-blue-400 {% endif %}"></div>
<h3 class="mb-1 text-xs font-normal leading-none text-gray-400">
{{ msg.user }}
</h3>
<div
class="flex gap-0 flex-wrap"
>
<time class="mr-2 text-sm font-semibold text-white shrink-0">{{ msg.date }}</time>
<p class="mb-2 text-sm font-normal text-gray-400 break-words">{{ msg.log }}</p>
</div>

</li>


{% endfor %}

{% endif %}

{% endfor %}
</ul>

</div>
2 changes: 1 addition & 1 deletion src/howitz/templates/components/footer/footer-info.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p class="p-2 text-white text-semibold inline-block">
Updating every {{ poll_interval }}s.
Updating every {{ refresh_interval }}s.
</p>
<p class="p-2 text-white text-semibold inline-block">
Configured timezone is {{ timezone }}.
Expand Down
Loading
Loading