完善 SimpleLite 启停控制与服务状态页。
拆分 StopAll/Restart,运维代理透传确认头与用户信息,服务状态页可更稳地重启进程。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user