完善 SimpleLite 启停控制与服务状态页。

拆分 StopAll/Restart,运维代理透传确认头与用户信息,服务状态页可更稳地重启进程。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhaowei.huang
2026-07-26 11:32:30 +08:00
co-authored by Cursor
parent 8299c3dc5c
commit beb6563060
3 changed files with 123 additions and 30 deletions
@@ -43,6 +43,28 @@
<el-alert v-if="error" :title="error" type="warning" :closable="false" class="status-block" />
<div v-if="canControlSimpleLite" class="status-block sl-control">
<div class="sl-control-title">SimpleLite 操作</div>
<div class="sl-control-actions">
<el-button
type="warning"
:loading="actionLoading === 'restart'"
:disabled="!!actionLoading"
@click="onRestart">
重启 SimpleLite
</el-button>
<el-button
type="danger"
plain
:loading="actionLoading === 'stop'"
:disabled="!!actionLoading"
@click="onStop">
关闭 SimpleLite
</el-button>
</div>
<p class="sl-control-hint">将终止本机全部 SimpleLite 进程重启后按当前启动模式重新拉起</p>
</div>
<div class="status-actions">
<el-button :loading="loading" @click="refresh">刷新</el-button>
<el-button @click="back">返回</el-button>
@@ -54,34 +76,29 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import http from '@/api/http'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getHealth,
getSimpleLiteDiagnostics,
restartSimpleLite,
resolveRestartLaunchMode,
stopSimpleLite,
type HealthInfo,
type SimpleLiteDiagnostics
} from '@/api/health'
import { useAuthStore } from '@/stores/auth'
interface HealthInfo {
status: string
startTime: string
uptimeSec: number
}
interface SimpleLiteDiagnostics {
enabled: boolean
isRunning: boolean
lastLaunchMode?: string | null
projectionPort: number
projectionPortReachable: boolean
gotoSiteApiAvailable?: boolean | null
executableExists: boolean
deployHint?: string | null
}
const router = useRouter()
const auth = useAuthStore()
const loading = ref(false)
const actionLoading = ref<'stop' | 'restart' | null>(null)
const health = ref<HealthInfo | null>(null)
const sl = ref<SimpleLiteDiagnostics | null>(null)
const error = ref('')
const canControlSimpleLite = computed(() => auth.token && auth.scope === 'Platform')
let timer: number | undefined
const serverStartTime = computed(() =>
@@ -117,11 +134,10 @@ async function refresh() {
try {
// /status 是 public 页:未登录只拉匿名 /health,不调需登录的 simplelite 诊断
// (401 会触发全局拦截器强制跳转登录页)。
const requests: [Promise<{ data: HealthInfo }>, Promise<{ data: SimpleLiteDiagnostics }> | null] = [
http.get<HealthInfo>('/health'),
auth.token ? http.get<SimpleLiteDiagnostics>('/health/simplelite') : null
]
const [h, d] = await Promise.allSettled([requests[0], requests[1] ?? Promise.reject(new Error('skipped'))])
const [h, d] = await Promise.allSettled([
getHealth(),
auth.token ? getSimpleLiteDiagnostics() : Promise.reject(new Error('skipped'))
])
health.value = h.status === 'fulfilled' ? h.value.data : null
sl.value = d.status === 'fulfilled' ? d.value.data : null
if (h.status === 'rejected') error.value = '无法连接 MiGu.Server/api/health'
@@ -131,6 +147,59 @@ async function refresh() {
}
}
async function onStop() {
try {
await ElMessageBox.confirm(
'将终止本机全部 SimpleLite 进程,地图监控与 /api/sl/* 功能将不可用,直到重新登录或手动重启。',
'关闭 SimpleLite',
{ type: 'warning', confirmButtonText: '关闭', cancelButtonText: '取消' }
)
} catch {
return
}
actionLoading.value = 'stop'
try {
const { data } = await stopSimpleLite()
sl.value = data.diagnostics
ElMessage.success(data.killed > 0 ? `已关闭 ${data.killed} 个 SimpleLite 进程` : '当前没有运行中的 SimpleLite 进程')
} catch (e) {
ElMessage.error(e instanceof Error ? e.message : '关闭 SimpleLite 失败')
} finally {
actionLoading.value = null
}
}
async function onRestart() {
const launchMode = resolveRestartLaunchMode(sl.value?.lastLaunchMode, auth.runMode)
const modeLabel = launchMode === 'WebOnly' ? '仅 Web' : '本地 + Web'
try {
await ElMessageBox.confirm(
`将关闭本机全部 SimpleLite 并按「${modeLabel}」模式重新拉起,期间 /api/sl/* 可能短暂不可用。`,
'重启 SimpleLite',
{ type: 'warning', confirmButtonText: '重启', cancelButtonText: '取消' }
)
} catch {
return
}
actionLoading.value = 'restart'
try {
const { data } = await restartSimpleLite(launchMode)
sl.value = data.diagnostics
const r = data.restart
if (r.warning) ElMessage.warning(r.warning)
else if (r.started) ElMessage.success('SimpleLite 已重新拉起')
else ElMessage.error(`重启未完成:${r.status}`)
} catch (e) {
ElMessage.error(e instanceof Error ? e.message : '重启 SimpleLite 失败')
} finally {
actionLoading.value = null
void refresh()
}
}
onMounted(() => {
void refresh()
// 10s 轮询:服务端对 goto-site 探测有 60s 缓存,此频率不会对 SimpleLite 产生压力。
@@ -148,5 +217,8 @@ function back() { router.back() }
.status-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.status-card { width: 720px; }
.status-block { margin-top: 16px; }
.sl-control-title { font-weight: 600; margin-bottom: 8px; }
.sl-control-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.sl-control-hint { margin: 8px 0 0; font-size: 12px; color: var(--el-text-color-secondary); }
.status-actions { display: flex; gap: 8px; margin-top: 16px; justify-content: flex-end; }
</style>