فهرست منبع

Merge pull request #79 from zauberzeug/traffic

Log and show traffic
Falko Schindler 2 سال پیش
والد
کامیت
c8f16cf4f2
3فایلهای تغییر یافته به همراه90 افزوده شده و 2 حذف شده
  1. 1 0
      .gitignore
  2. 8 2
      main.py
  3. 81 0
      traffic_tracking.py

+ 1 - 0
.gitignore

@@ -3,3 +3,4 @@ __pycache__/
 .*.swp
 dist
 /test.py
+*.pickle

+ 8 - 2
main.py

@@ -8,6 +8,7 @@ from typing import Callable, Union
 import docutils.core
 
 from nicegui import ui
+from traffic_tracking import TrafficChard as traffic_chart
 
 # add docutils css to webpage
 ui.add_head_html(docutils.core.publish_parts('', writer_name='html')['stylesheet'])
@@ -86,8 +87,10 @@ with ui.row().classes('flex w-full'):
         content = re.sub(r'(?m)^\<img.*\n?', '', content)
         ui.markdown(content).classes('w-6/12')
 
-    with ui.card().classes('mx-auto mt-24'):
-        with ui.row():
+    with ui.column().classes('w-5/12 flex-center'):
+        width = 450
+
+        with ui.card(), ui.row().style(f'width:{width}px'):
             with ui.column():
                 ui.button('Click me!', on_click=lambda: output.set_text('Click'))
                 ui.checkbox('Check me!', on_change=lambda e: output.set_text('Checked' if e.value else 'Unchecked'))
@@ -106,6 +109,9 @@ with ui.row().classes('flex w-full'):
                 ui.label('Output:')
                 output = ui.label('').classes('text-bold')
 
+        with ui.row().style('margin-top: 40px'):
+            traffic_chart().style(f'width:{width}px;height:250px')
+
 ui.markdown('## API Documentation and Examples')
 
 

+ 81 - 0
traffic_tracking.py

@@ -0,0 +1,81 @@
+import logging
+import os
+import pickle
+import threading
+import time
+from typing import Dict, Set
+
+from starlette.requests import Request
+
+from nicegui import ui
+
+VISITS_FILE = 'traffic_data/visits.pickle'
+SESSIONS_FILE = 'traffic_data/sessions.pickle'
+
+
+class TrafficChard(ui.chart):
+
+    def __init__(self) -> None:
+        self.visits: Dict[int, int] = {}
+        self.sessions: Dict[int, Set[str]] = {}
+        self.load()
+
+        ui.on_connect(self.on_connect)
+        ui.timer(10, self.save)
+        ui.timer(10, self.update_visibility)
+
+        super().__init__({
+            'title': {'text': 'Page Visits'},
+            'navigation': {'buttonOptions': {'enabled': False}},
+            'chart': {'type': 'line'},
+            'yAxis': {'title': False},
+            'xAxis': {
+                'type': 'datetime',
+                'labels': {'format': '{value:%b %e}', },
+            },
+            'series': [
+                {'name': 'Views', 'data': []},
+                {'name': 'Sessions', 'data': []},
+            ],
+        })
+
+    def on_connect(self, request: Request) -> None:
+        def seconds_to_day(seconds: float) -> int: return int(seconds / 60 / 60 / 24)
+        def day_to_milliseconds(day: int) -> float: return day * 24 * 60 * 60 * 1000
+        today = seconds_to_day(time.time())
+        self.visits[today] = self.visits.get(today, 0) + 1
+        self.options.series[0].data[:] = [[day_to_milliseconds(day), count] for day, count in self.visits.items()]
+        if today not in self.sessions:
+            self.sessions[today] = set()
+        self.sessions[today].add(request.session_id)
+        self.options.series[1].data[:] = [[day_to_milliseconds(day), len(s)] for day, s in self.sessions.items()]
+        self.update()
+
+    def load(self) -> None:
+        os.makedirs(os.path.dirname(VISITS_FILE), exist_ok=True)
+        os.makedirs(os.path.dirname(SESSIONS_FILE), exist_ok=True)
+        try:
+            with open(VISITS_FILE, 'rb') as f:
+                self.visits = pickle.load(f)
+            with open(SESSIONS_FILE, 'rb') as f:
+                self.sessions = pickle.load(f)
+        except FileNotFoundError:
+            pass
+        except:
+            logging.exception('Error loading traffic data')
+
+    def save(self) -> None:
+        def _save() -> None:
+            try:
+                with open(VISITS_FILE, 'wb') as f:
+                    pickle.dump(self.visits, f)
+                with open(SESSIONS_FILE, 'wb') as f:
+                    pickle.dump(self.sessions, f)
+            except:
+                logging.exception('Error saving traffic data')
+
+        t = threading.Thread(target=_save, name='Save Traffic Data')
+        t.start()
+
+    def update_visibility(self) -> None:
+        self.visible = True  # len(self.visits.keys()) >= 3 and len(self.sessions.keys()) >= 3