App.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. <template>
  2. <div v-if="!authenticated" class="login-page">
  3. <div class="login-panel">
  4. <div class="login-brand">Windows 监控</div>
  5. <h1>输入访问 Token</h1>
  6. <p>用于进入局域网自动化控制台。当前后端未设置 Token 时,可以留空直接进入。</p>
  7. <el-input
  8. v-model="loginToken"
  9. show-password
  10. size="large"
  11. placeholder="远程执行 Token,可留空"
  12. @keyup.enter="login"
  13. />
  14. <el-button type="primary" size="large" :loading="loggingIn" @click="login">进入控制台</el-button>
  15. </div>
  16. </div>
  17. <div v-else class="app-shell">
  18. <aside v-if="activeView !== 'automation-workflow-editor'" class="sidebar">
  19. <div class="brand">Windows 监控</div>
  20. <el-menu :default-active="activeView" background-color="#1f2937" text-color="#d1d5db" active-text-color="#fff" @select="activeView = $event">
  21. <el-menu-item index="dashboard">仪表盘</el-menu-item>
  22. <el-menu-item index="pending">待确认中心</el-menu-item>
  23. <el-menu-item index="services">Windows 服务</el-menu-item>
  24. <el-menu-item index="processes">Windows 进程</el-menu-item>
  25. <el-menu-item index="tags">标签管理</el-menu-item>
  26. <el-menu-item index="settings">系统设置</el-menu-item>
  27. <el-sub-menu index="ai">
  28. <template #title>AI 配置</template>
  29. <el-menu-item index="ai-providers">AI 服务商管理</el-menu-item>
  30. <el-menu-item index="ai-models">AI 模型管理</el-menu-item>
  31. <el-menu-item index="ai-test">AI 服务测试</el-menu-item>
  32. </el-sub-menu>
  33. <el-sub-menu index="automation">
  34. <template #title>自动化</template>
  35. <el-menu-item index="automation-actions">自动化操作</el-menu-item>
  36. <el-menu-item index="automation-workflows">自动化工作流</el-menu-item>
  37. <el-menu-item index="automation-tasks">自动化任务记录</el-menu-item>
  38. <el-menu-item index="automation-screens">已识别界面</el-menu-item>
  39. <el-menu-item index="automation-errors">自动化错误记录</el-menu-item>
  40. </el-sub-menu>
  41. <el-menu-item index="sensors">传感器信息</el-menu-item>
  42. <el-menu-item index="smart">硬盘 SMART</el-menu-item>
  43. <el-menu-item index="scans">扫描历史</el-menu-item>
  44. </el-menu>
  45. </aside>
  46. <main class="main" :class="{ 'main-fullscreen': activeView === 'automation-workflow-editor' }">
  47. <div v-if="activeView !== 'automation-workflow-editor'" class="topbar">
  48. <div>
  49. <div class="page-title">{{ title }}</div>
  50. <div class="muted">{{ subtitle }}</div>
  51. </div>
  52. <div class="filters">
  53. <el-button type="primary" :loading="scanning" @click="runScan">执行扫描</el-button>
  54. <el-button @click="logout">退出登录</el-button>
  55. </div>
  56. </div>
  57. <section v-if="activeView === 'dashboard'">
  58. <div class="metrics">
  59. <div class="metric"><div class="metric-label">服务总数</div><div class="metric-value">{{ dashboard.service_total || 0 }}</div></div>
  60. <div class="metric"><div class="metric-label">进程总数</div><div class="metric-value">{{ dashboard.process_total || 0 }}</div></div>
  61. <div class="metric"><div class="metric-label">待确认服务</div><div class="metric-value">{{ dashboard.pending_services || 0 }}</div></div>
  62. <div class="metric"><div class="metric-label">待确认进程</div><div class="metric-value">{{ dashboard.pending_processes || 0 }}</div></div>
  63. <div class="metric"><div class="metric-label">本次未出现</div><div class="metric-value">{{ (dashboard.missing_services || 0) + (dashboard.missing_processes || 0) }}</div></div>
  64. </div>
  65. <div class="panel">
  66. <el-descriptions title="最近扫描" :column="2" border>
  67. <el-descriptions-item label="状态">{{ dashboard.latest_scan?.status || '-' }}</el-descriptions-item>
  68. <el-descriptions-item label="开始时间">{{ dashboard.latest_scan?.started_at || '-' }}</el-descriptions-item>
  69. <el-descriptions-item label="服务数量">{{ dashboard.latest_scan?.services_found ?? '-' }}</el-descriptions-item>
  70. <el-descriptions-item label="进程数量">{{ dashboard.latest_scan?.processes_found ?? '-' }}</el-descriptions-item>
  71. </el-descriptions>
  72. </div>
  73. </section>
  74. <section v-if="activeView === 'services'">
  75. <ItemTable type="service" ref="serviceTable" />
  76. </section>
  77. <section v-if="activeView === 'processes'">
  78. <ItemTable type="process" ref="processTable" />
  79. </section>
  80. <section v-if="activeView === 'pending'">
  81. <el-tabs v-model="pendingTab">
  82. <el-tab-pane label="待确认服务" name="services">
  83. <ItemTable type="service" confirm-status="PENDING" ref="pendingServiceTable" />
  84. </el-tab-pane>
  85. <el-tab-pane label="待确认进程" name="processes">
  86. <ItemTable type="process" confirm-status="PENDING" ref="pendingProcessTable" />
  87. </el-tab-pane>
  88. </el-tabs>
  89. </section>
  90. <section v-if="activeView === 'tags'">
  91. <TagManager />
  92. </section>
  93. <section v-if="activeView === 'settings'">
  94. <SystemSettingsView ref="systemSettingsView" />
  95. </section>
  96. <section v-if="activeView === 'ai-providers'">
  97. <AiProviderManager ref="aiProviderManager" />
  98. </section>
  99. <section v-if="activeView === 'ai-models'">
  100. <AiModelManager ref="aiModelManager" />
  101. </section>
  102. <section v-if="activeView === 'ai-test'">
  103. <AiTestView ref="aiTestView" />
  104. </section>
  105. <section v-if="activeView === 'automation-actions'">
  106. <AutomationActionView ref="automationActionView" />
  107. </section>
  108. <section v-if="activeView === 'automation-workflows'">
  109. <AutomationWorkflowView
  110. ref="automationWorkflowView"
  111. @create="openWorkflowEditor(null)"
  112. @edit="openWorkflowEditor"
  113. @task-created="openTaskHistory"
  114. />
  115. </section>
  116. <section v-if="activeView === 'automation-workflow-editor'">
  117. <AutomationWorkflowEditorPage
  118. :key="workflowEditorKey"
  119. :workflow-id="editingWorkflowId"
  120. @back="closeWorkflowEditor"
  121. @saved="editingWorkflowId = $event"
  122. @task-created="openTaskHistory"
  123. />
  124. </section>
  125. <section v-if="activeView === 'automation-tasks'">
  126. <AutomationWorkflowTasksView ref="automationWorkflowTasksView" />
  127. </section>
  128. <section v-if="activeView === 'automation-screens'">
  129. <AutomationScreensView ref="automationScreensView" />
  130. </section>
  131. <section v-if="activeView === 'automation-errors'">
  132. <AutomationErrorsView ref="automationErrorsView" />
  133. </section>
  134. <section v-if="activeView === 'sensors'">
  135. <SensorView />
  136. </section>
  137. <section v-if="activeView === 'smart'">
  138. <SmartView />
  139. </section>
  140. <section v-if="activeView === 'scans'" class="panel">
  141. <el-table :data="scans.items" border stripe>
  142. <el-table-column prop="id" label="ID" width="80" />
  143. <el-table-column prop="status" label="状态" width="110" />
  144. <el-table-column prop="started_at" label="开始时间" min-width="180" />
  145. <el-table-column prop="finished_at" label="完成时间" min-width="180" />
  146. <el-table-column prop="services_found" label="服务" width="90" />
  147. <el-table-column prop="processes_found" label="进程" width="90" />
  148. <el-table-column prop="new_services" label="新增服务" width="100" />
  149. <el-table-column prop="new_processes" label="新增进程" width="100" />
  150. <el-table-column prop="error_message" label="错误" min-width="180" />
  151. </el-table>
  152. </section>
  153. </main>
  154. </div>
  155. </template>
  156. <script setup>
  157. import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
  158. import { ElMessage } from 'element-plus'
  159. import { api, clearAutomationAuth, hasAutomationAuth, setAutomationToken } from './api'
  160. import AiModelManager from './components/AiModelManager.vue'
  161. import AiProviderManager from './components/AiProviderManager.vue'
  162. import AiTestView from './components/AiTestView.vue'
  163. import AutomationActionView from './components/AutomationActionView.vue'
  164. import AutomationErrorsView from './components/AutomationErrorsView.vue'
  165. import AutomationWorkflowEditorPage from './components/AutomationWorkflowEditorPage.vue'
  166. import AutomationScreensView from './components/AutomationScreensView.vue'
  167. import AutomationWorkflowView from './components/AutomationWorkflowView.vue'
  168. import AutomationWorkflowTasksView from './components/AutomationWorkflowTasksView.vue'
  169. import ItemTable from './components/ItemTable.vue'
  170. import SensorView from './components/SensorView.vue'
  171. import SmartView from './components/SmartView.vue'
  172. import SystemSettingsView from './components/SystemSettingsView.vue'
  173. import TagManager from './components/TagManager.vue'
  174. const activeView = ref('dashboard')
  175. const pendingTab = ref('services')
  176. const dashboard = ref({})
  177. const scans = ref({ items: [] })
  178. const scanning = ref(false)
  179. const serviceTable = ref(null)
  180. const processTable = ref(null)
  181. const pendingServiceTable = ref(null)
  182. const pendingProcessTable = ref(null)
  183. const aiProviderManager = ref(null)
  184. const aiModelManager = ref(null)
  185. const aiTestView = ref(null)
  186. const systemSettingsView = ref(null)
  187. const automationActionView = ref(null)
  188. const automationWorkflowView = ref(null)
  189. const automationWorkflowTasksView = ref(null)
  190. const automationScreensView = ref(null)
  191. const automationErrorsView = ref(null)
  192. const editingWorkflowId = ref(null)
  193. const workflowEditorKey = ref(0)
  194. const authenticated = ref(hasAutomationAuth())
  195. const loginToken = ref('')
  196. const loggingIn = ref(false)
  197. const title = computed(() => ({
  198. dashboard: '仪表盘',
  199. pending: '待确认中心',
  200. services: 'Windows 服务',
  201. processes: 'Windows 进程',
  202. tags: '标签管理',
  203. settings: '系统设置',
  204. 'ai-providers': 'AI 服务商管理',
  205. 'ai-models': 'AI 模型管理',
  206. 'ai-test': 'AI 服务测试',
  207. 'automation-actions': '自动化操作',
  208. 'automation-workflows': '自动化工作流',
  209. 'automation-tasks': '自动化任务记录',
  210. 'automation-workflow-editor': '工作流编辑器',
  211. 'automation-screens': '已识别界面',
  212. 'automation-errors': '自动化错误记录',
  213. sensors: '传感器信息',
  214. smart: '硬盘 SMART',
  215. scans: '扫描历史',
  216. })[activeView.value])
  217. const subtitle = computed(() => {
  218. if (String(activeView.value).startsWith('automation')) {
  219. return '通过 AI 视觉识别 Windows 界面,执行自动化动作,并沉淀可复用工作流。'
  220. }
  221. return '采集 Windows 服务和进程,确认可信状态,并整理给 AI 分析的数据。'
  222. })
  223. async function loadDashboard() {
  224. const { data } = await api.get('/api/dashboard')
  225. dashboard.value = data
  226. }
  227. async function loadScans() {
  228. const { data } = await api.get('/api/scans')
  229. scans.value = data
  230. }
  231. async function refreshCurrent() {
  232. if (!authenticated.value) return
  233. await loadDashboard()
  234. if (activeView.value === 'scans') await loadScans()
  235. await nextTick()
  236. serviceTable.value?.load()
  237. processTable.value?.load()
  238. pendingServiceTable.value?.load()
  239. pendingProcessTable.value?.load()
  240. aiProviderManager.value?.load()
  241. aiModelManager.value?.refreshAll()
  242. aiTestView.value?.loadOptions()
  243. systemSettingsView.value?.load()
  244. automationActionView.value?.loadOptions()
  245. automationWorkflowView.value?.load()
  246. automationWorkflowTasksView.value?.load()
  247. automationScreensView.value?.load()
  248. automationErrorsView.value?.load()
  249. }
  250. async function login() {
  251. loggingIn.value = true
  252. try {
  253. setAutomationToken(loginToken.value)
  254. authenticated.value = true
  255. await refreshCurrent()
  256. } catch (error) {
  257. clearAutomationAuth()
  258. authenticated.value = false
  259. ElMessage.error(error.response?.data?.detail || 'Token 校验失败')
  260. } finally {
  261. loggingIn.value = false
  262. }
  263. }
  264. function logout() {
  265. clearAutomationAuth()
  266. authenticated.value = false
  267. loginToken.value = ''
  268. }
  269. function openWorkflowEditor(id) {
  270. editingWorkflowId.value = id
  271. workflowEditorKey.value += 1
  272. activeView.value = 'automation-workflow-editor'
  273. }
  274. async function closeWorkflowEditor() {
  275. activeView.value = 'automation-workflows'
  276. await nextTick()
  277. await automationWorkflowView.value?.load()
  278. }
  279. async function openTaskHistory(task) {
  280. activeView.value = 'automation-tasks'
  281. await nextTick()
  282. await automationWorkflowTasksView.value?.load()
  283. if (task?.id) await automationWorkflowTasksView.value?.showDetail(task)
  284. }
  285. async function runScan() {
  286. scanning.value = true
  287. try {
  288. const { data } = await api.post('/api/scans/run')
  289. ElMessage.success(`扫描完成:服务 ${data.services_found},进程 ${data.processes_found}`)
  290. await refreshCurrent()
  291. } catch (error) {
  292. ElMessage.error(error.response?.data?.detail || '扫描失败')
  293. } finally {
  294. scanning.value = false
  295. }
  296. }
  297. watch(activeView, async (view) => {
  298. if (!authenticated.value) return
  299. if (view === 'dashboard') await loadDashboard()
  300. if (view === 'scans') await loadScans()
  301. if (view === 'settings') await systemSettingsView.value?.load()
  302. if (view === 'automation-workflows') await automationWorkflowView.value?.load()
  303. if (view === 'automation-tasks') await automationWorkflowTasksView.value?.load()
  304. if (view === 'automation-screens') await automationScreensView.value?.load()
  305. if (view === 'automation-errors') await automationErrorsView.value?.load()
  306. })
  307. function handleAuthRequired() {
  308. authenticated.value = false
  309. loginToken.value = ''
  310. ElMessage.warning('访问 Token 无效或已失效,请重新输入')
  311. }
  312. onMounted(() => {
  313. window.addEventListener('automation-auth-required', handleAuthRequired)
  314. if (authenticated.value) refreshCurrent().catch(() => {})
  315. })
  316. onUnmounted(() => {
  317. window.removeEventListener('automation-auth-required', handleAuthRequired)
  318. })
  319. </script>