File size: 5,799 Bytes
b4f2238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
"""
Token 管理 Admin
提供 Token 的查看、搜索、过滤功能
"""

from __future__ import annotations

from typing import Any

from django.contrib import admin
from django.db.models import QuerySet
from django.http import HttpRequest
from django.utils import timezone
from django.utils.html import format_html
from django.utils.safestring import SafeString
from django.utils.translation import gettext_lazy as _

from apps.automation.models import CourtToken

SITE_NAME_LABELS: dict[str, str] = {
    "court_zxfw": "人民法院在线服务网(一张网)",
    "court_baoquan": "人民法院保全系统",
}


@admin.register(CourtToken)
class CourtTokenAdmin(admin.ModelAdmin[CourtToken]):
    """
    一张网/保全系统 Token 管理 Admin

    功能:
    - 查看所有一张网/保全系统 Token
    - 按网站、账号搜索
    - 按过期状态过滤
    - 显示 Token 状态(有效/过期)
    """

    def get_model_perms(self, request: HttpRequest) -> dict[str, bool]:
        """隐藏 Admin 首页入口,但保留直接 URL 访问能力"""
        return {}

    list_display = [
        "id",
        "site_name_display",
        "account",
        "token_preview",
        "token_type",
        "status_display",
        "expires_at",
        "created_at",
        "updated_at",
    ]

    list_filter = [
        "site_name",
        "token_type",
        "created_at",
        "expires_at",
    ]

    search_fields = [
        "site_name",
        "account",
        "token",
    ]

    readonly_fields = [
        "id",
        "token_full",
        "status_display",
        "remaining_time",
        "created_at",
        "updated_at",
    ]

    fieldsets = (
        (
            _("基本信息"),
            {
                "description": _("该模块用于管理人民法院在线服务网(一张网)和保全系统的认证Token。"),
                "fields": ("id", "site_name", "account", "token_type"),
            },
        ),
        (_("Token 信息"), {"fields": ("token_full", "status_display", "remaining_time")}),
        (_("时间信息"), {"fields": ("expires_at", "created_at", "updated_at")}),
    )

    ordering = ["-created_at"]
    date_hierarchy = "created_at"

    list_per_page = 50

    @admin.display(description=_("站点"))
    def site_name_display(self, obj: CourtToken) -> str:
        """显示可读站点名称"""
        return SITE_NAME_LABELS.get(obj.site_name, obj.site_name)  # type: ignore[no-any-return]

    @admin.display(description=_("Token 预览"))
    def token_preview(self, obj: CourtToken) -> str:
        """Token 预览(只显示前20个字符)"""
        if len(obj.token) > 20:
            return f"{obj.token[:20]}..."
        return obj.token  # type: ignore[no-any-return]

    @admin.display(description=_("完整 Token"))
    def token_full(self, obj: CourtToken) -> SafeString:
        """完整的 Token(在详情页显示)"""
        return format_html(
            '<textarea readonly style="width: 100%; height: 100px; '
            "font-family: monospace; font-size: 12px; padding: 10px; "
            'border: 1px solid #ddd; border-radius: 4px;">{}</textarea>',
            obj.token,
        )

    @admin.display(description=_("状态"))
    def status_display(self, obj: CourtToken) -> SafeString:
        """显示 Token 状态(有效/过期)"""
        if obj.is_expired():
            return format_html('<span style="color: red; font-weight: bold;">{}</span>', "❌ 已过期")
        else:
            return format_html('<span style="color: green; font-weight: bold;">{}</span>', "✅ 有效")

    @admin.display(description=_("剩余时间"))
    def remaining_time(self, obj: CourtToken) -> SafeString:
        """剩余有效时间"""
        if obj.is_expired():
            return format_html('<span style="color: red;">{}</span>', "已过期")

        now = timezone.now()
        remaining = obj.expires_at - now

        total_seconds = int(remaining.total_seconds())
        hours = total_seconds // 3600
        minutes = (total_seconds % 3600) // 60
        seconds = total_seconds % 60

        if hours > 0:
            time_str = f"{hours} 小时 {minutes} 分钟"
        elif minutes > 0:
            time_str = f"{minutes} 分钟 {seconds} 秒"
        else:
            time_str = f"{seconds} 秒"

        if total_seconds < 300:
            color = "red"
        elif total_seconds < 1800:
            color = "orange"
        else:
            color = "green"

        return format_html('<span style="color: {}; font-weight: bold;">{}</span>', color, time_str)

    def has_add_permission(self, request: HttpRequest) -> bool:
        """禁用添加功能(Token 由系统自动创建)"""
        return False

    def has_change_permission(self, request: HttpRequest, obj: CourtToken | None = None) -> bool:
        """禁用修改功能(Token 由系统管理)"""
        return False

    def get_actions(self, request: HttpRequest) -> dict[str, Any]:
        """自定义批量操作"""
        actions = super().get_actions(request)

        actions["delete_expired_tokens"] = (  # type: ignore[assignment]
            self.delete_expired_tokens,
            "delete_expired_tokens",
            _("删除已过期的一张网/保全Token"),
        )

        return actions

    def delete_expired_tokens(self, request: HttpRequest, queryset: QuerySet[CourtToken]) -> None:
        """批量删除过期的一张网/保全Token"""
        expired_tokens = [token for token in queryset if token.is_expired()]
        count = len(expired_tokens)

        for token in expired_tokens:
            token.delete()

        self.message_user(request, _(f"成功删除 {count} 个已过期的一张网/保全Token"))