diff --git a/apps/desktop-tauri/src-tauri/src/commands/bridge.rs b/apps/desktop-tauri/src-tauri/src/commands/bridge.rs index 753edd23b3..194e1082f8 100644 --- a/apps/desktop-tauri/src-tauri/src/commands/bridge.rs +++ b/apps/desktop-tauri/src-tauri/src/commands/bridge.rs @@ -439,6 +439,10 @@ pub struct SettingsSnapshot { agent_sessions_enabled: bool, agent_session_ssh_hosts: Vec, hooks_enabled: bool, + http_proxy_enabled: bool, + http_proxy_url: String, + http_proxy_username: String, + http_proxy_password: String, ui_language: &'static str, theme: &'static str, window_scale_percent: u16, @@ -535,6 +539,10 @@ impl From for SettingsSnapshot { agent_sessions_enabled: settings.agent_sessions_enabled, agent_session_ssh_hosts: settings.agent_session_ssh_hosts, hooks_enabled: settings.hooks_enabled, + http_proxy_enabled: settings.http_proxy_enabled, + http_proxy_url: settings.http_proxy_url, + http_proxy_username: settings.http_proxy_username, + http_proxy_password: settings.http_proxy_password, ui_language: language_label(settings.ui_language), theme: theme_label(settings.theme), window_scale_percent: settings.window_scale_percent, diff --git a/apps/desktop-tauri/src-tauri/src/commands/settings.rs b/apps/desktop-tauri/src-tauri/src/commands/settings.rs index 94f87ad6d0..0835364f62 100644 --- a/apps/desktop-tauri/src-tauri/src/commands/settings.rs +++ b/apps/desktop-tauri/src-tauri/src/commands/settings.rs @@ -40,6 +40,10 @@ pub struct SettingsUpdate { pub agent_sessions_enabled: Option, pub agent_session_ssh_hosts: Option>, pub hooks_enabled: Option, + pub http_proxy_enabled: Option, + pub http_proxy_url: Option, + pub http_proxy_username: Option, + pub http_proxy_password: Option, pub ui_language: Option, pub theme: Option, pub window_scale_percent: Option, @@ -258,6 +262,18 @@ impl SettingsUpdate { if let Some(v) = self.hooks_enabled { settings.hooks_enabled = v; } + if let Some(v) = self.http_proxy_enabled { + settings.http_proxy_enabled = v; + } + if let Some(v) = self.http_proxy_url.clone() { + settings.http_proxy_url = v.trim().to_string(); + } + if let Some(v) = self.http_proxy_username.clone() { + settings.http_proxy_username = v.trim().to_string(); + } + if let Some(v) = self.http_proxy_password.clone() { + settings.http_proxy_password = v; + } if let Some(v) = self.install_updates_on_quit { settings.install_updates_on_quit = v; } diff --git a/apps/desktop-tauri/src/i18n/keys.ts b/apps/desktop-tauri/src/i18n/keys.ts index 6f977b76a6..dbe2034f6d 100644 --- a/apps/desktop-tauri/src/i18n/keys.ts +++ b/apps/desktop-tauri/src/i18n/keys.ts @@ -287,6 +287,16 @@ export const ALL_LOCALE_KEYS = [ "HooksEnableLabel", "HooksEnableHelper", "HooksConfigPathHint", + "NetworkProxyInvalidUrl", + "NetworkProxyPasswordHelper", + "NetworkProxyPasswordLabel", + "NetworkProxyUserLabel", + "NetworkProxyUrlHelper", + "NetworkProxyUrlLabel", + "NetworkProxyEnableHelper", + "NetworkProxyEnableLabel", + "NetworkProxyCaption", + "NetworkProxyTitle", "UsageSpendTitle", "UsageSpendCaption", "UsageSpendRefresh", diff --git a/apps/desktop-tauri/src/surfaces/settings/tabs/AdvancedTab.tsx b/apps/desktop-tauri/src/surfaces/settings/tabs/AdvancedTab.tsx index 0ed4d5c801..204a3bd032 100644 --- a/apps/desktop-tauri/src/surfaces/settings/tabs/AdvancedTab.tsx +++ b/apps/desktop-tauri/src/surfaces/settings/tabs/AdvancedTab.tsx @@ -197,6 +197,68 @@ export default function AdvancedTab({ settings, set, saving }: TabProps) { + {/* -- Network proxy ---------------------------------------- */} +
+

{t("NetworkProxyTitle")}

+

{t("NetworkProxyCaption")}

+
+ + set({ httpProxyEnabled: v })} + /> + + + set({ httpProxyUrl: event.target.value })} + onBlur={(event) => + set({ httpProxyUrl: event.target.value.trim() }) + } + /> + + + + set({ httpProxyUsername: event.target.value }) + } + /> + + + + set({ httpProxyPassword: event.target.value }) + } + /> + +
+
+ {/* -- External hooks --------------------------------------- */}

{t("HooksTitle")}

diff --git a/apps/desktop-tauri/src/types/bridge.ts b/apps/desktop-tauri/src/types/bridge.ts index bb87f1fe05..32131af241 100644 --- a/apps/desktop-tauri/src/types/bridge.ts +++ b/apps/desktop-tauri/src/types/bridge.ts @@ -256,6 +256,11 @@ export interface SettingsSnapshot { agentSessionSshHosts?: string[]; /** Master switch for external hooks (hooks.json next to settings). */ hooksEnabled?: boolean; + /** Route provider HTTPS through a user HTTP(S) proxy (#235). */ + httpProxyEnabled?: boolean; + httpProxyUrl?: string; + httpProxyUsername?: string; + httpProxyPassword?: string; uiLanguage: Language; theme: ThemePreference; /** 100..=250 — clamped server-side. */ @@ -322,6 +327,10 @@ export interface SettingsUpdate { agentSessionsEnabled?: boolean; agentSessionSshHosts?: string[]; hooksEnabled?: boolean; + httpProxyEnabled?: boolean; + httpProxyUrl?: string; + httpProxyUsername?: string; + httpProxyPassword?: string; uiLanguage?: Language; theme?: ThemePreference; windowScalePercent?: number; diff --git a/rust/src/core/http.rs b/rust/src/core/http.rs index 7ad38b3ecf..1f901c393d 100644 --- a/rust/src/core/http.rs +++ b/rust/src/core/http.rs @@ -2,14 +2,19 @@ use reqwest::Url; +use super::http_proxy::apply_app_proxy; + /// Build a client for requests that may carry cookies, OAuth tokens, API keys, /// or other provider credentials. /// /// Credentialed provider requests should not automatically follow redirects to /// a different origin. Reqwest strips some sensitive headers during redirects, /// but an explicit same-origin policy keeps the invariant local and testable. +/// +/// When Settings → Advanced HTTP proxy is enabled, the global proxy is applied +/// here so provider refreshes pick it up on the next `instantiate_provider`. pub fn credentialed_http_client_builder() -> reqwest::ClientBuilder { - reqwest::Client::builder().redirect(reqwest::redirect::Policy::custom(|attempt| { + let builder = reqwest::Client::builder().redirect(reqwest::redirect::Policy::custom(|attempt| { let previous = attempt.previous(); let Some(last_url) = previous.last() else { return attempt.follow(); @@ -20,7 +25,8 @@ pub fn credentialed_http_client_builder() -> reqwest::ClientBuilder { } else { attempt.stop() } - })) + })); + apply_app_proxy(builder) } pub(crate) fn is_same_origin(from: &Url, to: &Url) -> bool { diff --git a/rust/src/core/http_proxy.rs b/rust/src/core/http_proxy.rs new file mode 100644 index 0000000000..b4bedca6c9 --- /dev/null +++ b/rust/src/core/http_proxy.rs @@ -0,0 +1,177 @@ +//! Global HTTP(S) proxy configuration for provider / app network traffic. +//! +//! Issue #235 — opt-in settings proxy (not CLIProxyAPI, not LLM Proxy provider). +//! Password may live in local settings.json (same trust boundary as other local secrets). + +use reqwest::{ClientBuilder, Proxy, Url}; + +/// Snapshot of proxy-related settings used when building HTTP clients. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct HttpProxySettings { + pub enabled: bool, + pub url: String, + pub username: String, + pub password: String, +} + +impl HttpProxySettings { + pub fn from_parts( + enabled: bool, + url: impl Into, + username: impl Into, + password: impl Into, + ) -> Self { + Self { + enabled, + url: url.into(), + username: username.into(), + password: password.into(), + } + } + + /// Load from persisted app settings (no-op if settings fail to load). + pub fn from_app_settings() -> Self { + let settings = crate::settings::Settings::load(); + Self { + enabled: settings.http_proxy_enabled, + url: settings.http_proxy_url.clone(), + username: settings.http_proxy_username.clone(), + password: settings.http_proxy_password.clone(), + } + } +} + +/// Resolve a reqwest [`Proxy`] from settings. +/// +/// - Disabled or empty URL → `Ok(None)` (direct / default). +/// - Invalid URL when enabled → `Err(...)`. +/// - Supports `http` and `https` proxy schemes only (MVP). +pub fn resolve_proxy(settings: &HttpProxySettings) -> Result, String> { + if !settings.enabled { + return Ok(None); + } + let raw = settings.url.trim(); + if raw.is_empty() { + return Err("Proxy URL is required when the proxy is enabled.".into()); + } + + let parsed = Url::parse(raw).map_err(|e| format!("Invalid proxy URL: {e}"))?; + let scheme = parsed.scheme().to_ascii_lowercase(); + if scheme != "http" && scheme != "https" { + return Err(format!( + "Unsupported proxy scheme '{scheme}'. Use http:// or https://." + )); + } + if parsed.host_str().is_none() { + return Err("Proxy URL must include a host.".into()); + } + + let mut proxy = Proxy::all(raw).map_err(|e| format!("Invalid proxy URL: {e}"))?; + + // Prefer explicit username/password fields. If the URL already embeds + // credentials, do not call basic_auth again (would double-apply). + let url_has_user = !parsed.username().is_empty(); + let user = settings.username.trim(); + let pass = settings.password.as_str(); + if !user.is_empty() && !url_has_user { + proxy = proxy.basic_auth(user, pass); + } + + Ok(Some(proxy)) +} + +/// Apply resolved proxy to a client builder. +/// +/// On misconfiguration, returns the builder unchanged so callers can still +/// connect direct (caller should log the error). +pub fn apply_proxy_to_builder( + builder: ClientBuilder, + settings: &HttpProxySettings, +) -> ClientBuilder { + match resolve_proxy(settings) { + Ok(Some(proxy)) => builder.proxy(proxy), + Ok(None) => builder, + Err(err) => { + tracing::warn!(error = %err, "http proxy config ignored; using direct connection"); + builder + } + } +} + +/// Convenience: apply the current app settings proxy to a builder. +pub fn apply_app_proxy(builder: ClientBuilder) -> ClientBuilder { + apply_proxy_to_builder(builder, &HttpProxySettings::from_app_settings()) +} + +/// Validate proxy settings for UI (returns `None` when OK / disabled). +pub fn validation_error(settings: &HttpProxySettings) -> Option { + if !settings.enabled { + return None; + } + resolve_proxy(settings).err() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn disabled_returns_none() { + let s = HttpProxySettings::from_parts(false, "http://127.0.0.1:7890", "", ""); + assert!(resolve_proxy(&s).unwrap().is_none()); + } + + #[test] + fn enabled_empty_url_errors() { + let s = HttpProxySettings::from_parts(true, " ", "", ""); + assert!(resolve_proxy(&s).unwrap_err().contains("required")); + } + + #[test] + fn valid_http_url_ok() { + let s = HttpProxySettings::from_parts(true, "http://127.0.0.1:7890", "", ""); + assert!(resolve_proxy(&s).unwrap().is_some()); + } + + #[test] + fn rejects_socks_scheme() { + let s = HttpProxySettings::from_parts(true, "socks5://127.0.0.1:1080", "", ""); + let err = resolve_proxy(&s).unwrap_err(); + assert!(err.contains("Unsupported"), "{err}"); + } + + #[test] + fn rejects_garbage_url() { + let s = HttpProxySettings::from_parts(true, "not a url", "", ""); + assert!(resolve_proxy(&s).is_err()); + } + + #[test] + fn accepts_userinfo_in_url() { + let s = HttpProxySettings::from_parts(true, "http://alice:secret@proxy.local:8080", "", ""); + assert!(resolve_proxy(&s).unwrap().is_some()); + } + + #[test] + fn accepts_explicit_basic_auth_fields() { + let s = HttpProxySettings::from_parts( + true, + "http://proxy.local:8080", + "alice", + "secret", + ); + assert!(resolve_proxy(&s).unwrap().is_some()); + } + + #[test] + fn validation_error_none_when_disabled() { + let s = HttpProxySettings::from_parts(false, "bad", "", ""); + assert!(validation_error(&s).is_none()); + } + + #[test] + fn validation_error_when_enabled_and_bad() { + let s = HttpProxySettings::from_parts(true, "ftp://x", "", ""); + assert!(validation_error(&s).is_some()); + } +} diff --git a/rust/src/core/mod.rs b/rust/src/core/mod.rs index d41aac2636..07476624a0 100755 --- a/rust/src/core/mod.rs +++ b/rust/src/core/mod.rs @@ -5,6 +5,7 @@ mod cost_pricing; mod credential_migration; mod hooks; mod http; +mod http_proxy; mod jsonl_scanner; mod models_dev_pricing; mod openai_dashboard; @@ -23,6 +24,7 @@ pub use cost_pricing::*; pub use credential_migration::*; pub use hooks::*; pub use http::*; +pub use http_proxy::*; pub use jsonl_scanner::*; pub use models_dev_pricing::*; pub use openai_dashboard::*; diff --git a/rust/src/core/models_dev_pricing.rs b/rust/src/core/models_dev_pricing.rs index dc1be873b2..2bba9d05fc 100644 --- a/rust/src/core/models_dev_pricing.rs +++ b/rust/src/core/models_dev_pricing.rs @@ -792,7 +792,7 @@ async fn refresh_unknown_models_at( } async fn refresh_catalog(now: SystemTime, cache_root: Option<&Path>) -> bool { - let Ok(client) = reqwest::Client::builder() + let Ok(client) = crate::core::apply_app_proxy(reqwest::Client::builder()) .timeout(Duration::from_secs(20)) .build() else { diff --git a/rust/src/locale.rs b/rust/src/locale.rs index 5069461b21..57235ca08b 100644 --- a/rust/src/locale.rs +++ b/rust/src/locale.rs @@ -503,6 +503,16 @@ locale_keys! { HooksEnableLabel, HooksEnableHelper, HooksConfigPathHint, + NetworkProxyTitle, + NetworkProxyCaption, + NetworkProxyEnableLabel, + NetworkProxyEnableHelper, + NetworkProxyUrlLabel, + NetworkProxyUrlHelper, + NetworkProxyUserLabel, + NetworkProxyPasswordLabel, + NetworkProxyPasswordHelper, + NetworkProxyInvalidUrl, UsageSpendTitle, UsageSpendCaption, UsageSpendRefresh, diff --git a/rust/src/locale/en-US.ftl b/rust/src/locale/en-US.ftl index d9d892c801..2c76904ae1 100644 --- a/rust/src/locale/en-US.ftl +++ b/rust/src/locale/en-US.ftl @@ -694,3 +694,13 @@ PromoteTrayIconUnsupportedHint = Not available on this operating system. Drag th UsageSpendShare = Export share card (PNG) UsageSpendShareEmpty = Nothing to export yet. UsageSpendShareFailed = Could not create the share card image. +NetworkProxyTitle = Network proxy +NetworkProxyCaption = Route provider and app HTTPS through an HTTP(S) proxy (corporate / local proxy). Does not affect browser cookie import or external CLI tools. +NetworkProxyEnableLabel = Use HTTP proxy +NetworkProxyEnableHelper = When enabled, usage refreshes use this proxy. Invalid URLs fall back to a direct connection. +NetworkProxyUrlLabel = Proxy URL +NetworkProxyUrlHelper = Example: http://127.0.0.1:7890 (http or https only). +NetworkProxyUserLabel = Username (optional) +NetworkProxyPasswordLabel = Password (optional) +NetworkProxyPasswordHelper = Stored in local settings.json. Prefer a local proxy without auth when possible. +NetworkProxyInvalidUrl = Invalid proxy URL. Use http://host:port. diff --git a/rust/src/locale/es-MX.ftl b/rust/src/locale/es-MX.ftl index 9e4342ef3f..f94adce69b 100644 --- a/rust/src/locale/es-MX.ftl +++ b/rust/src/locale/es-MX.ftl @@ -615,3 +615,13 @@ PromoteTrayIconUnsupportedHint = No disponible en este sistema operativo. Arrast UsageSpendShare = Export share card (PNG) UsageSpendShareEmpty = Nothing to export yet. UsageSpendShareFailed = Could not create the share card image. +NetworkProxyTitle = Network proxy +NetworkProxyCaption = Route provider and app HTTPS through an HTTP(S) proxy (corporate / local proxy). Does not affect browser cookie import or external CLI tools. +NetworkProxyEnableLabel = Use HTTP proxy +NetworkProxyEnableHelper = When enabled, usage refreshes use this proxy. Invalid URLs fall back to a direct connection. +NetworkProxyUrlLabel = Proxy URL +NetworkProxyUrlHelper = Example: http://127.0.0.1:7890 (http or https only). +NetworkProxyUserLabel = Username (optional) +NetworkProxyPasswordLabel = Password (optional) +NetworkProxyPasswordHelper = Stored in local settings.json. Prefer a local proxy without auth when possible. +NetworkProxyInvalidUrl = Invalid proxy URL. Use http://host:port. diff --git a/rust/src/locale/ja-JP.ftl b/rust/src/locale/ja-JP.ftl index 5214214d15..9130570aa1 100644 --- a/rust/src/locale/ja-JP.ftl +++ b/rust/src/locale/ja-JP.ftl @@ -694,3 +694,13 @@ PromoteTrayIconUnsupportedHint = このオペレーティングシステムで UsageSpendShare = Export share card (PNG) UsageSpendShareEmpty = Nothing to export yet. UsageSpendShareFailed = Could not create the share card image. +NetworkProxyTitle = Network proxy +NetworkProxyCaption = Route provider and app HTTPS through an HTTP(S) proxy (corporate / local proxy). Does not affect browser cookie import or external CLI tools. +NetworkProxyEnableLabel = Use HTTP proxy +NetworkProxyEnableHelper = When enabled, usage refreshes use this proxy. Invalid URLs fall back to a direct connection. +NetworkProxyUrlLabel = Proxy URL +NetworkProxyUrlHelper = Example: http://127.0.0.1:7890 (http or https only). +NetworkProxyUserLabel = Username (optional) +NetworkProxyPasswordLabel = Password (optional) +NetworkProxyPasswordHelper = Stored in local settings.json. Prefer a local proxy without auth when possible. +NetworkProxyInvalidUrl = Invalid proxy URL. Use http://host:port. diff --git a/rust/src/locale/ko-KR.ftl b/rust/src/locale/ko-KR.ftl index 6e2825b508..da9906f6ed 100644 --- a/rust/src/locale/ko-KR.ftl +++ b/rust/src/locale/ko-KR.ftl @@ -615,3 +615,13 @@ PromoteTrayIconUnsupportedHint = 이 운영 체제에서는 사용할 수 없습 UsageSpendShare = Export share card (PNG) UsageSpendShareEmpty = Nothing to export yet. UsageSpendShareFailed = Could not create the share card image. +NetworkProxyTitle = Network proxy +NetworkProxyCaption = Route provider and app HTTPS through an HTTP(S) proxy (corporate / local proxy). Does not affect browser cookie import or external CLI tools. +NetworkProxyEnableLabel = Use HTTP proxy +NetworkProxyEnableHelper = When enabled, usage refreshes use this proxy. Invalid URLs fall back to a direct connection. +NetworkProxyUrlLabel = Proxy URL +NetworkProxyUrlHelper = Example: http://127.0.0.1:7890 (http or https only). +NetworkProxyUserLabel = Username (optional) +NetworkProxyPasswordLabel = Password (optional) +NetworkProxyPasswordHelper = Stored in local settings.json. Prefer a local proxy without auth when possible. +NetworkProxyInvalidUrl = Invalid proxy URL. Use http://host:port. diff --git a/rust/src/locale/zh-CN.ftl b/rust/src/locale/zh-CN.ftl index e69c4d3e5e..1273ba0f12 100644 --- a/rust/src/locale/zh-CN.ftl +++ b/rust/src/locale/zh-CN.ftl @@ -694,3 +694,13 @@ PromoteTrayIconUnsupportedHint = 此操作系统不支持该功能。请在 Wind UsageSpendShare = Export share card (PNG) UsageSpendShareEmpty = Nothing to export yet. UsageSpendShareFailed = Could not create the share card image. +NetworkProxyTitle = Network proxy +NetworkProxyCaption = Route provider and app HTTPS through an HTTP(S) proxy (corporate / local proxy). Does not affect browser cookie import or external CLI tools. +NetworkProxyEnableLabel = Use HTTP proxy +NetworkProxyEnableHelper = When enabled, usage refreshes use this proxy. Invalid URLs fall back to a direct connection. +NetworkProxyUrlLabel = Proxy URL +NetworkProxyUrlHelper = Example: http://127.0.0.1:7890 (http or https only). +NetworkProxyUserLabel = Username (optional) +NetworkProxyPasswordLabel = Password (optional) +NetworkProxyPasswordHelper = Stored in local settings.json. Prefer a local proxy without auth when possible. +NetworkProxyInvalidUrl = Invalid proxy URL. Use http://host:port. diff --git a/rust/src/locale/zh-TW.ftl b/rust/src/locale/zh-TW.ftl index bb98fa0cd7..aa8f031a5d 100644 --- a/rust/src/locale/zh-TW.ftl +++ b/rust/src/locale/zh-TW.ftl @@ -694,3 +694,13 @@ PromoteTrayIconUnsupportedHint = 此作業系統不支援此功能。請在 Wind UsageSpendShare = Export share card (PNG) UsageSpendShareEmpty = Nothing to export yet. UsageSpendShareFailed = Could not create the share card image. +NetworkProxyTitle = Network proxy +NetworkProxyCaption = Route provider and app HTTPS through an HTTP(S) proxy (corporate / local proxy). Does not affect browser cookie import or external CLI tools. +NetworkProxyEnableLabel = Use HTTP proxy +NetworkProxyEnableHelper = When enabled, usage refreshes use this proxy. Invalid URLs fall back to a direct connection. +NetworkProxyUrlLabel = Proxy URL +NetworkProxyUrlHelper = Example: http://127.0.0.1:7890 (http or https only). +NetworkProxyUserLabel = Username (optional) +NetworkProxyPasswordLabel = Password (optional) +NetworkProxyPasswordHelper = Stored in local settings.json. Prefer a local proxy without auth when possible. +NetworkProxyInvalidUrl = Invalid proxy URL. Use http://host:port. diff --git a/rust/src/settings.rs b/rust/src/settings.rs index ab3e0e280b..0cc7a849fd 100755 --- a/rust/src/settings.rs +++ b/rust/src/settings.rs @@ -164,6 +164,22 @@ pub struct Settings { #[serde(default)] pub hooks_enabled: bool, + /// Route provider/app HTTPS through a user-configured HTTP(S) proxy (#235). + #[serde(default)] + pub http_proxy_enabled: bool, + + /// Proxy base URL, e.g. `http://127.0.0.1:7890`. + #[serde(default, skip_serializing_if = "String::is_empty")] + pub http_proxy_url: String, + + /// Optional proxy basic-auth username. + #[serde(default, skip_serializing_if = "String::is_empty")] + pub http_proxy_username: String, + + /// Optional proxy basic-auth password (stored in local settings.json). + #[serde(default, skip_serializing_if = "String::is_empty")] + pub http_proxy_password: String, + /// Automatically download updates in the background #[serde(default)] pub auto_download_updates: bool, @@ -410,6 +426,10 @@ impl Default for Settings { agent_sessions_enabled: false, agent_session_ssh_hosts: Vec::new(), hooks_enabled: false, + http_proxy_enabled: false, + http_proxy_url: String::new(), + http_proxy_username: String::new(), + http_proxy_password: String::new(), auto_download_updates: false, // Require explicit opt-in for background downloads install_updates_on_quit: false, // Don't auto-install on quit by default ui_language: Language::default(), // English by default diff --git a/rust/src/settings/raw.rs b/rust/src/settings/raw.rs index 2a14614a7f..57ab5c8170 100644 --- a/rust/src/settings/raw.rs +++ b/rust/src/settings/raw.rs @@ -114,6 +114,14 @@ pub(super) struct RawSettings { agent_session_ssh_hosts: Vec, #[serde(default)] hooks_enabled: bool, + #[serde(default)] + http_proxy_enabled: bool, + #[serde(default)] + http_proxy_url: String, + #[serde(default)] + http_proxy_username: String, + #[serde(default)] + http_proxy_password: String, auto_download_updates: bool, install_updates_on_quit: bool, ui_language: Language, @@ -216,6 +224,10 @@ impl Default for RawSettings { agent_sessions_enabled: s.agent_sessions_enabled, agent_session_ssh_hosts: s.agent_session_ssh_hosts, hooks_enabled: s.hooks_enabled, + http_proxy_enabled: s.http_proxy_enabled, + http_proxy_url: s.http_proxy_url, + http_proxy_username: s.http_proxy_username, + http_proxy_password: s.http_proxy_password, auto_download_updates: s.auto_download_updates, install_updates_on_quit: s.install_updates_on_quit, ui_language: s.ui_language, @@ -486,6 +498,10 @@ impl From for Settings { agent_sessions_enabled: raw.agent_sessions_enabled, agent_session_ssh_hosts: raw.agent_session_ssh_hosts, hooks_enabled: raw.hooks_enabled, + http_proxy_enabled: raw.http_proxy_enabled, + http_proxy_url: raw.http_proxy_url, + http_proxy_username: raw.http_proxy_username, + http_proxy_password: raw.http_proxy_password, auto_download_updates: raw.auto_download_updates, install_updates_on_quit: raw.install_updates_on_quit, ui_language: raw.ui_language, diff --git a/rust/src/status/mod.rs b/rust/src/status/mod.rs index d0a248568e..79f4331a5c 100755 --- a/rust/src/status/mod.rs +++ b/rust/src/status/mod.rs @@ -79,7 +79,7 @@ pub fn get_status_page_url(provider: &str) -> Option<&'static str> { /// Fetch status from a Statuspage.io-based status page pub async fn fetch_statuspage_io(url: &str) -> Result { - let client = reqwest::Client::builder() + let client = crate::core::apply_app_proxy(reqwest::Client::builder()) .timeout(std::time::Duration::from_secs(10)) .build() .map_err(|e| e.to_string())?; @@ -131,7 +131,7 @@ pub async fn fetch_statuspage_io(url: &str) -> Result { /// Fetch status with components from a Statuspage.io-based status page pub async fn fetch_statuspage_io_components(url: &str) -> Result { - let client = reqwest::Client::builder() + let client = crate::core::apply_app_proxy(reqwest::Client::builder()) .timeout(std::time::Duration::from_secs(10)) .build() .map_err(|e| e.to_string())?; diff --git a/rust/src/updater.rs b/rust/src/updater.rs index 15d981c86e..220ec423f9 100755 --- a/rust/src/updater.rs +++ b/rust/src/updater.rs @@ -114,7 +114,7 @@ fn release_url(channel: UpdateChannel) -> String { } fn update_client() -> Option { - reqwest::Client::builder() + crate::core::apply_app_proxy(reqwest::Client::builder()) .user_agent("CodexBar") .build() .ok() @@ -303,7 +303,7 @@ fn expected_update_sha256(update_info: &UpdateInfo) -> Result<&str, String> { } fn update_http_client() -> Result { - reqwest::Client::builder() + crate::core::apply_app_proxy(reqwest::Client::builder()) .user_agent("CodexBar") .build() .map_err(|e| format!("Failed to create HTTP client: {}", e))