This commit is contained in:
schmeeve
2026-09-23 13:01:22 -07:00
parent 220991de09
commit 35c71a1e6d
4 changed files with 80 additions and 0 deletions

View File

@@ -233,5 +233,10 @@ async def search(q: str, _: None = Depends(require_access)):
return await notes.search_notes(q) return await notes.search_notes(q)
@app.get("/api/homepage")
async def homepage_stats(_: None = Depends(require_access)):
return await notes.get_homepage_stats()
# Mount static assets last so /api routes take precedence # Mount static assets last so /api routes take precedence
app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/static", StaticFiles(directory="static"), name="static")

View File

@@ -346,6 +346,36 @@ async def _record_history(
) )
async def get_homepage_stats() -> dict:
from pathlib import Path
async with aiosqlite.connect(settings.database_path) as db:
db.row_factory = aiosqlite.Row
async def count(query: str) -> int:
row = await db.execute(query)
row = await row.fetchone()
return row[0]
total_notes = await count("SELECT COUNT(*) FROM notes WHERE deleted_at IS NULL")
actions = await count("SELECT COUNT(*) FROM history")
users = await count("SELECT COUNT(*) FROM users")
attachments = 0
data_root = Path(settings.data_dir)
if data_root.is_dir():
for attach_dir in data_root.rglob("attachments"):
if attach_dir.is_dir():
attachments += sum(1 for f in attach_dir.iterdir() if f.is_file())
return {
"total_notes": total_notes,
"attachments": attachments,
"actions": actions,
"users": users,
}
async def get_history(note_id: str) -> list[dict]: async def get_history(note_id: str) -> list[dict]:
async with aiosqlite.connect(settings.database_path) as db: async with aiosqlite.connect(settings.database_path) as db:
db.row_factory = aiosqlite.Row db.row_factory = aiosqlite.Row

View File

@@ -0,0 +1,27 @@
# Paste this into /opt/homepage/config/services.yaml as a new entry.
#
# Replace {{ SNOTES_URL }} with where snotes is reachable, e.g.
# http://192.168.1.45:8019 (host mapped port)
- Snotes:
icon: snotes-main.png
href: "{{ SNOTES_URL }}"
description: Markdown notes
widget:
type: customapi
url: "{{ SNOTES_URL }}/api/homepage"
refreshInterval: 60000
display: block
mappings:
- field: total_notes
label: Notes
format: number
- field: attachments
label: Attachments
format: number
- field: actions
label: Actions
format: number
- field: users
label: Users
format: number

View File

@@ -153,6 +153,24 @@ async def test_history_recorded(client):
assert history[0]["ip"] == "192.168.1.5" assert history[0]["ip"] == "192.168.1.5"
async def test_homepage_stats(client):
await client.post("/api/notes", json={"body": "note one"})
await client.post("/api/notes", json={"body": "note two"})
await client.post(
"/api/attachments",
files={"file": ("a.png", b"abc", "image/png")},
data={"username": "schmeeve"},
)
r = await client.get("/api/homepage")
assert r.status_code == 200
stats = r.json()
assert stats["total_notes"] == 2
assert stats["attachments"] == 1
assert stats["actions"] == 2
assert stats["users"] == 3
async def test_user_selection_cookie(client): async def test_user_selection_cookie(client):
r = await client.post("/api/users/select", json={"username": "ilya"}) r = await client.post("/api/users/select", json={"username": "ilya"})
assert r.status_code == 200 assert r.status_code == 200