Implement periodic health checks every 15 seconds to monitor connection status, pausing when the tab is hidden and resuming on visibility. Update UI bindings to use data store for connection status instead of global store. Add destroy method to clean up timers on component teardown.
379 lines
22 KiB
HTML
379 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" data-theme="antigravity" class="dark">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Antigravity Console</title>
|
|
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
|
|
|
<!-- Libraries -->
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.12.14/dist/full.min.css" rel="stylesheet" type="text/css" />
|
|
<script src="https://cdn.tailwindcss.com?plugins=forms"></script>
|
|
<!-- Alpine.js must be deferred so stores register their listeners first -->
|
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
|
|
<!-- Custom Config -->
|
|
<script>
|
|
tailwind.config = {
|
|
darkMode: 'class',
|
|
theme: {
|
|
extend: {
|
|
fontFamily: {
|
|
mono: ['"JetBrains Mono"', '"Fira Code"', 'Consolas', 'monospace'],
|
|
sans: ['Inter', 'system-ui', 'sans-serif']
|
|
},
|
|
colors: {
|
|
// Deep Space Palette
|
|
space: {
|
|
950: 'var(--color-space-950)', // Deep background
|
|
900: 'var(--color-space-900)', // Panel background
|
|
850: 'var(--color-space-850)', // Hover states
|
|
800: 'var(--color-space-800)', // UI elements
|
|
border: 'var(--color-space-border)'
|
|
},
|
|
neon: {
|
|
purple: 'var(--color-neon-purple)',
|
|
cyan: 'var(--color-neon-cyan)',
|
|
green: 'var(--color-neon-green)',
|
|
yellow: 'var(--color-neon-yellow)',
|
|
red: 'var(--color-neon-red)'
|
|
}
|
|
}
|
|
}
|
|
},
|
|
daisyui: {
|
|
themes: [{
|
|
antigravity: {
|
|
"primary": "var(--color-neon-purple)", // Neon Purple
|
|
"secondary": "var(--color-neon-green)", // Neon Green
|
|
"accent": "var(--color-neon-cyan)", // Neon Cyan
|
|
"neutral": "var(--color-space-800)", // space-800
|
|
"base-100": "var(--color-space-950)", // space-950
|
|
"info": "var(--color-neon-cyan)",
|
|
"success": "var(--color-neon-green)",
|
|
"warning": "var(--color-neon-yellow)",
|
|
"error": "var(--color-neon-red)",
|
|
}
|
|
}]
|
|
}
|
|
}
|
|
</script>
|
|
<link rel="stylesheet" href="css/style.css">
|
|
</head>
|
|
|
|
<body
|
|
class="bg-space-950 text-gray-300 font-sans antialiased min-h-screen overflow-hidden selection:bg-neon-purple selection:text-white"
|
|
x-cloak x-data="app" x-init="console.log('App initialized')">
|
|
|
|
<!-- Toast Notification -->
|
|
<div class="fixed top-4 right-4 z-[100] flex flex-col gap-2 pointer-events-none">
|
|
<template x-if="$store.global.toast">
|
|
<div x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 translate-x-8 scale-95"
|
|
x-transition:enter-end="opacity-100 translate-x-0 scale-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 translate-x-0 scale-100"
|
|
x-transition:leave-end="opacity-0 translate-x-4 scale-95"
|
|
class="alert shadow-lg border backdrop-blur-md pointer-events-auto min-w-[300px]" :class="{
|
|
'alert-info border-neon-cyan/20 bg-space-900/90 text-neon-cyan': $store.global.toast.type === 'info',
|
|
'alert-success border-neon-green/20 bg-space-900/90 text-neon-green': $store.global.toast.type === 'success',
|
|
'alert-error border-red-500/20 bg-space-900/90 text-red-400': $store.global.toast.type === 'error'
|
|
}">
|
|
<div class="flex items-center gap-3">
|
|
<!-- Icons based on type -->
|
|
<template x-if="$store.global.toast.type === 'info'">
|
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</template>
|
|
<template x-if="$store.global.toast.type === 'success'">
|
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</template>
|
|
<template x-if="$store.global.toast.type === 'error'">
|
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</template>
|
|
<span x-text="$store.global.toast.message" class="font-mono text-sm"></span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Navbar -->
|
|
<div
|
|
class="h-14 border-b border-space-border flex items-center px-6 justify-between bg-space-900/50 backdrop-blur-md z-50">
|
|
<div class="flex items-center gap-3">
|
|
<div
|
|
class="w-8 h-8 rounded bg-gradient-to-br from-neon-purple to-blue-600 flex items-center justify-center text-white font-bold shadow-[0_0_15px_rgba(168,85,247,0.4)]">
|
|
AG</div>
|
|
<div class="flex flex-col">
|
|
<span class="text-sm font-bold tracking-wide text-white"
|
|
x-text="$store.global.t('systemName')">ANTIGRAVITY</span>
|
|
<span class="text-[10px] text-gray-500 font-mono tracking-wider"
|
|
x-text="$store.global.t('systemDesc')">CLAUDE PROXY SYSTEM</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-4">
|
|
<!-- Connection Pill -->
|
|
<div class="flex items-center gap-2 px-3 py-1 rounded-full text-xs font-mono border transition-all duration-300"
|
|
:class="connectionStatus === 'connected'
|
|
? 'bg-neon-green/10 border-neon-green/20 text-neon-green'
|
|
: (connectionStatus === 'connecting' ? 'bg-yellow-500/10 border-yellow-500/20 text-yellow-500' : 'bg-red-500/10 border-red-500/20 text-red-500')">
|
|
<div class="w-1.5 h-1.5 rounded-full"
|
|
:class="$store.data.connectionStatus === 'connected' ? 'bg-neon-green shadow-[0_0_8px_rgba(34,197,94,0.6)]' : ($store.data.connectionStatus === 'connecting' ? 'bg-yellow-500 animate-pulse' : 'bg-red-500')">
|
|
</div>
|
|
<span
|
|
x-text="$store.data.connectionStatus === 'connected' ? $store.global.t('online') : ($store.data.connectionStatus === 'disconnected' ? $store.global.t('offline') : $store.global.t('connecting'))"></span>
|
|
</div>
|
|
|
|
<div class="h-4 w-px bg-space-border"></div>
|
|
|
|
<!-- Refresh Button -->
|
|
<button class="btn btn-ghost btn-xs btn-square text-gray-400 hover:text-white hover:bg-white/5"
|
|
@click="fetchData" :disabled="loading" :title="$store.global.t('refreshData')">
|
|
<svg class="w-4 h-4" :class="{'animate-spin': loading}" fill="none" stroke="currentColor"
|
|
viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Layout -->
|
|
<div class="flex h-[calc(100vh-56px)]">
|
|
|
|
<!-- Sidebar -->
|
|
<div class="w-64 bg-space-900 border-r border-space-border flex flex-col pt-6 pb-4">
|
|
<div class="px-4 mb-2 text-xs font-bold text-gray-600 uppercase tracking-widest"
|
|
x-text="$store.global.t('main')">Main</div>
|
|
<nav class="flex flex-col gap-1">
|
|
<button
|
|
class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
|
|
:class="{'active': $store.global.activeTab === 'dashboard'}"
|
|
@click="$store.global.activeTab = 'dashboard'">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
|
|
</svg>
|
|
<span x-text="$store.global.t('dashboard')">Dashboard</span>
|
|
</button>
|
|
<button
|
|
class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
|
|
:class="{'active': $store.global.activeTab === 'models'}"
|
|
@click="$store.global.activeTab = 'models'">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
|
</svg>
|
|
<span x-text="$store.global.t('models')">Models</span>
|
|
</button>
|
|
<button
|
|
class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
|
|
:class="{'active': $store.global.activeTab === 'accounts'}"
|
|
@click="$store.global.activeTab = 'accounts'">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
|
</svg>
|
|
<span x-text="$store.global.t('accounts')">Accounts</span>
|
|
</button>
|
|
</nav>
|
|
|
|
<div class="px-4 mt-8 mb-2 text-xs font-bold text-gray-600 uppercase tracking-widest"
|
|
x-text="$store.global.t('system')">System</div>
|
|
<nav class="flex flex-col gap-1">
|
|
<button
|
|
class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
|
|
:class="{'active': $store.global.activeTab === 'logs'}" @click="$store.global.activeTab = 'logs'">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
</svg>
|
|
<span x-text="$store.global.t('logs')">Logs</span>
|
|
</button>
|
|
<button
|
|
class="nav-item flex items-center gap-3 px-6 py-3 text-sm font-medium text-gray-400 hover:text-white hover:bg-white/5"
|
|
:class="{'active': $store.global.activeTab === 'settings'}"
|
|
@click="$store.global.activeTab = 'settings'">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
<span x-text="$store.global.t('settings')">Settings</span>
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- Footer Info -->
|
|
<div class="mt-auto px-6 text-[10px] text-gray-700 font-mono">
|
|
<div class="flex justify-between">
|
|
<span>V 1.0.0</span>
|
|
<a href="https://github.com/badri-s2001/antigravity-claude-proxy" target="_blank"
|
|
class="hover:text-neon-purple transition-colors">GitHub</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content -->
|
|
<div class="flex-1 overflow-auto bg-space-950 relative custom-scrollbar" style="scrollbar-gutter: stable;">
|
|
|
|
<!-- Views Container -->
|
|
<!-- Dashboard -->
|
|
<div x-show="$store.global.activeTab === 'dashboard'" x-load-view="'dashboard'"
|
|
x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
|
|
class="w-full"></div>
|
|
|
|
<!-- Models -->
|
|
<div x-show="$store.global.activeTab === 'models'" x-load-view="'models'"
|
|
x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
|
|
class="w-full"></div>
|
|
|
|
<!-- Logs -->
|
|
<div x-show="$store.global.activeTab === 'logs'" x-load-view="'logs'" x-transition:enter="fade-enter-active"
|
|
x-transition:enter-start="fade-enter-from" class="w-full h-full"></div>
|
|
|
|
<!-- Accounts -->
|
|
<div x-show="$store.global.activeTab === 'accounts'" x-load-view="'accounts'"
|
|
x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
|
|
class="w-full"></div>
|
|
|
|
<!-- Settings -->
|
|
<div x-show="$store.global.activeTab === 'settings'" x-load-view="'settings'"
|
|
x-transition:enter="fade-enter-active" x-transition:enter-start="fade-enter-from"
|
|
class="w-full"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Account Modal -->
|
|
<dialog id="add_account_modal" class="modal backdrop-blur-sm">
|
|
<div class="modal-box max-w-md w-full bg-space-900 border border-space-border text-gray-300 shadow-[0_0_50px_rgba(0,0,0,0.5)] p-6">
|
|
<h3 class="font-bold text-lg text-white mb-4" x-text="$store.global.t('addAccount')">Add New Account</h3>
|
|
|
|
<div class="flex flex-col gap-4">
|
|
<p class="text-sm text-gray-400 leading-relaxed" x-text="$store.global.t('connectGoogleDesc')">Connect a Google
|
|
Workspace account to increase your API quota limit.
|
|
The account will be used to proxy Claude requests via Antigravity.</p>
|
|
|
|
<button class="btn btn-primary flex items-center justify-center gap-3 h-11" @click="addAccountWeb">
|
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
|
|
<path
|
|
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z">
|
|
</path>
|
|
<path
|
|
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z">
|
|
</path>
|
|
<path
|
|
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z">
|
|
</path>
|
|
<path
|
|
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z">
|
|
</path>
|
|
</svg>
|
|
<span x-text="$store.global.t('connectGoogle')">Connect Google Account</span>
|
|
</button>
|
|
|
|
<div class="text-center mt-2">
|
|
<p class="text-xs text-gray-500 mb-2" x-text="$store.global.t('or')">OR</p>
|
|
<details class="group">
|
|
<summary class="text-xs text-gray-400 hover:text-neon-cyan cursor-pointer transition-colors inline-flex items-center gap-1">
|
|
<svg class="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
|
</svg>
|
|
<span x-text="$store.global.t('useCliCommand')">Use CLI Command</span>
|
|
</summary>
|
|
<div class="mt-3 p-3 bg-black/50 rounded border border-space-border/30 font-mono text-xs text-gray-300">
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-gray-600">$</span>
|
|
<code>npm run accounts:add</code>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-action mt-6">
|
|
<form method="dialog">
|
|
<button class="btn btn-ghost hover:bg-white/10" x-text="$store.global.t('close')">Close</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<form method="dialog" class="modal-backdrop">
|
|
<button x-text="$store.global.t('close')">close</button>
|
|
</form>
|
|
</dialog>
|
|
|
|
<!-- OAuth Progress Modal -->
|
|
<dialog id="oauth_progress_modal" class="modal" :class="{ 'modal-open': $store.global.oauthProgress.active }">
|
|
<div class="modal-box bg-space-900 border border-neon-purple/50">
|
|
<h3 class="font-bold text-lg text-white flex items-center gap-2">
|
|
<svg class="w-6 h-6 text-neon-purple animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
<span x-text="$store.global.t('oauthWaiting')">Waiting for OAuth...</span>
|
|
</h3>
|
|
<p class="py-4 text-gray-400 text-sm" x-text="$store.global.t('oauthWaitingDesc')">
|
|
Please complete authentication in the popup window.
|
|
</p>
|
|
|
|
<!-- Progress Bar -->
|
|
<div class="w-full bg-space-800 rounded-full h-2 mb-4 overflow-hidden">
|
|
<div class="bg-neon-purple h-2 rounded-full transition-all duration-500"
|
|
:style="`width: ${($store.global.oauthProgress.current / $store.global.oauthProgress.max) * 100}%`">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Progress Text -->
|
|
<div class="flex justify-between text-xs text-gray-600 mb-4">
|
|
<span x-text="`${$store.global.oauthProgress.current} / ${$store.global.oauthProgress.max}s`"></span>
|
|
<span x-text="`${Math.round(($store.global.oauthProgress.current / $store.global.oauthProgress.max) * 100)}%`"></span>
|
|
</div>
|
|
|
|
<div class="modal-action">
|
|
<button class="btn btn-sm btn-ghost text-gray-400"
|
|
@click="$store.global.oauthProgress.cancel && $store.global.oauthProgress.cancel()"
|
|
x-text="$store.global.t('cancelOAuth')">
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</dialog>
|
|
|
|
<!-- Scripts - Loading Order Matters! -->
|
|
<!-- 1. Config & Utils (global helpers) -->
|
|
<script src="js/config/constants.js"></script>
|
|
<script src="js/utils.js"></script>
|
|
<script src="js/utils/error-handler.js"></script>
|
|
<script src="js/utils/validators.js"></script>
|
|
<script src="js/utils/model-config.js"></script>
|
|
<!-- 2. Alpine Stores (register alpine:init listeners) -->
|
|
<script src="js/store.js"></script>
|
|
<script src="js/data-store.js"></script>
|
|
<script src="js/settings-store.js"></script>
|
|
<!-- 3. Components (register to window.Components) -->
|
|
<!-- Dashboard modules (load before main dashboard) -->
|
|
<script src="js/components/dashboard/stats.js"></script>
|
|
<script src="js/components/dashboard/charts.js"></script>
|
|
<script src="js/components/dashboard/filters.js"></script>
|
|
<script src="js/components/dashboard.js"></script>
|
|
<script src="js/components/models.js"></script>
|
|
<script src="js/components/account-manager.js"></script>
|
|
<script src="js/components/claude-config.js"></script>
|
|
<script src="js/components/logs-viewer.js"></script>
|
|
<script src="js/components/server-config.js"></script>
|
|
<script src="js/components/model-manager.js"></script>
|
|
<!-- 4. App (registers Alpine components from window.Components) -->
|
|
<script src="app.js"></script>
|
|
</body>
|
|
|
|
</html> |