feat(proxy): record tunnel stability metrics

This commit is contained in:
fawney19
2026-05-08 22:03:17 +08:00
parent 9a84a6ff6c
commit a703acd1fe
81 changed files with 7229 additions and 1110 deletions

View File

@@ -93,15 +93,6 @@ impl ServerTab {
required: true,
help: "Node name for identification in Aether dashboard",
},
Field {
label: "Upstream Proxy",
key: "upstream_proxy_url",
value: String::new(),
kind: FieldKind::Text,
required: false,
help:
"Optional provider egress proxy, e.g. http://127.0.0.1:8080 or socks5h://127.0.0.1:1080",
},
],
}
}
@@ -115,12 +106,6 @@ impl ServerTab {
}
tab
}
fn set_field_value(&mut self, key: &str, value: &str) {
if let Some(field) = self.fields.iter_mut().find(|field| field.key == key) {
field.value = value.to_string();
}
}
}
// -- App state ----------------------------------------------------------------
@@ -153,6 +138,15 @@ impl App {
server_tabs: vec![ServerTab::new()],
active_tab: 0,
global_fields: vec![
Field {
label: "Egress Proxy",
key: "upstream_proxy_url",
value: String::new(),
kind: FieldKind::Text,
required: false,
help:
"Optional egress proxy for Aether tunnel/API and provider requests, e.g. http://127.0.0.1:8080 or socks5h://127.0.0.1:1080",
},
Field {
label: "Install Service",
key: "install_service",
@@ -282,6 +276,7 @@ impl App {
"allow_private_targets" => cfg.allow_private_targets.map(|v| v.to_string()),
"heartbeat_interval" => cfg.heartbeat_interval.map(|v| v.to_string()),
"redirect_replay_budget_bytes" => cfg.redirect_replay_budget_bytes.clone(),
"upstream_proxy_url" => cfg.upstream_proxy_url.clone(),
_ => None,
};
if let Some(v) = val {
@@ -296,11 +291,6 @@ impl App {
} else {
self.server_tabs = servers.iter().map(ServerTab::from_entry).collect();
}
if let Some(proxy_url) = cfg.upstream_proxy_url.as_deref() {
for tab in &mut self.server_tabs {
tab.set_field_value("upstream_proxy_url", proxy_url);
}
}
self.active_tab = 0;
self.selected = 0;
self.scroll_offset = 0;
@@ -322,12 +312,6 @@ impl App {
.filter(|v| !v.is_empty())
}
fn get_shared_server_field(&self, key: &str) -> Option<String> {
self.server_tabs
.iter()
.find_map(|tab| Self::get_tab(tab, key))
}
fn toggle_enabled(&self, key: &str) -> bool {
self.get_global(key).as_deref() == Some("true")
}
@@ -373,7 +357,7 @@ impl App {
}
fn parse_optional_upstream_proxy_url(&self) -> anyhow::Result<Option<String>> {
let Some(raw) = self.get_shared_server_field("upstream_proxy_url") else {
let Some(raw) = self.get_global("upstream_proxy_url") else {
return Ok(None);
};
let trimmed = raw.trim();
@@ -381,7 +365,7 @@ impl App {
return Ok(None);
}
UpstreamProxyConfig::parse(trimmed)
.map_err(|err| anyhow::anyhow!("upstream proxy URL invalid: {err}"))?;
.map_err(|err| anyhow::anyhow!("egress proxy URL invalid: {err}"))?;
Ok(Some(trimmed.to_string()))
}
@@ -613,11 +597,7 @@ impl App {
}
// -- Add / remove server --
KeyCode::Char('+') | KeyCode::Char('a') => {
let upstream_proxy_url = self.get_shared_server_field("upstream_proxy_url");
let mut tab = ServerTab::new();
if let Some(proxy_url) = upstream_proxy_url.as_deref() {
tab.set_field_value("upstream_proxy_url", proxy_url);
}
let tab = ServerTab::new();
self.server_tabs.push(tab);
self.active_tab = self.server_tabs.len() - 1;
self.selected = 0;
@@ -694,14 +674,7 @@ impl App {
fn commit_edit_buffer(&mut self) -> bool {
if self.validate_edit() {
let key = self.selected_field().key;
if key == "upstream_proxy_url" {
for tab in &mut self.server_tabs {
tab.set_field_value(key, &self.edit_buffer);
}
} else {
self.selected_field_mut().value = self.edit_buffer.clone();
}
self.selected_field_mut().value = self.edit_buffer.clone();
self.modified = true;
self.mode = Mode::Normal;
true
@@ -1124,23 +1097,20 @@ mod tests {
}
#[test]
fn new_app_places_upstream_proxy_under_node_name() {
fn new_app_places_egress_proxy_in_global_fields() {
let app = sample_app();
let keys: Vec<&str> = app.server_tabs[0]
let server_keys: Vec<&str> = app.server_tabs[0]
.fields
.iter()
.map(|field| field.key)
.collect();
let global_keys: Vec<&str> = app.global_fields.iter().map(|field| field.key).collect();
assert_eq!(
keys,
vec![
"aether_url",
"management_token",
"node_name",
"upstream_proxy_url"
]
server_keys,
vec!["aether_url", "management_token", "node_name"]
);
assert_eq!(global_keys.first().copied(), Some("upstream_proxy_url"));
}
#[test]
@@ -1172,7 +1142,7 @@ mod tests {
set_global_field(&mut app, "allow_private_targets", "true");
set_global_field(&mut app, "heartbeat_interval", "45");
set_global_field(&mut app, "redirect_replay_budget_bytes", "6m");
set_server_field(&mut app, "upstream_proxy_url", "socks5h://127.0.0.1:1080");
set_global_field(&mut app, "upstream_proxy_url", "socks5h://127.0.0.1:1080");
let cfg = app.to_config().expect("config should serialize");
assert_eq!(cfg.allow_private_targets, Some(true));
@@ -1194,14 +1164,14 @@ mod tests {
}
#[test]
fn to_config_rejects_invalid_upstream_proxy_url() {
fn to_config_rejects_invalid_egress_proxy_url() {
let mut app = sample_app();
set_server_field(&mut app, "upstream_proxy_url", "ftp://proxy.example");
set_global_field(&mut app, "upstream_proxy_url", "ftp://proxy.example");
let error = app
.to_config()
.expect_err("invalid upstream proxy should be rejected");
assert!(error.to_string().contains("upstream proxy URL"));
.expect_err("invalid egress proxy should be rejected");
assert!(error.to_string().contains("egress proxy URL"));
}
#[test]