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 @@
|