diff --git a/public/app.js b/public/app.js index 3ddbbf5..013816c 100644 --- a/public/app.js +++ b/public/app.js @@ -109,22 +109,43 @@ document.addEventListener('alpine:init', () => { const data = await response.json(); if (data.status === 'ok') { + // Show info toast that OAuth is in progress + Alpine.store('global').showToast(Alpine.store('global').t('oauthInProgress'), 'info'); + + // Open OAuth window window.open(data.url, 'google_oauth', 'width=600,height=700,scrollbars=yes'); - const messageHandler = (event) => { - if (event.data?.type === 'oauth-success') { + // Poll for account changes instead of relying on postMessage + // (since OAuth callback is now on port 51121, not this server) + const initialAccountCount = Alpine.store('data').accounts.length; + let pollCount = 0; + const maxPolls = 60; // 2 minutes (2 second intervals) + + const pollInterval = setInterval(async () => { + pollCount++; + + // Refresh account list + await Alpine.store('data').fetchData(); + + // Check if new account was added + const currentAccountCount = Alpine.store('data').accounts.length; + if (currentAccountCount > initialAccountCount) { + clearInterval(pollInterval); const actionKey = reAuthEmail ? 'reauthenticated' : 'added'; const action = Alpine.store('global').t(actionKey); const successfully = Alpine.store('global').t('successfully'); - const msg = `${Alpine.store('global').t('accounts')} ${event.data.email} ${action} ${successfully}`; - - Alpine.store('global').showToast(msg, 'success'); - Alpine.store('data').fetchData(); + Alpine.store('global').showToast( + `${Alpine.store('global').t('accounts')} ${action} ${successfully}`, + 'success' + ); document.getElementById('add_account_modal')?.close(); } - }; - window.addEventListener('message', messageHandler); - setTimeout(() => window.removeEventListener('message', messageHandler), 300000); + + // Stop polling after max attempts + if (pollCount >= maxPolls) { + clearInterval(pollInterval); + } + }, 2000); // Poll every 2 seconds } else { Alpine.store('global').showToast(data.error || Alpine.store('global').t('failedToGetAuthUrl'), 'error'); } diff --git a/public/css/style.css b/public/css/style.css index f021efd..7e18137 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -130,7 +130,10 @@ /* View Containers */ .view-container { - @apply max-w-7xl mx-auto p-6 space-y-6 animate-fade-in; + @apply mx-auto p-6 space-y-6 animate-fade-in; + /* Responsive max-width: use most of screen on small displays, + but cap at 1600px on large displays for reading comfort */ + max-width: min(95%, 1600px); } /* Section Headers */ diff --git a/public/js/components/account-manager.js b/public/js/components/account-manager.js index 04501c8..cb564b3 100644 --- a/public/js/components/account-manager.js +++ b/public/js/components/account-manager.js @@ -28,6 +28,14 @@ window.Components.accountManager = () => ({ async toggleAccount(email, enabled) { const store = Alpine.store('global'); const password = store.webuiPassword; + + // Optimistic update: immediately update UI + const dataStore = Alpine.store('data'); + const account = dataStore.accounts.find(a => a.email === email); + if (account) { + account.enabled = enabled; + } + try { const { response, newPassword } = await window.utils.request(`/api/accounts/${encodeURIComponent(email)}/toggle`, { method: 'POST', @@ -40,12 +48,23 @@ window.Components.accountManager = () => ({ if (data.status === 'ok') { const status = enabled ? store.t('enabledStatus') : store.t('disabledStatus'); store.showToast(store.t('accountToggled', { email, status }), 'success'); - Alpine.store('data').fetchData(); + // Refresh to confirm server state + await dataStore.fetchData(); } else { store.showToast(data.error || store.t('toggleFailed'), 'error'); + // Rollback optimistic update on error + if (account) { + account.enabled = !enabled; + } + await dataStore.fetchData(); } } catch (e) { store.showToast(store.t('toggleFailed') + ': ' + e.message, 'error'); + // Rollback optimistic update on error + if (account) { + account.enabled = !enabled; + } + await dataStore.fetchData(); } }, diff --git a/public/js/components/dashboard.js b/public/js/components/dashboard.js index d41f841..83c42fb 100644 --- a/public/js/components/dashboard.js +++ b/public/js/components/dashboard.js @@ -495,7 +495,10 @@ window.Components.dashboard = () => ({ const isCore = (id) => /sonnet|opus|pro|flash/i.test(id); - accounts.forEach(acc => { + // Only count enabled accounts in statistics + const enabledAccounts = accounts.filter(acc => acc.enabled !== false); + + enabledAccounts.forEach(acc => { if (acc.status === 'ok') { const limits = Object.entries(acc.limits || {}); let hasActiveCore = limits.some(([id, l]) => l && l.remainingFraction > 0.05 && isCore(id)); @@ -512,7 +515,10 @@ window.Components.dashboard = () => ({ limited++; } }); - this.stats.total = accounts.length; + + // TOTAL shows only enabled accounts + // Disabled accounts are excluded from all statistics + this.stats.total = enabledAccounts.length; this.stats.active = active; this.stats.limited = limited; }, diff --git a/public/js/store.js b/public/js/store.js index 2aa6c99..4539586 100644 --- a/public/js/store.js +++ b/public/js/store.js @@ -50,17 +50,20 @@ document.addEventListener('alpine:init', () => { enabled: "ENABLED", health: "HEALTH", identity: "IDENTITY (EMAIL)", + source: "SOURCE", projectId: "PROJECT ID", sessionState: "SESSION STATE", operations: "OPERATIONS", delete: "Delete", confirmDelete: "Are you sure you want to remove this account?", + cannotDeleteDatabase: "Cannot delete: This account is from Antigravity database (read-only)", connectGoogle: "Connect Google Account", reauthenticated: "re-authenticated", added: "added", successfully: "successfully", failedToGetAuthUrl: "Failed to get auth URL", failedToStartOAuth: "Failed to start OAuth flow", + oauthInProgress: "OAuth in progress. Please complete authentication in the popup window...", family: "Family", model: "Model", activeSuffix: "Active", @@ -211,17 +214,20 @@ document.addEventListener('alpine:init', () => { enabled: "启用", health: "健康度", identity: "身份 (邮箱)", + source: "来源", projectId: "项目 ID", sessionState: "会话状态", operations: "操作", delete: "删除", confirmDelete: "确定要移除此账号吗?", + cannotDeleteDatabase: "无法删除:此账号来自 Antigravity 数据库(只读)", connectGoogle: "连接 Google 账号", reauthenticated: "已重新认证", added: "已添加", successfully: "成功", failedToGetAuthUrl: "获取认证链接失败", failedToStartOAuth: "启动 OAuth 流程失败", + oauthInProgress: "OAuth 授权进行中,请在弹出窗口中完成认证...", family: "系列", model: "模型", activeSuffix: "活跃", diff --git a/public/views/accounts.html b/public/views/accounts.html index b5d0f1b..c37bd37 100644 --- a/public/views/accounts.html +++ b/public/views/accounts.html @@ -25,6 +25,7 @@ Enabled Identity (Email) + Source Project ID Health Operations @@ -50,6 +51,12 @@ + + + +
@@ -81,8 +88,11 @@