Skip to content

Commit 92ffe98

Browse files
committed
Implement Web UI for Moodle Mate with authentication and configuration management
This commit introduces a new Web UI for Moodle Mate, enhancing user interaction and configuration management. Key changes include: - Added a `WebUI` class in `src/web/api.py` to handle web server functionality using FastAPI. - Implemented authentication via a simple cookie mechanism, allowing secure access to the dashboard. - Created HTML templates for the login page and main dashboard, providing a user-friendly interface. - Updated the configuration model to include web settings, enabling dynamic configuration through the UI. - Introduced a notification history feature to track past notifications within the UI. These updates aim to improve user experience and streamline configuration management for Moodle Mate.
1 parent 0ef4b33 commit 92ffe98

File tree

18 files changed

+1454
-19
lines changed

18 files changed

+1454
-19
lines changed

README.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ Moodle Mate comes with an optional AI-powered summarization feature that will su
2929
- Optional AI-based content summarization that creates quick TLDRs.
3030
- Converts HTML notifications to Markdown for improved readability using [turndown-python](https://github.com/EvickaStudio/turndown-python).
3131

32+
- **Web Dashboard**
33+
- Monitor system status, logs, and configuration.
34+
- Live configuration editing.
35+
- Test notifications.
36+
- Protected by optional authentication.
37+
3238
- **Multi-Platform Support**
3339
- Discord (via webhooks)
3440
- Pushbullet (send to all your devices)
@@ -84,12 +90,14 @@ Moodle Mate comes with an optional AI-powered summarization feature that will su
8490
Edit `.env` with your settings. Configuration uses environment variables with the `MOODLEMATE_` prefix.
8591

8692
Example `.env`:
87-
8893
```env
8994
MOODLEMATE_MOODLE__URL=https://moodle.example.com
9095
MOODLEMATE_MOODLE__USERNAME=your_username
9196
MOODLEMATE_MOODLE__PASSWORD=your_password
9297
98+
# Web UI Protection
99+
MOODLEMATE_WEB__AUTH_SECRET=your_secure_password
100+
93101
MOODLEMATE_DISCORD__ENABLED=true
94102
MOODLEMATE_DISCORD__WEBHOOK_URL=https://discord.com/api/webhooks/...
95103
```
@@ -155,14 +163,31 @@ docker compose down
155163
When running, the application will:
156164

157165
1. Validate your configuration.
158-
2. Connect to your Moodle instance.
159-
3. Continuously monitor for new notifications.
160-
4. Process and deliver notifications according to your settings.
166+
2. Start the Web Dashboard (default: http://localhost:9095).
167+
3. Connect to your Moodle instance.
168+
4. Continuously monitor for new notifications.
169+
5. Process and deliver notifications according to your settings.
170+
171+
## Web Dashboard
172+
173+
Moodle Mate includes a built-in web dashboard for monitoring and configuration.
174+
175+
- **URL**: `http://0.0.0.0:9095` (default)
176+
- **Features**:
177+
- View current status and metrics.
178+
- Trigger test notifications.
179+
- Edit configuration live (requires `MOODLEMATE_WEB__AUTH_SECRET` if set).
180+
181+
**Security**: To protect the dashboard, set `MOODLEMATE_WEB__AUTH_SECRET` in your `.env` file. If this variable is set, you will be prompted to enter this password to access the dashboard.
161182

162183
## Screenshots
163184

164185
*Colors between the screenshots are different because of different themes in Termius.*
165186

187+
### v2.0.3 (Web UI)
188+
189+
![v2.0.3](assets/webui_v2.0.3.png)
190+
166191
### v2.0.2 (Docker)
167192

168193
![v2.0.2](assets/running_v2.0.2.png)

assets/webui_v2.0.3.png

86.6 KB
Loading

cleaner.sh

100644100755
File mode changed.

example.env

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ MOODLEMATE_MOODLE__USERNAME=your_username
33
MOODLEMATE_MOODLE__PASSWORD=your_password
44
MOODLEMATE_MOODLE__INITIAL_FETCH_COUNT=1
55

6+
# Web UI Configuration
7+
MOODLEMATE_WEB__ENABLED=false
8+
MOODLEMATE_WEB__HOST=0.0.0.0
9+
MOODLEMATE_WEB__PORT=9095
10+
MOODLEMATE_WEB__AUTH_SECRET=your_secure_password
11+
612
# AI Configuration (Optional)
713
MOODLEMATE_AI__ENABLED=true
814
MOODLEMATE_AI__API_KEY=sk-...
915
MOODLEMATE_AI__MODEL=gpt-4o-mini
1016
MOODLEMATE_AI__TEMPERATURE=0.7
1117
MOODLEMATE_AI__MAX_TOKENS=150
12-
# MOODLEMATE_AI__ENDPOINT=https://api.openai.com/v1/chat/completions
18+
MOODLEMATE_AI__SYSTEM_PROMPT=Summarize the message concisely with appropriate emojis, excluding links.
19+
MOODLEMATE_AI__ENDPOINT=https://api.openai.com/v1/
20+
# for openrouter change to https://openrouter.ai/api/v1/
1321

1422
# Notification Settings
1523
MOODLEMATE_NOTIFICATION__MAX_RETRIES=5
@@ -28,4 +36,3 @@ MOODLEMATE_PUSHBULLET__API_KEY=
2836
# Webhook Site
2937
MOODLEMATE_WEBHOOK_SITE__ENABLED=false
3038
MOODLEMATE_WEBHOOK_SITE__WEBHOOK_URL=
31-

format.sh

100644100755
File mode changed.

main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def main() -> None:
6767
providers = initialize_providers(settings)
6868

6969
# Notification Processor
70-
notification_processor = NotificationProcessor(settings, providers, summarizer)
70+
notification_processor = NotificationProcessor(settings, providers, state_manager, summarizer)
7171

7272
# Moodle Handler
7373
moodle_handler = MoodleNotificationHandler(settings, moodle_api, state_manager)

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,8 @@ dependencies = [
1616
"pydantic>=2.10.0",
1717
"pydantic-settings>=2.0.0",
1818
"urllib3[secure]>=2.3.0",
19+
"fastapi>=0.109.0",
20+
"uvicorn>=0.27.0",
21+
"jinja2>=3.1.0",
22+
"python-multipart>=0.0.9",
1923
]

requirements.txt

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
# This file was autogenerated by uv via the following command:
22
# uv pip compile pyproject.toml -o requirements.txt
3+
annotated-doc==0.0.4
4+
# via fastapi
35
annotated-types==0.7.0
46
# via pydantic
57
anyio==4.10.0
68
# via
79
# httpx
810
# openai
11+
# starlette
12+
bleach==6.3.0
13+
# via moodle-mate (pyproject.toml)
914
certifi==2025.8.3
1015
# via
1116
# httpcore
@@ -15,16 +20,20 @@ cfgv==3.4.0
1520
# via pre-commit
1621
charset-normalizer==3.4.3
1722
# via requests
18-
configparser==7.2.0
19-
# via moodle-mate (pyproject.toml)
23+
click==8.3.1
24+
# via uvicorn
2025
distlib==0.4.0
2126
# via virtualenv
2227
distro==1.9.0
2328
# via openai
29+
fastapi==0.123.4
30+
# via moodle-mate (pyproject.toml)
2431
filelock==3.18.0
2532
# via virtualenv
2633
h11==0.16.0
27-
# via httpcore
34+
# via
35+
# httpcore
36+
# uvicorn
2837
httpcore==1.0.9
2938
# via httpx
3039
httpx==0.28.1
@@ -38,8 +47,12 @@ idna==3.10
3847
# requests
3948
iniconfig==2.1.0
4049
# via pytest
50+
jinja2==3.1.6
51+
# via moodle-mate (pyproject.toml)
4152
jiter==0.10.0
4253
# via openai
54+
markupsafe==3.0.3
55+
# via jinja2
4356
nodeenv==1.9.1
4457
# via pre-commit
4558
openai==1.99.9
@@ -53,13 +66,23 @@ pluggy==1.6.0
5366
pre-commit==4.3.0
5467
# via moodle-mate (pyproject.toml)
5568
pydantic==2.11.7
56-
# via openai
69+
# via
70+
# moodle-mate (pyproject.toml)
71+
# fastapi
72+
# openai
73+
# pydantic-settings
5774
pydantic-core==2.33.2
5875
# via pydantic
76+
pydantic-settings==2.12.0
77+
# via moodle-mate (pyproject.toml)
5978
pygments==2.19.2
6079
# via pytest
6180
pytest==8.4.1
6281
# via moodle-mate (pyproject.toml)
82+
python-dotenv==1.2.1
83+
# via pydantic-settings
84+
python-multipart==0.0.20
85+
# via moodle-mate (pyproject.toml)
6386
pyyaml==6.0.2
6487
# via pre-commit
6588
regex==2025.7.34
@@ -74,6 +97,8 @@ sniffio==1.3.1
7497
# via
7598
# anyio
7699
# openai
100+
starlette==0.50.0
101+
# via fastapi
77102
tiktoken==0.11.0
78103
# via moodle-mate (pyproject.toml)
79104
tqdm==4.67.1
@@ -83,15 +108,24 @@ types-requests==2.32.4.20250809
83108
typing-extensions==4.14.1
84109
# via
85110
# anyio
111+
# fastapi
86112
# openai
87113
# pydantic
88114
# pydantic-core
115+
# starlette
89116
# typing-inspection
90117
typing-inspection==0.4.1
91-
# via pydantic
118+
# via
119+
# pydantic
120+
# pydantic-settings
92121
urllib3==2.5.0
93122
# via
123+
# moodle-mate (pyproject.toml)
94124
# requests
95125
# types-requests
126+
uvicorn==0.38.0
127+
# via moodle-mate (pyproject.toml)
96128
virtualenv==20.34.0
97-
# via pre-commit
129+
# via pre-commit
130+
webencodings==0.5.1
131+
# via bleach

src/app.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import logging
2+
import threading
23
import time
34
from typing import TYPE_CHECKING
5+
import uvicorn
46

57
from src.core.utils.retry import with_retry
68
from src.infrastructure.http.request_manager import request_manager
9+
from src.web.api import WebUI
710

811
if TYPE_CHECKING:
912
from src.config import Settings
@@ -30,10 +33,14 @@ def __init__(
3033
self.moodle_api = moodle_api
3134
self.state_manager = state_manager
3235
self._last_heartbeat_sent: float = 0.0
36+
self._web_server_thread: threading.Thread = None
3337

3438
def run(self) -> None:
3539
"""Starts the main application loop."""
3640
try:
41+
if self.settings.web.enabled:
42+
self._start_web_ui()
43+
3744
self._main_loop()
3845
except KeyboardInterrupt:
3946
logging.info("Shutting down gracefully...")
@@ -42,6 +49,25 @@ def run(self) -> None:
4249
logging.error(f"An unexpected error occurred: {str(e)}")
4350
raise
4451

52+
def _start_web_ui(self):
53+
"""Starts the Web UI server in a separate thread."""
54+
web_ui = WebUI(self.settings, self.state_manager, self)
55+
app = web_ui.get_app()
56+
57+
def run_server():
58+
host = self.settings.web.host
59+
port = self.settings.web.port
60+
logging.info(f"Starting Web UI on http://{host}:{port}")
61+
uvicorn.run(
62+
app,
63+
host=host,
64+
port=port,
65+
log_level="warning"
66+
)
67+
68+
self._web_server_thread = threading.Thread(target=run_server, daemon=True)
69+
self._web_server_thread.start()
70+
4571
def _main_loop(self) -> None:
4672
"""The main loop that continuously fetches and processes notifications."""
4773
consecutive_errors = 0

src/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ class HealthConfig(BaseModel):
3131
failure_alert_threshold: Optional[int] = None
3232
target_provider: Optional[str] = None
3333

34+
class WebConfig(BaseModel):
35+
enabled: bool = True
36+
host: str = "0.0.0.0"
37+
port: int = 9095
38+
auth_secret: Optional[str] = None
39+
3440
# Providers
3541
class DiscordConfig(BaseModel):
3642
enabled: bool = False
@@ -62,6 +68,7 @@ class Settings(BaseSettings):
6268
notification: NotificationConfig = Field(default_factory=NotificationConfig)
6369
filters: FiltersConfig = Field(default_factory=FiltersConfig)
6470
health: HealthConfig = Field(default_factory=HealthConfig)
71+
web: WebConfig = Field(default_factory=WebConfig)
6572

6673
# Providers
6774
# To add a new provider, define its config model above and add it here.

0 commit comments

Comments
 (0)