Add VPN proxy connection handling
This commit is contained in:
@@ -1,20 +1,25 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { Cpu, FileCode2, FolderOpen } from 'lucide-react';
|
||||
import { Cpu, FileCode2, FolderOpen, MoreHorizontal } from 'lucide-react';
|
||||
import {
|
||||
applyProfiles,
|
||||
getComponents,
|
||||
getProxiFyreSetupStatus,
|
||||
getSavedState,
|
||||
installProxiFyre,
|
||||
openConfigLocation,
|
||||
saveProfile,
|
||||
saveTarget,
|
||||
startProxiFyreService,
|
||||
stopProxiFyreService,
|
||||
uninstallProxiFyre,
|
||||
type ApplyProfilesResponse,
|
||||
type ProxiFyreSetupStatus,
|
||||
} from '../api/tauriCommands';
|
||||
import type { ComponentStatus, Profile, ProfileItemInput, ProfileItemType, Target } from '../domain/types';
|
||||
|
||||
type DraftItemType = Extract<ProfileItemType, 'process' | 'folder' | 'exe'>;
|
||||
type ProxiFyreAction = 'start' | 'stop' | 'install' | 'uninstall';
|
||||
type ServiceVisualState = 'active' | 'settling' | null;
|
||||
|
||||
interface DraftItem {
|
||||
@@ -60,6 +65,8 @@ export function App() {
|
||||
const [processInput, setProcessInput] = useState('');
|
||||
const [pickerAction, setPickerAction] = useState<'exe' | 'folder' | null>(null);
|
||||
const [components, setComponents] = useState<ComponentStatus[]>(fallbackComponents);
|
||||
const [setupStatus, setSetupStatus] = useState<ProxiFyreSetupStatus | null>(null);
|
||||
const [isSetupOpen, setIsSetupOpen] = useState(false);
|
||||
const [generatedConfigPath, setGeneratedConfigPath] = useState('');
|
||||
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
|
||||
const [activeLogId, setActiveLogId] = useState<string | null>(null);
|
||||
@@ -68,7 +75,8 @@ export function App() {
|
||||
const [isDetectingComponents, setIsDetectingComponents] = useState(true);
|
||||
const [isApplying, setIsApplying] = useState(false);
|
||||
const [isOpeningConfig, setIsOpeningConfig] = useState(false);
|
||||
const [serviceAction, setServiceAction] = useState<'start' | 'stop' | null>(null);
|
||||
const [serviceAction, setServiceAction] = useState<ProxiFyreAction | null>(null);
|
||||
const [isServiceMenuOpen, setIsServiceMenuOpen] = useState(false);
|
||||
const [serviceVisualState, setServiceVisualState] = useState<ServiceVisualState>(null);
|
||||
const serviceVisualTimerRef = useRef<number | null>(null);
|
||||
|
||||
@@ -127,8 +135,12 @@ export function App() {
|
||||
async function refreshComponents() {
|
||||
setIsDetectingComponents(true);
|
||||
try {
|
||||
const detectedComponents = await getComponents();
|
||||
const [detectedComponents, detectedSetupStatus] = await Promise.all([
|
||||
getComponents(),
|
||||
getProxiFyreSetupStatus(),
|
||||
]);
|
||||
setComponents(detectedComponents);
|
||||
setSetupStatus(detectedSetupStatus);
|
||||
} catch (error) {
|
||||
showNotice({
|
||||
kind: 'error',
|
||||
@@ -254,13 +266,15 @@ export function App() {
|
||||
);
|
||||
|
||||
const result = await applyProfiles();
|
||||
const [saved, detectedComponents] = await Promise.all([
|
||||
const [saved, detectedComponents, detectedSetupStatus] = await Promise.all([
|
||||
getSavedState(),
|
||||
getComponents(),
|
||||
getProxiFyreSetupStatus(),
|
||||
]);
|
||||
|
||||
applySavedState(saved.profiles, saved.targets, result.generatedConfigPath);
|
||||
setComponents(detectedComponents);
|
||||
setSetupStatus(detectedSetupStatus);
|
||||
showNotice(noticeFromApply(result));
|
||||
} catch (error) {
|
||||
showNotice({
|
||||
@@ -296,6 +310,7 @@ export function App() {
|
||||
async function setProxiFyreServiceRunning(shouldRun: boolean) {
|
||||
const action = shouldRun ? 'start' : 'stop';
|
||||
setServiceAction(action);
|
||||
setIsServiceMenuOpen(false);
|
||||
startServiceVisual();
|
||||
try {
|
||||
await nextFrame();
|
||||
@@ -321,6 +336,65 @@ export function App() {
|
||||
}
|
||||
}
|
||||
|
||||
async function installProxiFyrePackage() {
|
||||
setServiceAction('install');
|
||||
setIsServiceMenuOpen(false);
|
||||
startServiceVisual();
|
||||
try {
|
||||
await nextFrame();
|
||||
const component = await installProxiFyre();
|
||||
const detectedSetupStatus = await getProxiFyreSetupStatus();
|
||||
setComponents((current) => upsertComponent(current, component));
|
||||
setSetupStatus(detectedSetupStatus);
|
||||
showNotice({
|
||||
kind: 'success',
|
||||
title: 'ProxiFyre установлен',
|
||||
text: proxyfierDetails(component, false),
|
||||
});
|
||||
} catch (error) {
|
||||
showNotice({
|
||||
kind: 'error',
|
||||
title: 'ProxiFyre не установлен',
|
||||
text: errorMessage(error),
|
||||
});
|
||||
} finally {
|
||||
setServiceAction(null);
|
||||
settleServiceVisual();
|
||||
}
|
||||
}
|
||||
|
||||
async function uninstallProxiFyrePackage() {
|
||||
const confirmed = window.confirm(
|
||||
'Удалить ProxiFyre с компьютера? Будет удалена служба и папка установки ProxiFyre.',
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
setServiceAction('uninstall');
|
||||
setIsServiceMenuOpen(false);
|
||||
startServiceVisual();
|
||||
try {
|
||||
await nextFrame();
|
||||
const component = await uninstallProxiFyre();
|
||||
const detectedSetupStatus = await getProxiFyreSetupStatus();
|
||||
setComponents((current) => upsertComponent(current, component));
|
||||
setSetupStatus(detectedSetupStatus);
|
||||
showNotice({
|
||||
kind: 'success',
|
||||
title: 'ProxiFyre удален',
|
||||
text: 'Служба и папка установки ProxiFyre удалены.',
|
||||
});
|
||||
} catch (error) {
|
||||
showNotice({
|
||||
kind: 'error',
|
||||
title: 'ProxiFyre не удален',
|
||||
text: errorMessage(error),
|
||||
});
|
||||
} finally {
|
||||
setServiceAction(null);
|
||||
settleServiceVisual();
|
||||
}
|
||||
}
|
||||
|
||||
function startServiceVisual() {
|
||||
if (serviceVisualTimerRef.current !== null) {
|
||||
window.clearTimeout(serviceVisualTimerRef.current);
|
||||
@@ -382,25 +456,89 @@ export function App() {
|
||||
<div className="finder-text">
|
||||
<strong>{proxyfierTitle(proxyfier, isDetectingComponents)}</strong>
|
||||
<span>{proxyfierDetails(proxyfier, isDetectingComponents)}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="setup-toggle"
|
||||
onClick={() => setIsSetupOpen((current) => !current)}
|
||||
disabled={isDetectingComponents && !setupStatus}
|
||||
aria-expanded={isSetupOpen}
|
||||
>
|
||||
{proxyfier?.installed ? 'Состав ProxiFyre' : 'Что будет установлено'}
|
||||
{setupStatus ? (
|
||||
<span>{setupStatus.ready ? 'все есть' : `не хватает: ${setupStatus.missingCount}`}</span>
|
||||
) : null}
|
||||
</button>
|
||||
</div>
|
||||
<div className="service-actions" aria-label="Управление службой ProxiFyre">
|
||||
<button
|
||||
type="button"
|
||||
className="service-button"
|
||||
onClick={() => setProxiFyreServiceRunning(true)}
|
||||
disabled={isDetectingComponents || Boolean(serviceAction) || !proxyfier?.installed || Boolean(proxyfier?.running)}
|
||||
>
|
||||
{serviceAction === 'start' ? '...' : 'Запустить'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="service-button stop"
|
||||
onClick={() => setProxiFyreServiceRunning(false)}
|
||||
disabled={isDetectingComponents || Boolean(serviceAction) || !proxyfier?.installed || !proxyfier?.running}
|
||||
>
|
||||
{serviceAction === 'stop' ? '...' : 'Остановить'}
|
||||
</button>
|
||||
{proxyfier?.installed ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className={`service-button ${proxyfier.running ? 'stop' : ''}`.trim()}
|
||||
onClick={() => setProxiFyreServiceRunning(!proxyfier.running)}
|
||||
disabled={isDetectingComponents || Boolean(serviceAction)}
|
||||
>
|
||||
{serviceAction === 'start' || serviceAction === 'stop'
|
||||
? '...'
|
||||
: proxyfier.running
|
||||
? 'Остановить'
|
||||
: 'Запустить'}
|
||||
</button>
|
||||
<div className="service-menu">
|
||||
<button
|
||||
type="button"
|
||||
className="service-menu-button"
|
||||
onClick={() => setIsServiceMenuOpen((current) => !current)}
|
||||
disabled={isDetectingComponents || Boolean(serviceAction)}
|
||||
aria-label="Дополнительные действия ProxiFyre"
|
||||
aria-expanded={isServiceMenuOpen}
|
||||
title="Еще"
|
||||
>
|
||||
<MoreHorizontal size={20} strokeWidth={2} />
|
||||
</button>
|
||||
{isServiceMenuOpen ? (
|
||||
<div className="service-menu-popover">
|
||||
<button type="button" onClick={() => void uninstallProxiFyrePackage()}>
|
||||
{serviceAction === 'uninstall' ? 'Удаляю...' : 'Удалить ProxiFyre'}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="service-button install"
|
||||
onClick={() => void installProxiFyrePackage()}
|
||||
disabled={isDetectingComponents || Boolean(serviceAction)}
|
||||
>
|
||||
{serviceAction === 'install' ? '...' : 'Установить'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{isSetupOpen ? (
|
||||
<div className="setup-details">
|
||||
{setupStatus ? (
|
||||
setupStatus.items.map((item) => (
|
||||
<div className={`setup-item ${item.installed ? 'installed' : 'missing'}`} key={item.id}>
|
||||
<span className="setup-state-dot" aria-hidden="true" />
|
||||
<div>
|
||||
<strong>{item.name}</strong>
|
||||
<span>{setupItemDetails(item.installed, item.version, item.details)}</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="setup-item">
|
||||
<span className="setup-state-dot" aria-hidden="true" />
|
||||
<div>
|
||||
<strong>Проверяю состав</strong>
|
||||
<span>Ищу установленные зависимости ProxiFyre.</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<label className="simple-field">
|
||||
@@ -699,6 +837,12 @@ function itemIcon(type: DraftItemType) {
|
||||
return <FileCode2 size={18} strokeWidth={1.9} />;
|
||||
}
|
||||
|
||||
function setupItemDetails(installed: boolean, version: string | undefined, details: string) {
|
||||
if (!installed) return `Нужно установить. ${details}`;
|
||||
if (version) return `${version}. ${details}`;
|
||||
return details;
|
||||
}
|
||||
|
||||
function profileInputFromProfile(profile: Profile, enabled: boolean) {
|
||||
return {
|
||||
id: profile.id,
|
||||
|
||||
Reference in New Issue
Block a user