feat(proxy): 实现代理节点批量升级回滚、隧道重定向跟随及远程配置管理

核心功能:
- 新增代理节点批量升级回滚工作流,支持分批升级、健康探针、跳过/重试/取消等操作
- proxy 隧道流处理器支持 HTTP 重定向跟随(最多 10 跳),区分 307/308 可重播与不可重播请求体
- proxy 协议新增 follow_redirects / http1_only 字段,网关侧同步支持
- 新增代理节点远端配置变更接口(名称、允许端口、调度状态、升级目标等)
- 新增代理节点注册/反注册/心跳的 Admin API,及节点过期清理维护任务
- gateway 隧道 owner-relay 支持流式代理大请求体,新增 5 MiB 默认限制
- 新增 ProxyNodeRegistrationMutation / ProxyNodeRemoteConfigMutation 数据类型
- proxy 配置新增重定向重播预算、心跳间隔等参数,TUI 安装向导同步更新
- 前端 ProxyNodes 页面新增批量升级操作面板及滚动进度展示
This commit is contained in:
fawney19
2026-04-12 16:02:38 +08:00
parent 7c5bb7f383
commit 9703840a36
83 changed files with 11832 additions and 1520 deletions
@@ -1,4 +1,6 @@
use crate::handlers::admin::request::AdminAppState;
use crate::handlers::shared::unix_secs_to_rfc3339;
use crate::maintenance::{inspect_proxy_upgrade_rollout, ProxyUpgradeRolloutStatus};
use crate::GatewayError;
use aether_admin::system::{
build_admin_proxy_node_event_payload, build_admin_proxy_node_events_payload_response,
@@ -9,6 +11,20 @@ use aether_admin::system::{
use axum::{body::Body, response::Response};
impl<'a> AdminAppState<'a> {
pub(crate) async fn register_proxy_node(
&self,
mutation: &aether_data::repository::proxy_nodes::ProxyNodeRegistrationMutation,
) -> Result<Option<aether_data::repository::proxy_nodes::StoredProxyNode>, GatewayError> {
self.app.register_proxy_node(mutation).await
}
pub(crate) async fn apply_proxy_node_heartbeat(
&self,
mutation: &aether_data::repository::proxy_nodes::ProxyNodeHeartbeatMutation,
) -> Result<Option<aether_data::repository::proxy_nodes::StoredProxyNode>, GatewayError> {
self.app.apply_proxy_node_heartbeat(mutation).await
}
pub(crate) async fn build_admin_proxy_nodes_list_response(
&self,
skip: usize,
@@ -42,8 +58,12 @@ impl<'a> AdminAppState<'a> {
.take(limit)
.map(|node| build_admin_proxy_node_payload(&node))
.collect::<Vec<_>>();
let rollout = inspect_proxy_upgrade_rollout(self.app().data.as_ref())
.await
.map_err(|err| GatewayError::Internal(err.to_string()))?
.map(build_admin_proxy_upgrade_rollout_payload);
Ok(build_admin_proxy_nodes_list_payload_response(
items, total, skip, limit,
items, total, skip, limit, rollout,
))
}
@@ -66,4 +86,55 @@ impl<'a> AdminAppState<'a> {
.collect::<Vec<_>>();
Ok(build_admin_proxy_node_events_payload_response(items))
}
pub(crate) async fn unregister_proxy_node(
&self,
node_id: &str,
) -> Result<Option<aether_data::repository::proxy_nodes::StoredProxyNode>, GatewayError> {
self.app.unregister_proxy_node(node_id).await
}
pub(crate) async fn update_proxy_node_remote_config(
&self,
mutation: &aether_data::repository::proxy_nodes::ProxyNodeRemoteConfigMutation,
) -> Result<Option<aether_data::repository::proxy_nodes::StoredProxyNode>, GatewayError> {
self.app.update_proxy_node_remote_config(mutation).await
}
}
fn build_admin_proxy_upgrade_rollout_payload(
rollout: ProxyUpgradeRolloutStatus,
) -> serde_json::Value {
serde_json::json!({
"version": rollout.version,
"batch_size": rollout.batch_size,
"cooldown_secs": rollout.cooldown_secs,
"started_at": unix_secs_to_rfc3339(rollout.started_at_unix_secs),
"last_dispatched_at": rollout
.last_dispatched_at_unix_secs
.and_then(unix_secs_to_rfc3339),
"updated_at": unix_secs_to_rfc3339(rollout.updated_at_unix_secs),
"probe": rollout.probe.map(|probe| serde_json::json!({
"url": probe.url,
"timeout_secs": probe.timeout_secs,
})),
"blocked": rollout.blocked,
"online_eligible_total": rollout.online_eligible_total,
"completed_node_ids": rollout.completed_node_ids,
"pending_node_ids": rollout.pending_node_ids,
"conflict_node_ids": rollout.conflict_node_ids,
"skipped_node_ids": rollout.skipped_node_ids,
"tracked_nodes": rollout.tracked_nodes.into_iter().map(|tracked| serde_json::json!({
"node_id": tracked.node_id,
"state": tracked.state,
"dispatched_at": unix_secs_to_rfc3339(tracked.dispatched_at_unix_secs),
"version_confirmed_at": tracked
.version_confirmed_at_unix_secs
.and_then(unix_secs_to_rfc3339),
"traffic_confirmed_at": tracked
.traffic_confirmed_at_unix_secs
.and_then(unix_secs_to_rfc3339),
"cooldown_remaining_secs": tracked.cooldown_remaining_secs,
})).collect::<Vec<_>>(),
})
}