homepage
This commit is contained in:
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
27
extras/homepage-widget.yaml
Normal file
27
extras/homepage-widget.yaml
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user