mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-20 00:47:57 +00:00
fix: worker 进程并发 double-close panic + 状态页连接语义重构 + 集群页骨架
- process: spawn 不再重复起 supervise goroutine(Restart 触发旧 supervise close doneCh 后再 close → panic);新增 closeOnce/supervising 守卫;Start 首次起 supervise,重启循环在同一 goroutine 内 - httpapi: status 增加 connState(connected/connecting/failed/not_started/disabled),由 worker 日志探测真实 frps 连接(不再把本地 worker 状态误当远程 frps 状态) - StatusView: 远程卡片主状态改为连接状态徽标;启停按钮标注为控制本地 frpc worker;本地转发区保留 worker 状态显示 - 新增 ClusterView 集群页骨架(M3 LB+健康检查载体)+ App.vue 导航接入 - render: bandwidthLimit 从 proxy 顶层移入 transport 段(frpc 0.61 正确 schema,修复真实连不上的 bug)
This commit is contained in:
@ -13,11 +13,15 @@
|
||||
<button class="nav-btn" :class="{ active: view === 'settings' }" @click="view = 'settings'">
|
||||
设置
|
||||
</button>
|
||||
<button class="nav-btn" :class="{ active: view === 'cluster' }" @click="view = 'cluster'">
|
||||
集群
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
<main class="content">
|
||||
<CanvasView v-if="view === 'canvas'" />
|
||||
<SettingsView v-else-if="view === 'settings'" />
|
||||
<ClusterView v-else-if="view === 'cluster'" />
|
||||
<StatusView v-else />
|
||||
</main>
|
||||
</div>
|
||||
@ -28,8 +32,9 @@ import { ref } from 'vue'
|
||||
import CanvasView from './views/CanvasView.vue'
|
||||
import SettingsView from './views/SettingsView.vue'
|
||||
import StatusView from './views/StatusView.vue'
|
||||
import ClusterView from './views/ClusterView.vue'
|
||||
|
||||
const view = ref<'canvas' | 'settings' | 'status'>('status')
|
||||
const view = ref<'canvas' | 'settings' | 'status' | 'cluster'>('status')
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@ -87,6 +87,8 @@ export interface StatusProfile {
|
||||
process: ProcessStatus;
|
||||
hasProcess: boolean;
|
||||
forwards: { service: string; remotePort: number; localPort?: number }[];
|
||||
// connState: derived frps connection state (we do not control remote frps)
|
||||
connState?: string; // connected | connecting | failed | not_started | disabled
|
||||
}
|
||||
|
||||
export interface StatusResp {
|
||||
|
||||
45
web/src/views/ClusterView.vue
Normal file
45
web/src/views/ClusterView.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="cluster-page">
|
||||
<div class="cluster-top">
|
||||
<h2 class="page-title">集群</h2>
|
||||
<span class="page-sub">
|
||||
负载均衡组(group)与健康检查(health check)将在这里展示 — M3 功能,占位
|
||||
</span>
|
||||
</div>
|
||||
<div class="cluster-body">
|
||||
<el-empty description="集群功能开发中(M3:负载均衡与健康检查)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// M3 集群页骨架:group / groupKey 多后端负载均衡 + 健康检查(tcp/http)将在此呈现。
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cluster-page {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.cluster-top {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.page-sub {
|
||||
color: $color-text-muted;
|
||||
font-size: 13px;
|
||||
}
|
||||
.cluster-body {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
@ -20,12 +20,12 @@
|
||||
>
|
||||
<div class="ns-head">
|
||||
<span class="ns-name">{{ p.name }}</span>
|
||||
<span class="ns-badge" :class="{ ok: workerHealthy(p.process.state) }">
|
||||
● {{ workerStateLabel(p.process.state) }}
|
||||
<span class="ns-badge" :class="connBadgeClass(p.connState)">
|
||||
● {{ connStateLabel(p.connState) }}
|
||||
</span>
|
||||
<span class="ns-actions">
|
||||
<button v-if="p.process.state === 'running'" class="mini-btn" @click="stopRemote(p.name)">停止</button>
|
||||
<button v-else class="mini-btn" @click="startRemote(p.name)">启动</button>
|
||||
<span class="ns-actions" title="控制本地 frpc worker 进程(不管理远程 frps)">
|
||||
<button v-if="p.process.state === 'running'" class="mini-btn" @click="stopRemote(p.name)">停本地worker</button>
|
||||
<button v-else class="mini-btn" @click="startRemote(p.name)">启本地worker</button>
|
||||
<button class="mini-btn" @click="openEdit(p)">编辑</button>
|
||||
<button class="mini-btn danger" @click="removeRemote(p.name)">删除</button>
|
||||
</span>
|
||||
@ -63,7 +63,7 @@
|
||||
<span class="ls-remote">{{ t.remote }}</span>
|
||||
<span class="ls-port">:{{ t.remotePort }}</span>
|
||||
<span class="ls-state" :class="{ ok: workerHealthy(t.workerState) }">
|
||||
{{ workerStateLabel(t.workerState) }}
|
||||
{{ localWorkerLabel(t.workerState) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="!ls.targets.length" class="ls-none">未转发</div>
|
||||
@ -204,10 +204,47 @@ const loadStatus = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const workerStateLabel = (s?: string) => {
|
||||
// connState refers to the frps connection from the local frpc worker.
|
||||
// We never control the remote frps itself.
|
||||
const connStateLabel = (s?: string) => {
|
||||
switch (s) {
|
||||
case 'connected':
|
||||
return '已连接'
|
||||
case 'connecting':
|
||||
return '连接中'
|
||||
case 'failed':
|
||||
return '连接失败'
|
||||
case 'disabled':
|
||||
return '已停用'
|
||||
case 'not_started':
|
||||
default:
|
||||
return '未启动'
|
||||
}
|
||||
}
|
||||
|
||||
const connBadgeClass = (s?: string) => {
|
||||
switch (s) {
|
||||
case 'connected':
|
||||
return 'ok'
|
||||
case 'connecting':
|
||||
return 'pending'
|
||||
case 'failed':
|
||||
return 'err'
|
||||
case 'disabled':
|
||||
case 'not_started':
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const workerHealthy = (s?: string) => s === 'running'
|
||||
|
||||
// localWorkerLabel: state of the LOCAL frpc worker driving this target
|
||||
// (we do not control the remote frps).
|
||||
const localWorkerLabel = (s?: string) => {
|
||||
switch (s) {
|
||||
case 'running':
|
||||
return '通畅'
|
||||
return '运行中'
|
||||
case 'starting':
|
||||
return '启动中'
|
||||
case 'restarting':
|
||||
@ -219,8 +256,6 @@ const workerStateLabel = (s?: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const workerHealthy = (s?: string) => s === 'running'
|
||||
|
||||
// remoteVhostPort returns the frps vhostHTTPPort for a remote (0 if unset).
|
||||
const remoteVhostPort = (name: string): number =>
|
||||
status.value?.remotes.find((r) => r.name === name)?.vhostHttpPort || 0
|
||||
@ -397,6 +432,16 @@ onBeforeUnmount(() => {
|
||||
background: #f0f9eb;
|
||||
color: $color-success;
|
||||
}
|
||||
|
||||
&.pending {
|
||||
background: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
&.err {
|
||||
background: #fef0f0;
|
||||
color: $color-danger;
|
||||
}
|
||||
}
|
||||
|
||||
.ns-meta {
|
||||
|
||||
Reference in New Issue
Block a user