|
38 | 38 | from divbase_api.deps import _authenticate_frontend_user_from_tokens |
39 | 39 | from divbase_api.frontend_routes.auth import get_login, post_logout |
40 | 40 | from divbase_api.models.announcements import AnnouncementDB, AnnouncementLevel, AnnouncementTarget |
| 41 | +from divbase_api.models.personal_access_tokens import PersonalAccessTokenDB |
41 | 42 | from divbase_api.models.project_versions import ProjectVersionDB |
42 | 43 | from divbase_api.models.projects import ProjectDB, ProjectMembershipDB, ProjectRoles |
43 | | -from divbase_api.models.queue_status import QueueStatus |
| 44 | +from divbase_api.models.queue_status import QueueStatusDB |
44 | 45 | from divbase_api.models.revoked_tokens import RevokedTokenDB, TokenRevokeReason |
45 | 46 | from divbase_api.models.task_history import CeleryTaskMeta, TaskHistoryDB, TaskStartedAtDB |
46 | 47 | from divbase_api.models.users import UserDB |
@@ -631,7 +632,7 @@ class AnnouncementView(ModelView): |
631 | 632 |
|
632 | 633 | class QueueStatusView(ModelView): |
633 | 634 | """ |
634 | | - Custom admin panel View for the QueueStatus model. |
| 635 | + Custom admin panel View for the QueueStatusDB model. |
635 | 636 |
|
636 | 637 | This is a singleton table (only 1 row allowed). |
637 | 638 | Deletion and creation are disabled - only editing the existing row is allowed. |
@@ -724,6 +725,76 @@ async def validate(self, request: Request, data: dict[str, Any]) -> None: |
724 | 725 | # as does not work well when modifying the timestamp in edit view, easier to just display as UTC |
725 | 726 |
|
726 | 727 |
|
| 728 | +class PersonalAccessTokenView(ModelView): |
| 729 | + """ |
| 730 | + Custom admin panel View for the PersonalAccessTokenDB model. |
| 731 | + As with passwords, the hashed_token is never displayed in the admin panel on purpose. |
| 732 | + """ |
| 733 | + |
| 734 | + page_size_options = PAGINATION_DEFAULTS |
| 735 | + fields = [ |
| 736 | + IntegerField("id", label="ID", disabled=True), |
| 737 | + StringField("name", label="Name", required=True), |
| 738 | + TextAreaField("description", required=False, label="Description"), |
| 739 | + JSONField( |
| 740 | + "permissions", |
| 741 | + required=False, |
| 742 | + label="Permissions", |
| 743 | + help_text="Permissions associated with the PAT.", |
| 744 | + disabled=True, |
| 745 | + ), |
| 746 | + DateTimeField( |
| 747 | + "expires_at", |
| 748 | + help_text="Timestamp when the PAT expires. Value can be left empty for no expiration. Value determined by system, cannot be edited.", |
| 749 | + required=False, |
| 750 | + disabled=True, |
| 751 | + ), |
| 752 | + DateTimeField( |
| 753 | + "last_used_at", |
| 754 | + help_text="Timestamp when the PAT was last used. Value empty if not yet used. Value determined by system, cannot be edited.", |
| 755 | + required=False, |
| 756 | + disabled=True, |
| 757 | + ), |
| 758 | + BooleanField("is_deleted", required=True, label="Is Deleted", help_text="Mark the PAT as deleted or not."), |
| 759 | + DateTimeField( |
| 760 | + "date_deleted", |
| 761 | + help_text="Timestamp when the PAT was soft deleted (else None). Value determined by system, cannot be edited.", |
| 762 | + disabled=True, |
| 763 | + ), |
| 764 | + IntegerField("user_id", label="User ID", required=False), |
| 765 | + HasOne("user", identity="user", label="User"), |
| 766 | + DateTimeField("created_at", label="Created At", disabled=True), |
| 767 | + DateTimeField("updated_at", label="Updated At", disabled=True), |
| 768 | + ] |
| 769 | + |
| 770 | + exclude_fields_from_list = ["hashed_token", "permissions", "user_id"] |
| 771 | + exclude_fields_from_edit = ["hashed_token", "id", "created_at", "updated_at", "user_id", "user", "permissions"] |
| 772 | + exclude_fields_from_detail = ["hashed_token", "user_id"] |
| 773 | + |
| 774 | + def can_create(self, request: Request) -> bool: |
| 775 | + """Disable manual creation of PATs.""" |
| 776 | + return False |
| 777 | + |
| 778 | + async def edit(self, request: Request, pk: Any, data: dict) -> Any: |
| 779 | + """ |
| 780 | + Override the default edit method to ensure that the `date_deleted` field is updated |
| 781 | + when/if a users soft deletion status is changed. |
| 782 | + """ |
| 783 | + if "is_deleted" in data: |
| 784 | + if data["is_deleted"]: |
| 785 | + data["date_deleted"] = datetime.now(tz=timezone.utc) |
| 786 | + else: |
| 787 | + data["date_deleted"] = None |
| 788 | + |
| 789 | + return await super().edit(request=request, pk=pk, data=data) |
| 790 | + |
| 791 | + async def serialize_field_value(self, value: Any, field: Any, action: RequestAction, request: Request) -> Any: |
| 792 | + formatted = _format_cet_datetime(value, field, ["created_at", "updated_at", "expires_at", "last_used_at"]) |
| 793 | + if formatted is not None: |
| 794 | + return formatted |
| 795 | + return await super().serialize_field_value(value, field, action, request) |
| 796 | + |
| 797 | + |
727 | 798 | class DivBaseAuthProvider(AuthProvider): |
728 | 799 | """ |
729 | 800 | This class enables starlette-admin to make use of DivBase's pre-existing auth system. |
@@ -807,5 +878,12 @@ def register_admin_panel(app: FastAPI, engine: AsyncEngine) -> None: |
807 | 878 | admin.add_view( |
808 | 879 | AnnouncementView(AnnouncementDB, icon="fas fa-bullhorn", label="Announcements", identity="announcement") |
809 | 880 | ) |
810 | | - admin.add_view(QueueStatusView(QueueStatus, icon="fas fa-power-off", label="Queue Status", identity="queue-status")) |
| 881 | + admin.add_view( |
| 882 | + QueueStatusView(QueueStatusDB, icon="fas fa-power-off", label="Queue Status", identity="queue-status") |
| 883 | + ) |
| 884 | + admin.add_view( |
| 885 | + PersonalAccessTokenView( |
| 886 | + PersonalAccessTokenDB, icon="fas fa-key", label="Personal Access Tokens", identity="personal-access-token" |
| 887 | + ) |
| 888 | + ) |
811 | 889 | admin.mount_to(app) |
0 commit comments