AutoTeam-F / web /src /App.vue
ZRainbow's picture
feat: AutoTeam 大版本升级:安全轮换、并行、IPv6、指纹加固与前端重构
1f4cfcb
Raw
History Blame Contribute Delete
7.85 kB
<template>
<!-- 初始配置页 -->
<SetupPage v-if="needSetup" @configured="onSetupDone" />
<!-- 登录页 -->
<div v-else-if="!authenticated" class="min-h-screen flex items-center justify-center px-4">
<div class="glass rounded-lg p-8 w-full max-w-sm">
<div>
<div class="text-[10px] uppercase tracking-[0.3em] text-ink-400 mb-1">Account Operations</div>
<h1 class="text-2xl font-extrabold text-ink-950 mb-1 tracking-tight">AutoTeam</h1>
<p class="text-sm text-ink-500 mb-6">输入管理 API Key 进入控制台</p>
<div v-if="authError"
class="mb-4 px-3 py-2.5 rounded-lg text-sm bg-rose-50 text-rose-700 border border-rose-200">
{{ authError }}
</div>
<input
v-model.trim="inputKey"
type="password"
placeholder="API Key"
@keyup.enter="doLogin"
class="w-full px-3.5 py-2.5 bg-surface border border-hairline rounded-lg text-sm text-ink-950
font-mono placeholder:text-ink-400 focus-ring mb-4 transition" />
<AtButton variant="primary" class="w-full" :loading="authLoading" :disabled="!inputKey" @click="doLogin">
{{ authLoading ? '验证中…' : '进入控制台' }}
</AtButton>
</div>
</div>
</div>
<!-- 主面板 -->
<div v-else class="flex min-h-screen">
<!-- 侧边栏 -->
<Sidebar :active="currentPage" :loading="loading" :auth-required="authRequired"
@navigate="currentPage = $event" @refresh="refresh" @logout="doLogout" />
<!-- 主内容区 -->
<div class="flex-1 p-4 md:p-6 overflow-y-auto pb-20 md:pb-6 max-w-screen-2xl mx-auto w-full">
<!-- 任务执行中提示 -->
<div v-if="busyTask"
class="flex items-center gap-2.5 text-sm text-amber-800 mb-4 px-3 py-2 rounded-lg border border-amber-200 bg-amber-50 w-fit animate-rise">
<span class="animate-spin inline-block w-3.5 h-3.5 border-2 border-amber-700 border-t-transparent rounded-full"></span>
<span class="font-medium">
{{ busyTask.command === 'admin-login'
? '管理员登录中...'
: busyTask.command === 'main-codex-sync'
? '主号 Codex 同步中...'
: `${busyTask.command} 执行中...` }}
</span>
</div>
<!-- 页面内容 — round-12 F1 加 Vue Transition page 过渡 -->
<Transition name="page" mode="out-in">
<Dashboard v-if="currentPage === 'dashboard'" key="dashboard"
:status="status" :loading="loading" :running-task="busyTask" :admin-status="adminStatus"
:register-failures="registerFailures" :register-failures-loading="registerFailuresLoading"
:register-failures-unavailable="registerFailuresUnavailable"
:master-health="masterHealth" @refresh="onActionRefresh" @reload-master-health="reloadMasterHealth" />
<TeamMembers v-else-if="currentPage === 'team'" key="team" />
<PoolPage v-else-if="currentPage === 'pool'" key="pool"
:running-task="busyTask" :admin-status="adminStatus" :master-health="masterHealth" :status="status"
:rotate-stream="rotateStream"
@task-started="onTaskStarted" @refresh="onActionRefresh" @reload-master-health="reloadMasterHealth" />
<SyncPage v-else-if="currentPage === 'sync'" key="sync"
:running-task="busyTask" :admin-status="adminStatus"
:rotate-stream="rotateStream"
@task-started="onTaskStarted" @refresh="onActionRefresh" />
<OAuthPage v-else-if="currentPage === 'oauth'" key="oauth"
:manual-account-status="manualAccountStatus" @refresh="onActionRefresh" @progress="onAdminProgress" />
<TaskHistoryPage v-else-if="currentPage === 'tasks'" key="tasks"
:tasks="tasks" />
<LogViewer v-else-if="currentPage === 'logs'" key="logs" />
<Settings v-else-if="currentPage === 'settings'" key="settings"
:admin-status="adminStatus" :codex-status="codexStatus"
:master-health="masterHealth" :status="status"
@refresh="onActionRefresh" @admin-progress="onAdminProgress" @reload-master-health="reloadMasterHealth" />
</Transition>
</div>
<ToastHost />
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { api, setApiKey, clearApiKey } from './api.js'
import { useAppState } from './composables/useAppState.js'
import SetupPage from './components/SetupPage.vue'
import Sidebar from './components/Sidebar.vue'
import Dashboard from './components/Dashboard.vue'
import TeamMembers from './components/TeamMembers.vue'
import PoolPage from './components/PoolPage.vue'
import SyncPage from './components/SyncPage.vue'
import TaskHistoryPage from './components/TaskHistoryPage.vue'
import LogViewer from './components/LogViewer.vue'
import OAuthPage from './components/OAuthPage.vue'
import Settings from './components/Settings.vue'
import ToastHost from './components/ToastHost.vue'
import AtButton from './components/AtButton.vue'
const needSetup = ref(false)
const authenticated = ref(false)
const authRequired = ref(false)
const authLoading = ref(false)
const authError = ref('')
const inputKey = ref('')
const currentPage = ref('dashboard')
const appState = useAppState(authenticated)
const {
status,
adminStatus,
codexStatus,
manualAccountStatus,
registerFailures,
registerFailuresLoading,
registerFailuresUnavailable,
tasks,
loading,
busyTask,
rotateStream,
} = appState
// Round 9 — master-health 提到 App 级,4 个页面共享同一份(避免每页各刷各的)
const masterHealth = ref(null)
const masterHealthLoading = ref(false)
async function checkAuth() {
try {
const result = await api.checkAuth()
authenticated.value = result.authenticated
authRequired.value = result.auth_required
return result.authenticated
} catch (e) {
if (e.status === 401) {
authenticated.value = false
authRequired.value = true
return false
}
authenticated.value = true
authRequired.value = false
return true
}
}
async function doLogin() {
authError.value = ''
authLoading.value = true
try {
setApiKey(inputKey.value)
const ok = await checkAuth()
if (!ok) {
clearApiKey()
authError.value = 'API Key 无效'
} else {
inputKey.value = ''
refresh()
}
} catch (e) {
clearApiKey()
authError.value = e.message
} finally {
authLoading.value = false
}
}
function doLogout() {
clearApiKey()
authenticated.value = false
}
async function refresh() {
await appState.refresh()
}
async function reloadMasterHealth(forceRefresh = false) {
if (!adminStatus.value?.configured) return
masterHealthLoading.value = true
try {
masterHealth.value = await api.getMasterHealth(!!forceRefresh)
} catch (e) {
masterHealth.value = null
} finally {
masterHealthLoading.value = false
}
}
function onTaskStarted() {
appState.notifyActionStarted()
}
function onAdminProgress() {
appState.notifyActionStarted()
}
function onActionRefresh() {
appState.notifyActionStarted()
}
async function checkSetup() {
try {
const result = await api.getSetupStatus()
return result.configured
} catch {
return true // 接口不存在说明是旧版本,跳过
}
}
function onSetupDone() {
needSetup.value = false
checkAuth().then(ok => {
if (ok) {
refresh()
}
})
}
// admin 配置完成后自动拉一次 master-health
watch(
() => adminStatus.value?.configured,
(configured) => {
if (configured && !masterHealth.value) reloadMasterHealth(false)
}
)
onMounted(async () => {
const setupOk = await checkSetup()
if (!setupOk) {
needSetup.value = true
return
}
const ok = await checkAuth()
if (ok) {
refresh()
}
})
</script>