mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-09-30 21:19:35 +02:00
refactor: make ui reactive for proxy changes
This commit is contained in:
1 parent
9f68a21824
commit
59f430ec43
8 files changed
+181
-167
No files matched your search
+12
-11
@@ -537,11 +537,11 @@ async fn get_group(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn create_group(
|
async fn create_group(
|
||||||
State(_state): State<ApiServerState>,
|
State(state): State<ApiServerState>,
|
||||||
Json(request): Json<CreateGroupRequest>,
|
Json(request): Json<CreateGroupRequest>,
|
||||||
) -> Result<Json<ApiGroupResponse>, StatusCode> {
|
) -> Result<Json<ApiGroupResponse>, StatusCode> {
|
||||||
match GROUP_MANAGER.lock() {
|
match GROUP_MANAGER.lock() {
|
||||||
Ok(manager) => match manager.create_group(request.name) {
|
Ok(manager) => match manager.create_group(&state.app_handle, request.name) {
|
||||||
Ok(group) => Ok(Json(ApiGroupResponse {
|
Ok(group) => Ok(Json(ApiGroupResponse {
|
||||||
id: group.id,
|
id: group.id,
|
||||||
name: group.name,
|
name: group.name,
|
||||||
@@ -555,11 +555,11 @@ async fn create_group(
|
|||||||
|
|
||||||
async fn update_group(
|
async fn update_group(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
State(_state): State<ApiServerState>,
|
State(state): State<ApiServerState>,
|
||||||
Json(request): Json<UpdateGroupRequest>,
|
Json(request): Json<UpdateGroupRequest>,
|
||||||
) -> Result<Json<ApiGroupResponse>, StatusCode> {
|
) -> Result<Json<ApiGroupResponse>, StatusCode> {
|
||||||
match GROUP_MANAGER.lock() {
|
match GROUP_MANAGER.lock() {
|
||||||
Ok(manager) => match manager.update_group(id.clone(), request.name) {
|
Ok(manager) => match manager.update_group(&state.app_handle, id.clone(), request.name) {
|
||||||
Ok(group) => Ok(Json(ApiGroupResponse {
|
Ok(group) => Ok(Json(ApiGroupResponse {
|
||||||
id: group.id,
|
id: group.id,
|
||||||
name: group.name,
|
name: group.name,
|
||||||
@@ -573,10 +573,10 @@ async fn update_group(
|
|||||||
|
|
||||||
async fn delete_group(
|
async fn delete_group(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
State(_state): State<ApiServerState>,
|
State(state): State<ApiServerState>,
|
||||||
) -> Result<StatusCode, StatusCode> {
|
) -> Result<StatusCode, StatusCode> {
|
||||||
match GROUP_MANAGER.lock() {
|
match GROUP_MANAGER.lock() {
|
||||||
Ok(manager) => match manager.delete_group(id.clone()) {
|
Ok(manager) => match manager.delete_group(&state.app_handle, id.clone()) {
|
||||||
Ok(_) => Ok(StatusCode::NO_CONTENT),
|
Ok(_) => Ok(StatusCode::NO_CONTENT),
|
||||||
Err(_) => Err(StatusCode::BAD_REQUEST),
|
Err(_) => Err(StatusCode::BAD_REQUEST),
|
||||||
},
|
},
|
||||||
@@ -629,13 +629,13 @@ async fn get_proxy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn create_proxy(
|
async fn create_proxy(
|
||||||
State(_state): State<ApiServerState>,
|
State(state): State<ApiServerState>,
|
||||||
Json(request): Json<CreateProxyRequest>,
|
Json(request): Json<CreateProxyRequest>,
|
||||||
) -> Result<Json<ApiProxyResponse>, StatusCode> {
|
) -> Result<Json<ApiProxyResponse>, StatusCode> {
|
||||||
// Convert JSON value to ProxySettings
|
// Convert JSON value to ProxySettings
|
||||||
match serde_json::from_value(request.proxy_settings.clone()) {
|
match serde_json::from_value(request.proxy_settings.clone()) {
|
||||||
Ok(proxy_settings) => {
|
Ok(proxy_settings) => {
|
||||||
match PROXY_MANAGER.create_stored_proxy(request.name.clone(), proxy_settings) {
|
match PROXY_MANAGER.create_stored_proxy(&state.app_handle, request.name.clone(), proxy_settings) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// Find the created proxy to return it
|
// Find the created proxy to return it
|
||||||
let proxies = PROXY_MANAGER.get_stored_proxies();
|
let proxies = PROXY_MANAGER.get_stored_proxies();
|
||||||
@@ -658,7 +658,7 @@ async fn create_proxy(
|
|||||||
|
|
||||||
async fn update_proxy(
|
async fn update_proxy(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
State(_state): State<ApiServerState>,
|
State(state): State<ApiServerState>,
|
||||||
Json(request): Json<UpdateProxyRequest>,
|
Json(request): Json<UpdateProxyRequest>,
|
||||||
) -> Result<Json<ApiProxyResponse>, StatusCode> {
|
) -> Result<Json<ApiProxyResponse>, StatusCode> {
|
||||||
let proxies = PROXY_MANAGER.get_stored_proxies();
|
let proxies = PROXY_MANAGER.get_stored_proxies();
|
||||||
@@ -674,6 +674,7 @@ async fn update_proxy(
|
|||||||
};
|
};
|
||||||
|
|
||||||
match PROXY_MANAGER.update_stored_proxy(
|
match PROXY_MANAGER.update_stored_proxy(
|
||||||
|
&state.app_handle,
|
||||||
&id,
|
&id,
|
||||||
Some(new_name.clone()),
|
Some(new_name.clone()),
|
||||||
Some(new_proxy_settings.clone()),
|
Some(new_proxy_settings.clone()),
|
||||||
@@ -692,9 +693,9 @@ async fn update_proxy(
|
|||||||
|
|
||||||
async fn delete_proxy(
|
async fn delete_proxy(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
State(_state): State<ApiServerState>,
|
State(state): State<ApiServerState>,
|
||||||
) -> Result<StatusCode, StatusCode> {
|
) -> Result<StatusCode, StatusCode> {
|
||||||
match PROXY_MANAGER.delete_stored_proxy(&id) {
|
match PROXY_MANAGER.delete_stored_proxy(&state.app_handle, &id) {
|
||||||
Ok(_) => Ok(StatusCode::NO_CONTENT),
|
Ok(_) => Ok(StatusCode::NO_CONTENT),
|
||||||
Err(_) => Err(StatusCode::BAD_REQUEST),
|
Err(_) => Err(StatusCode::BAD_REQUEST),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,11 +174,12 @@ async fn handle_url_open(app: tauri::AppHandle, url: String) -> Result<(), Strin
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn create_stored_proxy(
|
async fn create_stored_proxy(
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
name: String,
|
name: String,
|
||||||
proxy_settings: crate::browser::ProxySettings,
|
proxy_settings: crate::browser::ProxySettings,
|
||||||
) -> Result<crate::proxy_manager::StoredProxy, String> {
|
) -> Result<crate::proxy_manager::StoredProxy, String> {
|
||||||
crate::proxy_manager::PROXY_MANAGER
|
crate::proxy_manager::PROXY_MANAGER
|
||||||
.create_stored_proxy(name, proxy_settings)
|
.create_stored_proxy(&app_handle, name, proxy_settings)
|
||||||
.map_err(|e| format!("Failed to create stored proxy: {e}"))
|
.map_err(|e| format!("Failed to create stored proxy: {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,19 +190,20 @@ async fn get_stored_proxies() -> Result<Vec<crate::proxy_manager::StoredProxy>,
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn update_stored_proxy(
|
async fn update_stored_proxy(
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
proxy_id: String,
|
proxy_id: String,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
proxy_settings: Option<crate::browser::ProxySettings>,
|
proxy_settings: Option<crate::browser::ProxySettings>,
|
||||||
) -> Result<crate::proxy_manager::StoredProxy, String> {
|
) -> Result<crate::proxy_manager::StoredProxy, String> {
|
||||||
crate::proxy_manager::PROXY_MANAGER
|
crate::proxy_manager::PROXY_MANAGER
|
||||||
.update_stored_proxy(&proxy_id, name, proxy_settings)
|
.update_stored_proxy(&app_handle, &proxy_id, name, proxy_settings)
|
||||||
.map_err(|e| format!("Failed to update stored proxy: {e}"))
|
.map_err(|e| format!("Failed to update stored proxy: {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn delete_stored_proxy(proxy_id: String) -> Result<(), String> {
|
async fn delete_stored_proxy(app_handle: tauri::AppHandle, proxy_id: String) -> Result<(), String> {
|
||||||
crate::proxy_manager::PROXY_MANAGER
|
crate::proxy_manager::PROXY_MANAGER
|
||||||
.delete_stored_proxy(&proxy_id)
|
.delete_stored_proxy(&app_handle, &proxy_id)
|
||||||
.map_err(|e| format!("Failed to delete stored proxy: {e}"))
|
.map_err(|e| format!("Failed to delete stored proxy: {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::fs;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use tauri_plugin_shell::ShellExt;
|
use tauri_plugin_shell::ShellExt;
|
||||||
|
use tauri::Emitter;
|
||||||
|
|
||||||
use crate::browser::ProxySettings;
|
use crate::browser::ProxySettings;
|
||||||
|
|
||||||
@@ -146,6 +147,7 @@ impl ProxyManager {
|
|||||||
// Create a new stored proxy
|
// Create a new stored proxy
|
||||||
pub fn create_stored_proxy(
|
pub fn create_stored_proxy(
|
||||||
&self,
|
&self,
|
||||||
|
app_handle: &tauri::AppHandle,
|
||||||
name: String,
|
name: String,
|
||||||
proxy_settings: ProxySettings,
|
proxy_settings: ProxySettings,
|
||||||
) -> Result<StoredProxy, String> {
|
) -> Result<StoredProxy, String> {
|
||||||
@@ -168,6 +170,11 @@ impl ProxyManager {
|
|||||||
eprintln!("Warning: Failed to save proxy: {e}");
|
eprintln!("Warning: Failed to save proxy: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit event for reactive UI updates
|
||||||
|
if let Err(e) = app_handle.emit("proxies-changed", ()) {
|
||||||
|
eprintln!("Failed to emit proxies-changed event: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(stored_proxy)
|
Ok(stored_proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +189,7 @@ impl ProxyManager {
|
|||||||
// Update a stored proxy
|
// Update a stored proxy
|
||||||
pub fn update_stored_proxy(
|
pub fn update_stored_proxy(
|
||||||
&self,
|
&self,
|
||||||
|
app_handle: &tauri::AppHandle,
|
||||||
proxy_id: &str,
|
proxy_id: &str,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
proxy_settings: Option<ProxySettings>,
|
proxy_settings: Option<ProxySettings>,
|
||||||
@@ -226,11 +234,16 @@ impl ProxyManager {
|
|||||||
eprintln!("Warning: Failed to save proxy: {e}");
|
eprintln!("Warning: Failed to save proxy: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit event for reactive UI updates
|
||||||
|
if let Err(e) = app_handle.emit("proxies-changed", ()) {
|
||||||
|
eprintln!("Failed to emit proxies-changed event: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(updated_proxy)
|
Ok(updated_proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete a stored proxy
|
// Delete a stored proxy
|
||||||
pub fn delete_stored_proxy(&self, proxy_id: &str) -> Result<(), String> {
|
pub fn delete_stored_proxy(&self, app_handle: &tauri::AppHandle, proxy_id: &str) -> Result<(), String> {
|
||||||
{
|
{
|
||||||
let mut stored_proxies = self.stored_proxies.lock().unwrap();
|
let mut stored_proxies = self.stored_proxies.lock().unwrap();
|
||||||
if stored_proxies.remove(proxy_id).is_none() {
|
if stored_proxies.remove(proxy_id).is_none() {
|
||||||
@@ -242,6 +255,11 @@ impl ProxyManager {
|
|||||||
eprintln!("Warning: Failed to delete proxy file: {e}");
|
eprintln!("Warning: Failed to delete proxy file: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit event for reactive UI updates
|
||||||
|
if let Err(e) = app_handle.emit("proxies-changed", ()) {
|
||||||
|
eprintln!("Failed to emit proxies-changed event: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,6 +532,11 @@ impl ProxyManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit event for reactive UI updates
|
||||||
|
if let Err(e) = app_handle.emit("proxies-changed", ()) {
|
||||||
|
eprintln!("Failed to emit proxies-changed event: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,6 +577,11 @@ impl ProxyManager {
|
|||||||
let _ = self.stop_proxy(app_handle.clone(), *dead_pid).await;
|
let _ = self.stop_proxy(app_handle.clone(), *dead_pid).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit event for reactive UI updates
|
||||||
|
if let Err(e) = app_handle.emit("proxies-changed", ()) {
|
||||||
|
eprintln!("Failed to emit proxies-changed event: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(dead_pids)
|
Ok(dead_pids)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { useBrowserState } from "@/hooks/use-browser-state";
|
import { useBrowserState } from "@/hooks/use-browser-state";
|
||||||
|
import { useProxyEvents } from "@/hooks/use-proxy-events";
|
||||||
import { useTableSorting } from "@/hooks/use-table-sorting";
|
import { useTableSorting } from "@/hooks/use-table-sorting";
|
||||||
import {
|
import {
|
||||||
getBrowserDisplayName,
|
getBrowserDisplayName,
|
||||||
@@ -413,10 +414,8 @@ export function ProfilesDataTable({
|
|||||||
new Set(),
|
new Set(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [storedProxies, setStoredProxies] = React.useState<StoredProxy[]>([]);
|
const { storedProxies } = useProxyEvents();
|
||||||
const [openProxySelectorFor, setOpenProxySelectorFor] = React.useState<
|
|
||||||
string | null
|
|
||||||
>(null);
|
|
||||||
const [proxyOverrides, setProxyOverrides] = React.useState<
|
const [proxyOverrides, setProxyOverrides] = React.useState<
|
||||||
Record<string, string | null>
|
Record<string, string | null>
|
||||||
>({});
|
>({});
|
||||||
@@ -428,6 +427,9 @@ export function ProfilesDataTable({
|
|||||||
const [openTagsEditorFor, setOpenTagsEditorFor] = React.useState<
|
const [openTagsEditorFor, setOpenTagsEditorFor] = React.useState<
|
||||||
string | null
|
string | null
|
||||||
>(null);
|
>(null);
|
||||||
|
const [openProxySelectorFor, setOpenProxySelectorFor] = React.useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
const loadAllTags = React.useCallback(async () => {
|
const loadAllTags = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -466,16 +468,6 @@ export function ProfilesDataTable({
|
|||||||
stoppingProfiles,
|
stoppingProfiles,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load stored proxies
|
|
||||||
const loadStoredProxies = React.useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const proxiesList = await invoke<StoredProxy[]>("get_stored_proxies");
|
|
||||||
setStoredProxies(proxiesList);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load stored proxies:", error);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Clear launching/stopping spinners when backend reports running status changes
|
// Clear launching/stopping spinners when backend reports running status changes
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!browserState.isClient) return;
|
if (!browserState.isClient) return;
|
||||||
@@ -511,12 +503,6 @@ export function ProfilesDataTable({
|
|||||||
};
|
};
|
||||||
}, [browserState.isClient]);
|
}, [browserState.isClient]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (browserState.isClient) {
|
|
||||||
void loadStoredProxies();
|
|
||||||
}
|
|
||||||
}, [browserState.isClient, loadStoredProxies]);
|
|
||||||
|
|
||||||
// Keep stored proxies up-to-date by listening for changes emitted elsewhere in the app
|
// Keep stored proxies up-to-date by listening for changes emitted elsewhere in the app
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!browserState.isClient) return;
|
if (!browserState.isClient) return;
|
||||||
@@ -524,10 +510,7 @@ export function ProfilesDataTable({
|
|||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
unlisten = await listen("stored-proxies-changed", () => {
|
unlisten = await listen("stored-proxies-changed", () => {
|
||||||
void loadStoredProxies();
|
// Also refresh tags on profile updates
|
||||||
});
|
|
||||||
// Also refresh tags on profile updates
|
|
||||||
await listen("profile-updated", () => {
|
|
||||||
void loadAllTags();
|
void loadAllTags();
|
||||||
});
|
});
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
@@ -537,7 +520,7 @@ export function ProfilesDataTable({
|
|||||||
return () => {
|
return () => {
|
||||||
if (unlisten) unlisten();
|
if (unlisten) unlisten();
|
||||||
};
|
};
|
||||||
}, [browserState.isClient, loadStoredProxies, loadAllTags]);
|
}, [browserState.isClient, loadAllTags]);
|
||||||
|
|
||||||
// Automatically deselect profiles that become running, updating, launching, or stopping
|
// Automatically deselect profiles that become running, updating, launching, or stopping
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { useBrowserState } from "@/hooks/use-browser-state";
|
import { useBrowserState } from "@/hooks/use-browser-state";
|
||||||
import { useProfileEvents } from "@/hooks/use-profile-events";
|
import { useProfileEvents } from "@/hooks/use-profile-events";
|
||||||
|
import { useProxyEvents } from "@/hooks/use-proxy-events";
|
||||||
import { getBrowserDisplayName, getBrowserIcon } from "@/lib/browser-utils";
|
import { getBrowserDisplayName, getBrowserIcon } from "@/lib/browser-utils";
|
||||||
import type { BrowserProfile, StoredProxy } from "@/types";
|
import type { BrowserProfile } from "@/types";
|
||||||
import { RippleButton } from "./ui/ripple";
|
import { RippleButton } from "./ui/ripple";
|
||||||
|
|
||||||
interface ProfileSelectorDialogProps {
|
interface ProfileSelectorDialogProps {
|
||||||
@@ -53,9 +54,10 @@ export function ProfileSelectorDialog({
|
|||||||
// Use external runningProfiles if provided, otherwise use hook's runningProfiles
|
// Use external runningProfiles if provided, otherwise use hook's runningProfiles
|
||||||
const runningProfiles = externalRunningProfiles || hookRunningProfiles;
|
const runningProfiles = externalRunningProfiles || hookRunningProfiles;
|
||||||
|
|
||||||
|
const { storedProxies } = useProxyEvents();
|
||||||
|
|
||||||
const [selectedProfile, setSelectedProfile] = useState<string | null>(null);
|
const [selectedProfile, setSelectedProfile] = useState<string | null>(null);
|
||||||
const [isLaunching, setIsLaunching] = useState(false);
|
const [isLaunching, setIsLaunching] = useState(false);
|
||||||
const [storedProxies, setStoredProxies] = useState<StoredProxy[]>([]);
|
|
||||||
const [launchingProfiles, setLaunchingProfiles] = useState<Set<string>>(
|
const [launchingProfiles, setLaunchingProfiles] = useState<Set<string>>(
|
||||||
new Set(),
|
new Set(),
|
||||||
);
|
);
|
||||||
@@ -82,16 +84,6 @@ export function ProfileSelectorDialog({
|
|||||||
[storedProxies],
|
[storedProxies],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load stored proxies
|
|
||||||
const loadStoredProxies = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const proxiesList = await invoke<StoredProxy[]>("get_stored_proxies");
|
|
||||||
setStoredProxies(proxiesList);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to load stored proxies:", err);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Helper function to get tooltip content for profiles - now uses shared hook
|
// Helper function to get tooltip content for profiles - now uses shared hook
|
||||||
const getProfileTooltipContent = (profile: BrowserProfile): string | null => {
|
const getProfileTooltipContent = (profile: BrowserProfile): string | null => {
|
||||||
return browserState.getProfileTooltipContent(profile);
|
return browserState.getProfileTooltipContent(profile);
|
||||||
@@ -182,13 +174,6 @@ export function ProfileSelectorDialog({
|
|||||||
}
|
}
|
||||||
}, [isOpen, profiles, selectedProfile, runningProfiles]);
|
}, [isOpen, profiles, selectedProfile, runningProfiles]);
|
||||||
|
|
||||||
// Load stored proxies when dialog opens
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen) {
|
|
||||||
void loadStoredProxies();
|
|
||||||
}
|
|
||||||
}, [isOpen, loadStoredProxies]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||||
<DialogContent className="max-w-md">
|
<DialogContent className="max-w-md">
|
||||||
|
|||||||
@@ -35,14 +35,12 @@ interface ProxyFormData {
|
|||||||
interface ProxyFormDialogProps {
|
interface ProxyFormDialogProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSave: (proxy: StoredProxy) => void;
|
|
||||||
editingProxy?: StoredProxy | null;
|
editingProxy?: StoredProxy | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProxyFormDialog({
|
export function ProxyFormDialog({
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
onSave,
|
|
||||||
editingProxy,
|
editingProxy,
|
||||||
}: ProxyFormDialogProps) {
|
}: ProxyFormDialogProps) {
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
@@ -105,11 +103,9 @@ export function ProxyFormDialog({
|
|||||||
password: formData.password.trim() || undefined,
|
password: formData.password.trim() || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
let savedProxy: StoredProxy;
|
|
||||||
|
|
||||||
if (editingProxy) {
|
if (editingProxy) {
|
||||||
// Update existing proxy
|
// Update existing proxy
|
||||||
savedProxy = await invoke<StoredProxy>("update_stored_proxy", {
|
await invoke("update_stored_proxy", {
|
||||||
proxyId: editingProxy.id,
|
proxyId: editingProxy.id,
|
||||||
name: formData.name.trim(),
|
name: formData.name.trim(),
|
||||||
proxySettings,
|
proxySettings,
|
||||||
@@ -117,14 +113,13 @@ export function ProxyFormDialog({
|
|||||||
toast.success("Proxy updated successfully");
|
toast.success("Proxy updated successfully");
|
||||||
} else {
|
} else {
|
||||||
// Create new proxy
|
// Create new proxy
|
||||||
savedProxy = await invoke<StoredProxy>("create_stored_proxy", {
|
await invoke("create_stored_proxy", {
|
||||||
name: formData.name.trim(),
|
name: formData.name.trim(),
|
||||||
proxySettings,
|
proxySettings,
|
||||||
});
|
});
|
||||||
toast.success("Proxy created successfully");
|
toast.success("Proxy created successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave(savedProxy);
|
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to save proxy:", error);
|
console.error("Failed to save proxy:", error);
|
||||||
@@ -134,7 +129,7 @@ export function ProxyFormDialog({
|
|||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
}, [formData, editingProxy, onSave, onClose]);
|
}, [formData, editingProxy, onClose]);
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
if (!isSubmitting) {
|
if (!isSubmitting) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
|
import { useProxyEvents } from "@/hooks/use-proxy-events";
|
||||||
import { trimName } from "@/lib/name-utils";
|
import { trimName } from "@/lib/name-utils";
|
||||||
import type { StoredProxy } from "@/types";
|
import type { StoredProxy } from "@/types";
|
||||||
import { RippleButton } from "./ui/ripple";
|
import { RippleButton } from "./ui/ripple";
|
||||||
@@ -34,84 +35,12 @@ export function ProxyManagementDialog({
|
|||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
}: ProxyManagementDialogProps) {
|
}: ProxyManagementDialogProps) {
|
||||||
const [storedProxies, setStoredProxies] = useState<StoredProxy[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [showProxyForm, setShowProxyForm] = useState(false);
|
const [showProxyForm, setShowProxyForm] = useState(false);
|
||||||
const [editingProxy, setEditingProxy] = useState<StoredProxy | null>(null);
|
const [editingProxy, setEditingProxy] = useState<StoredProxy | null>(null);
|
||||||
const [proxyUsage, setProxyUsage] = useState<Record<string, number>>({});
|
|
||||||
const [proxyToDelete, setProxyToDelete] = useState<StoredProxy | null>(null);
|
const [proxyToDelete, setProxyToDelete] = useState<StoredProxy | null>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
|
||||||
const loadStoredProxies = useCallback(async () => {
|
const { storedProxies, proxyUsage, isLoading } = useProxyEvents();
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const proxies = await invoke<StoredProxy[]>("get_stored_proxies");
|
|
||||||
setStoredProxies(proxies);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load stored proxies:", error);
|
|
||||||
toast.error("Failed to load proxies");
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const loadProxyUsage = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const profiles = await invoke<Array<{ proxy_id?: string }>>(
|
|
||||||
"list_browser_profiles",
|
|
||||||
);
|
|
||||||
const counts: Record<string, number> = {};
|
|
||||||
for (const p of profiles) {
|
|
||||||
if (p.proxy_id) counts[p.proxy_id] = (counts[p.proxy_id] ?? 0) + 1;
|
|
||||||
}
|
|
||||||
setProxyUsage(counts);
|
|
||||||
} catch (_err) {
|
|
||||||
// ignore non-critical errors
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen) {
|
|
||||||
loadStoredProxies();
|
|
||||||
void loadProxyUsage();
|
|
||||||
}
|
|
||||||
}, [isOpen, loadStoredProxies, loadProxyUsage]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let unlisten: (() => void) | undefined;
|
|
||||||
const setup = async () => {
|
|
||||||
try {
|
|
||||||
unlisten = await listen("profile-updated", () => {
|
|
||||||
void loadProxyUsage();
|
|
||||||
});
|
|
||||||
} catch (_err) {
|
|
||||||
// ignore non-critical errors
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (isOpen) void setup();
|
|
||||||
return () => {
|
|
||||||
if (unlisten) unlisten();
|
|
||||||
};
|
|
||||||
}, [isOpen, loadProxyUsage]);
|
|
||||||
|
|
||||||
// Keep list in sync with external changes (e.g., created from CreateProfileDialog)
|
|
||||||
useEffect(() => {
|
|
||||||
let unlisten: (() => void) | undefined;
|
|
||||||
const setup = async () => {
|
|
||||||
try {
|
|
||||||
unlisten = await listen("stored-proxies-changed", () => {
|
|
||||||
void loadStoredProxies();
|
|
||||||
void loadProxyUsage();
|
|
||||||
});
|
|
||||||
} catch (_err) {
|
|
||||||
// ignore non-critical errors
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (isOpen) void setup();
|
|
||||||
return () => {
|
|
||||||
if (unlisten) unlisten();
|
|
||||||
};
|
|
||||||
}, [isOpen, loadStoredProxies, loadProxyUsage]);
|
|
||||||
|
|
||||||
const handleDeleteProxy = useCallback((proxy: StoredProxy) => {
|
const handleDeleteProxy = useCallback((proxy: StoredProxy) => {
|
||||||
// Open in-app confirmation dialog
|
// Open in-app confirmation dialog
|
||||||
@@ -123,7 +52,6 @@ export function ProxyManagementDialog({
|
|||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await invoke("delete_stored_proxy", { proxyId: proxyToDelete.id });
|
await invoke("delete_stored_proxy", { proxyId: proxyToDelete.id });
|
||||||
setStoredProxies((prev) => prev.filter((p) => p.id !== proxyToDelete.id));
|
|
||||||
toast.success("Proxy deleted successfully");
|
toast.success("Proxy deleted successfully");
|
||||||
await emit("stored-proxies-changed");
|
await emit("stored-proxies-changed");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -145,24 +73,6 @@ export function ProxyManagementDialog({
|
|||||||
setShowProxyForm(true);
|
setShowProxyForm(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleProxySaved = useCallback((savedProxy: StoredProxy) => {
|
|
||||||
setStoredProxies((prev) => {
|
|
||||||
const existingIndex = prev.findIndex((p) => p.id === savedProxy.id);
|
|
||||||
if (existingIndex >= 0) {
|
|
||||||
// Update existing proxy
|
|
||||||
const updated = [...prev];
|
|
||||||
updated[existingIndex] = savedProxy;
|
|
||||||
return updated;
|
|
||||||
} else {
|
|
||||||
// Add new proxy
|
|
||||||
return [...prev, savedProxy];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setShowProxyForm(false);
|
|
||||||
setEditingProxy(null);
|
|
||||||
void emit("stored-proxies-changed");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleProxyFormClose = useCallback(() => {
|
const handleProxyFormClose = useCallback(() => {
|
||||||
setShowProxyForm(false);
|
setShowProxyForm(false);
|
||||||
setEditingProxy(null);
|
setEditingProxy(null);
|
||||||
@@ -200,13 +110,12 @@ export function ProxyManagementDialog({
|
|||||||
|
|
||||||
{/* Proxy List - Scrollable */}
|
{/* Proxy List - Scrollable */}
|
||||||
<div className="flex-1 min-h-0">
|
<div className="flex-1 min-h-0">
|
||||||
{loading ? (
|
{isLoading && (
|
||||||
<div className="flex justify-center items-center h-32">
|
<div className="flex justify-center items-center py-6">
|
||||||
<p className="text-sm text-muted-foreground">
|
<div className="w-8 h-8 rounded-full border-b-2 animate-spin border-primary"></div>
|
||||||
Loading proxies...
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
) : storedProxies.length === 0 ? (
|
)}
|
||||||
|
{storedProxies.length === 0 && !isLoading ? (
|
||||||
<div className="flex flex-col justify-center items-center h-32 text-center">
|
<div className="flex flex-col justify-center items-center h-32 text-center">
|
||||||
<FiWifi className="mx-auto mb-4 w-12 h-12 text-muted-foreground" />
|
<FiWifi className="mx-auto mb-4 w-12 h-12 text-muted-foreground" />
|
||||||
<p className="mb-2 text-muted-foreground">
|
<p className="mb-2 text-muted-foreground">
|
||||||
@@ -310,7 +219,6 @@ export function ProxyManagementDialog({
|
|||||||
<ProxyFormDialog
|
<ProxyFormDialog
|
||||||
isOpen={showProxyForm}
|
isOpen={showProxyForm}
|
||||||
onClose={handleProxyFormClose}
|
onClose={handleProxyFormClose}
|
||||||
onSave={handleProxySaved}
|
|
||||||
editingProxy={editingProxy}
|
editingProxy={editingProxy}
|
||||||
/>
|
/>
|
||||||
<DeleteConfirmationDialog
|
<DeleteConfirmationDialog
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import type { StoredProxy } from "@/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook to manage proxy-related state and listen for backend events.
|
||||||
|
* This hook eliminates the need for manual UI refreshes by automatically
|
||||||
|
* updating state when the backend emits proxy change events.
|
||||||
|
*/
|
||||||
|
export function useProxyEvents() {
|
||||||
|
const [storedProxies, setStoredProxies] = useState<StoredProxy[]>([]);
|
||||||
|
const [proxyUsage, setProxyUsage] = useState<Record<string, number>>({});
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Load proxy usage (how many profiles are using each proxy)
|
||||||
|
const loadProxyUsage = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const profiles = await invoke<Array<{ proxy_id?: string }>>(
|
||||||
|
"list_browser_profiles",
|
||||||
|
);
|
||||||
|
const counts: Record<string, number> = {};
|
||||||
|
for (const p of profiles) {
|
||||||
|
if (p.proxy_id) counts[p.proxy_id] = (counts[p.proxy_id] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
setProxyUsage(counts);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load proxy usage:", err);
|
||||||
|
// Don't set error for non-critical proxy usage
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Load proxies from backend
|
||||||
|
const loadProxies = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const stored = await invoke<StoredProxy[]>("get_stored_proxies");
|
||||||
|
setStoredProxies(stored);
|
||||||
|
await loadProxyUsage();
|
||||||
|
setError(null);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error("Failed to load proxies:", err);
|
||||||
|
setError(`Failed to load proxies: ${JSON.stringify(err)}`);
|
||||||
|
}
|
||||||
|
}, [loadProxyUsage]);
|
||||||
|
|
||||||
|
// Clear error state
|
||||||
|
const clearError = useCallback(() => {
|
||||||
|
setError(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Initial load and event listeners setup
|
||||||
|
useEffect(() => {
|
||||||
|
let proxiesUnlisten: (() => void) | undefined;
|
||||||
|
let profilesUnlisten: (() => void) | undefined;
|
||||||
|
let storedProxiesUnlisten: (() => void) | undefined;
|
||||||
|
|
||||||
|
const setupListeners = async () => {
|
||||||
|
try {
|
||||||
|
// Initial load
|
||||||
|
await loadProxies();
|
||||||
|
|
||||||
|
// Listen for proxy changes (create, delete, update, start, stop, etc.)
|
||||||
|
proxiesUnlisten = await listen("proxies-changed", () => {
|
||||||
|
console.log("Received proxies-changed event, reloading proxies");
|
||||||
|
void loadProxies();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for profile changes to update proxy usage counts
|
||||||
|
profilesUnlisten = await listen("profiles-changed", () => {
|
||||||
|
console.log("Received profiles-changed event, reloading proxy usage");
|
||||||
|
void loadProxyUsage();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for profile updates to update proxy usage counts
|
||||||
|
storedProxiesUnlisten = await listen("stored-proxies-changed", () => {
|
||||||
|
console.log(
|
||||||
|
"Received stored-proxies-changed event, reloading proxies",
|
||||||
|
);
|
||||||
|
void loadProxies();
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Proxy event listeners set up successfully");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to setup proxy event listeners:", err);
|
||||||
|
setError(
|
||||||
|
`Failed to setup proxy event listeners: ${JSON.stringify(err)}`,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void setupListeners();
|
||||||
|
|
||||||
|
// Cleanup listeners on unmount
|
||||||
|
return () => {
|
||||||
|
if (proxiesUnlisten) proxiesUnlisten();
|
||||||
|
if (profilesUnlisten) profilesUnlisten();
|
||||||
|
if (storedProxiesUnlisten) storedProxiesUnlisten();
|
||||||
|
};
|
||||||
|
}, [loadProxies, loadProxyUsage]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
storedProxies,
|
||||||
|
proxyUsage,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
loadProxies,
|
||||||
|
clearError,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in new issue
Block a user