diff options
| author | Ben Sima <ben@bensima.com> | 2025-12-17 13:29:40 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-12-17 13:29:40 -0500 |
| commit | ab01b34bf563990e0f491ada646472aaade97610 (patch) | |
| tree | 5e46a1a157bb846b0c3a090a83153c788da2b977 /Biz/PodcastItLater/Admin | |
| parent | e112d3ce07fa24f31a281e521a554cc881a76c7b (diff) | |
| parent | 337648981cc5a55935116141341521f4fce83214 (diff) | |
Merge Ava deployment changes
Diffstat (limited to 'Biz/PodcastItLater/Admin')
| -rw-r--r-- | Biz/PodcastItLater/Admin/Core.py | 95 | ||||
| -rw-r--r-- | Biz/PodcastItLater/Admin/Handlers.py | 28 | ||||
| -rw-r--r-- | Biz/PodcastItLater/Admin/Views.py | 107 | ||||
| -rw-r--r-- | Biz/PodcastItLater/Admin/__init__.py | 1 |
4 files changed, 221 insertions, 10 deletions
diff --git a/Biz/PodcastItLater/Admin/Core.py b/Biz/PodcastItLater/Admin/Core.py new file mode 100644 index 0000000..10ea7f6 --- /dev/null +++ b/Biz/PodcastItLater/Admin/Core.py @@ -0,0 +1,95 @@ +""" +PodcastItLater Admin Interface. + +Admin pages and functionality for managing users and queue items. +""" + +# : out podcastitlater-admin +# : dep ludic +# : dep httpx +# : dep starlette +# : dep pytest +# : dep pytest-asyncio +# : dep pytest-mock + +import Biz.PodcastItLater.Admin.Handlers as Handlers +import Biz.PodcastItLater.Admin.Views as Views +import sys + +# Re-export all symbols for backward compatibility +ActionButtons = Views.ActionButtons +ActionButtonsAttrs = Views.ActionButtonsAttrs +AdminUsers = Views.AdminUsers +AdminUsersAttrs = Views.AdminUsersAttrs +AdminView = Views.AdminView +AdminViewAttrs = Views.AdminViewAttrs +EpisodeTableRow = Views.EpisodeTableRow +EpisodeTableRowAttrs = Views.EpisodeTableRowAttrs +MetricCard = Views.MetricCard +MetricCardAttrs = Views.MetricCardAttrs +MetricsAttrs = Views.MetricsAttrs +MetricsDashboard = Views.MetricsDashboard +QueueTableRow = Views.QueueTableRow +QueueTableRowAttrs = Views.QueueTableRowAttrs +StatusBadge = Views.StatusBadge +StatusBadgeAttrs = Views.StatusBadgeAttrs +TopEpisodesTable = Views.TopEpisodesTable +TopEpisodesTableAttrs = Views.TopEpisodesTableAttrs +TruncatedText = Views.TruncatedText +TruncatedTextAttrs = Views.TruncatedTextAttrs +UserTableRow = Views.UserTableRow +UserTableRowAttrs = Views.UserTableRowAttrs +create_table_header = Views.create_table_header +AdminFeedback = Views.AdminFeedback +AdminFeedbackAttrs = Views.AdminFeedbackAttrs + +admin_feedback = Handlers.admin_feedback +admin_metrics = Handlers.admin_metrics +admin_queue_status = Handlers.admin_queue_status +admin_users = Handlers.admin_users +delete_queue_item = Handlers.delete_queue_item +retry_queue_item = Handlers.retry_queue_item +toggle_episode_public = Handlers.toggle_episode_public +update_user_status = Handlers.update_user_status + +__all__ = [ + "ActionButtons", + "ActionButtonsAttrs", + "AdminFeedback", + "AdminFeedbackAttrs", + "AdminUsers", + "AdminUsersAttrs", + "AdminView", + "AdminViewAttrs", + "EpisodeTableRow", + "EpisodeTableRowAttrs", + "MetricCard", + "MetricCardAttrs", + "MetricsAttrs", + "MetricsDashboard", + "QueueTableRow", + "QueueTableRowAttrs", + "StatusBadge", + "StatusBadgeAttrs", + "TopEpisodesTable", + "TopEpisodesTableAttrs", + "TruncatedText", + "TruncatedTextAttrs", + "UserTableRow", + "UserTableRowAttrs", + "admin_feedback", + "admin_metrics", + "admin_queue_status", + "admin_users", + "create_table_header", + "delete_queue_item", + "retry_queue_item", + "toggle_episode_public", + "update_user_status", +] + + +def main() -> None: + """Admin tests are currently in Web.""" + if "test" in sys.argv: + sys.exit(0) diff --git a/Biz/PodcastItLater/Admin/Handlers.py b/Biz/PodcastItLater/Admin/Handlers.py index b98c551..9e06fe6 100644 --- a/Biz/PodcastItLater/Admin/Handlers.py +++ b/Biz/PodcastItLater/Admin/Handlers.py @@ -296,3 +296,31 @@ def admin_metrics(request: Request) -> Views.MetricsDashboard | Response: metrics = Core.Database.get_metrics_summary() return Views.MetricsDashboard(metrics=metrics, user=user) + + +def admin_feedback(request: Request) -> Views.AdminFeedback | Response: + """Admin feedback view.""" + # Check if user is logged in and is admin + user_id = request.session.get("user_id") + if not user_id: + return Response( + "", + status_code=302, + headers={"Location": "/"}, + ) + + user = Core.Database.get_user_by_id( + user_id, + ) + if not user or not Core.is_admin(user): + return Response( + "", + status_code=302, + headers={"Location": "/?error=forbidden"}, + ) + + # Get feedback data + feedback = Core.Database.get_feedback(limit=100) + count = Core.Database.get_feedback_count() + + return Views.AdminFeedback(feedback=feedback, count=count, user=user) diff --git a/Biz/PodcastItLater/Admin/Views.py b/Biz/PodcastItLater/Admin/Views.py index 7cc71a5..057c5e0 100644 --- a/Biz/PodcastItLater/Admin/Views.py +++ b/Biz/PodcastItLater/Admin/Views.py @@ -263,16 +263,16 @@ class MetricsDashboard(Component[AnyChildren, MetricsAttrs]): "Most Played", classes=["card-title", "mb-0"], ), - classes=["card-header", "bg-white"], + classes=["card-header"], ), TopEpisodesTable( episodes=metrics["most_played"], metric_name="Plays", count_key="play_count", ), - classes=["card", "shadow-sm"], + classes=["card", "shadow-sm", "mb-3"], ), - classes=["col-lg-4"], + classes=["col-12"], ), html.div( html.div( @@ -288,16 +288,16 @@ class MetricsDashboard(Component[AnyChildren, MetricsAttrs]): "Most Downloaded", classes=["card-title", "mb-0"], ), - classes=["card-header", "bg-white"], + classes=["card-header"], ), TopEpisodesTable( episodes=metrics["most_downloaded"], metric_name="Downloads", count_key="download_count", ), - classes=["card", "shadow-sm"], + classes=["card", "shadow-sm", "mb-3"], ), - classes=["col-lg-4"], + classes=["col-12"], ), html.div( html.div( @@ -313,16 +313,16 @@ class MetricsDashboard(Component[AnyChildren, MetricsAttrs]): "Most Added to Feeds", classes=["card-title", "mb-0"], ), - classes=["card-header", "bg-white"], + classes=["card-header"], ), TopEpisodesTable( episodes=metrics["most_added"], metric_name="Adds", count_key="add_count", ), - classes=["card", "shadow-sm"], + classes=["card", "shadow-sm", "mb-3"], ), - classes=["col-lg-4"], + classes=["col-12"], ), classes=["row", "g-3"], ), @@ -742,3 +742,92 @@ class AdminView(Component[AnyChildren, AdminViewAttrs]): ), ), ) + + +class AdminFeedbackAttrs(Attrs): + """Attributes for AdminFeedback component.""" + + feedback: list[dict[str, typing.Any]] + count: int + user: dict[str, typing.Any] + + +class AdminFeedback(Component[AnyChildren, AdminFeedbackAttrs]): + """Admin feedback listing page.""" + + @override + def render(self) -> UI.PageLayout: + feedback = self.attrs["feedback"] + count = self.attrs["count"] + user = self.attrs["user"] + + rows = [ + html.tr( + html.td(html.small(fb["id"][:8], classes=["text-muted"])), + html.td(fb.get("email") or "-"), + html.td( + html.span( + fb.get("source") or "-", + classes=["badge", "bg-secondary"], + ), + ), + html.td(str(fb.get("rating") or "-")), + html.td( + TruncatedText( + text=fb.get("use_case") or "-", + max_length=50, + ), + ), + html.td( + TruncatedText( + text=fb.get("feedback_text") or "-", + max_length=50, + ), + ), + html.td( + html.small( + fb["created_at"], + classes=["text-muted"], + ), + ), + ) + for fb in feedback + ] + + return UI.PageLayout( + html.div( + html.h2( + html.i(classes=["bi", "bi-chat-heart-fill", "me-2"]), + f"Feedback ({count})", + classes=["mb-4"], + ), + html.div( + html.table( + create_table_header([ + "ID", + "Email", + "Source", + "Rating", + "Use Case", + "Feedback", + "Created", + ]), + html.tbody(*rows), + classes=["table", "table-hover", "table-sm"], + ), + classes=["table-responsive"], + ) + if rows + else html.div( + html.p( + "No feedback yet.", + classes=["text-muted", "text-center", "py-5"], + ), + ), + ), + user=user, + current_page="admin", + page_title="Admin Feedback - PodcastItLater", + error=None, + meta_tags=[], + ) diff --git a/Biz/PodcastItLater/Admin/__init__.py b/Biz/PodcastItLater/Admin/__init__.py deleted file mode 100644 index 04e3e32..0000000 --- a/Biz/PodcastItLater/Admin/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""PodcastItLater Admin package.""" |
