diff --git a/internal/admin/deps.go b/internal/admin/deps.go index e92c37b..7debcf0 100644 --- a/internal/admin/deps.go +++ b/internal/admin/deps.go @@ -16,6 +16,7 @@ type ConfigStore interface { Accounts() []config.Account FindAccount(identifier string) (config.Account, bool) UpdateAccountToken(identifier, token string) error + UpdateAccountTestStatus(identifier, status string) error Update(mutator func(*config.Config) error) error ExportJSONAndBase64() (string, string, error) IsEnvBacked() bool diff --git a/internal/admin/handler_accounts_crud.go b/internal/admin/handler_accounts_crud.go index daaa434..a0d64df 100644 --- a/internal/admin/handler_accounts_crud.go +++ b/internal/admin/handler_accounts_crud.go @@ -56,6 +56,7 @@ func (h *Handler) listAccounts(w http.ResponseWriter, r *http.Request) { "has_password": acc.Password != "", "has_token": token != "", "token_preview": preview, + "test_status": acc.TestStatus, }) } writeJSON(w, http.StatusOK, map[string]any{"items": items, "total": total, "page": page, "page_size": pageSize, "total_pages": totalPages}) diff --git a/internal/admin/handler_accounts_testing.go b/internal/admin/handler_accounts_testing.go index 2bd7706..7a7430d 100644 --- a/internal/admin/handler_accounts_testing.go +++ b/internal/admin/handler_accounts_testing.go @@ -88,7 +88,15 @@ func runAccountTestsConcurrently(accounts []config.Account, maxConcurrency int, func (h *Handler) testAccount(ctx context.Context, acc config.Account, model, message string) map[string]any { start := time.Now() - result := map[string]any{"account": acc.Identifier(), "success": false, "response_time": 0, "message": "", "model": model} + identifier := acc.Identifier() + result := map[string]any{"account": identifier, "success": false, "response_time": 0, "message": "", "model": model} + defer func() { + status := "failed" + if ok, _ := result["success"].(bool); ok { + status = "ok" + } + _ = h.Store.UpdateAccountTestStatus(identifier, status) + }() token := strings.TrimSpace(acc.Token) if token == "" { newToken, err := h.DS.Login(ctx, acc) diff --git a/internal/config/config.go b/internal/config/config.go index 4b281a2..b8d59d6 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -18,10 +18,11 @@ type Config struct { } type Account struct { - Email string `json:"email,omitempty"` - Mobile string `json:"mobile,omitempty"` - Password string `json:"password,omitempty"` - Token string `json:"token,omitempty"` + Email string `json:"email,omitempty"` + Mobile string `json:"mobile,omitempty"` + Password string `json:"password,omitempty"` + Token string `json:"token,omitempty"` + TestStatus string `json:"test_status,omitempty"` } type CompatConfig struct { diff --git a/internal/config/store.go b/internal/config/store.go index 2e6fcaf..7a09cdc 100644 --- a/internal/config/store.go +++ b/internal/config/store.go @@ -97,6 +97,18 @@ func (s *Store) FindAccount(identifier string) (Account, bool) { return Account{}, false } +func (s *Store) UpdateAccountTestStatus(identifier, status string) error { + identifier = strings.TrimSpace(identifier) + s.mu.Lock() + defer s.mu.Unlock() + idx, ok := s.findAccountIndexLocked(identifier) + if !ok { + return errors.New("account not found") + } + s.cfg.Accounts[idx].TestStatus = status + return s.saveLocked() +} + func (s *Store) UpdateAccountToken(identifier, token string) error { identifier = strings.TrimSpace(identifier) s.mu.Lock() diff --git a/webui/src/features/account/AccountManagerContainer.jsx b/webui/src/features/account/AccountManagerContainer.jsx index f5f9324..558739d 100644 --- a/webui/src/features/account/AccountManagerContainer.jsx +++ b/webui/src/features/account/AccountManagerContainer.jsx @@ -17,10 +17,12 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage, setKeysExpanded, accounts, page, + pageSize, totalPages, totalAccounts, loadingAccounts, fetchAccounts, + changePageSize, resolveAccountIdentifier, } = useAccountsData({ apiFetch }) @@ -79,6 +81,7 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage, batchProgress={batchProgress} totalAccounts={totalAccounts} page={page} + pageSize={pageSize} totalPages={totalPages} resolveAccountIdentifier={resolveAccountIdentifier} onTestAll={testAllAccounts} @@ -87,6 +90,7 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage, onDeleteAccount={deleteAccount} onPrevPage={() => fetchAccounts(page - 1)} onNextPage={() => fetchAccounts(page + 1)} + onPageSizeChange={changePageSize} /> { + navigator.clipboard.writeText(id).then(() => { + setCopiedId(id) + setTimeout(() => setCopiedId(null), 1500) + }) + } return (
@@ -83,12 +94,23 @@ export default function AccountsTable({
-
{id || '-'}
+
copyId(id)} + > + {id || '-'} + {copiedId === id + ? + : + } +
- {acc.has_token ? t('accountManager.sessionActive') : t('accountManager.reauthRequired')} + {acc.test_status === 'failed' ? t('accountManager.testStatusFailed') : (acc.test_status === 'ok' || acc.has_token) ? t('accountManager.sessionActive') : t('accountManager.reauthRequired')} {acc.token_preview && ( {acc.token_preview} @@ -122,8 +144,19 @@ export default function AccountsTable({ {totalPages > 1 && (
-
- {t('accountManager.pageInfo', { current: page, total: totalPages, count: totalAccounts })} +
+
+ {t('accountManager.pageInfo', { current: page, total: totalPages, count: totalAccounts })} +
+