Refactor proxy handling and update related UI flows
This commit is contained in:
@@ -219,6 +219,13 @@ pub struct PingSingBoxServerInputDto {
|
|||||||
pub tag: String,
|
pub tag: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PingProxyTargetInputDto {
|
||||||
|
pub host: String,
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PingServerResponse {
|
pub struct PingServerResponse {
|
||||||
@@ -619,6 +626,11 @@ pub fn ping_all_singbox_servers(
|
|||||||
ping_all_singbox_servers_in_storage(&state.storage())
|
ping_all_singbox_servers_in_storage(&state.storage())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn ping_proxy_target(input: PingProxyTargetInputDto) -> Result<PingServerResponse, CommandError> {
|
||||||
|
ping_proxy_target_endpoint(input)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn generate_singbox_config(
|
pub fn generate_singbox_config(
|
||||||
state: tauri::State<'_, CommandState>,
|
state: tauri::State<'_, CommandState>,
|
||||||
@@ -1115,6 +1127,20 @@ pub fn ping_all_singbox_servers_in_storage(
|
|||||||
Ok(cache.servers.iter().map(ping_subscription_server).collect())
|
Ok(cache.servers.iter().map(ping_subscription_server).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ping_proxy_target_endpoint(
|
||||||
|
input: PingProxyTargetInputDto,
|
||||||
|
) -> Result<PingServerResponse, CommandError> {
|
||||||
|
let host = input.host.trim();
|
||||||
|
if host.is_empty() {
|
||||||
|
return Err(CommandError::new(
|
||||||
|
"proxy_target_host_missing",
|
||||||
|
"Хост внешнего прокси не указан.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ping_endpoint("external-proxy", host, input.port))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate_singbox_config_with_services<C>(
|
pub fn generate_singbox_config_with_services<C>(
|
||||||
storage: &JsonStorage,
|
storage: &JsonStorage,
|
||||||
adapter: &SingBoxAdapter,
|
adapter: &SingBoxAdapter,
|
||||||
@@ -1196,14 +1222,18 @@ fn validate_subscription_url(subscription_url: &str) -> Result<(), CommandError>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ping_subscription_server(server: &SubscriptionServer) -> PingServerResponse {
|
fn ping_subscription_server(server: &SubscriptionServer) -> PingServerResponse {
|
||||||
|
ping_endpoint(&server.tag, &server.server, server.server_port)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping_endpoint(tag: &str, server: &str, server_port: u16) -> PingServerResponse {
|
||||||
let started = Instant::now();
|
let started = Instant::now();
|
||||||
let addresses = match (server.server.as_str(), server.server_port).to_socket_addrs() {
|
let addresses = match (server, server_port).to_socket_addrs() {
|
||||||
Ok(addresses) => addresses.collect::<Vec<_>>(),
|
Ok(addresses) => addresses.collect::<Vec<_>>(),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
return PingServerResponse {
|
return PingServerResponse {
|
||||||
tag: server.tag.clone(),
|
tag: tag.to_string(),
|
||||||
server: server.server.clone(),
|
server: server.to_string(),
|
||||||
server_port: server.server_port,
|
server_port,
|
||||||
ok: false,
|
ok: false,
|
||||||
latency: None,
|
latency: None,
|
||||||
error: Some(format!("DNS/адрес недоступен: {error}")),
|
error: Some(format!("DNS/адрес недоступен: {error}")),
|
||||||
@@ -1213,9 +1243,9 @@ fn ping_subscription_server(server: &SubscriptionServer) -> PingServerResponse {
|
|||||||
|
|
||||||
if addresses.is_empty() {
|
if addresses.is_empty() {
|
||||||
return PingServerResponse {
|
return PingServerResponse {
|
||||||
tag: server.tag.clone(),
|
tag: tag.to_string(),
|
||||||
server: server.server.clone(),
|
server: server.to_string(),
|
||||||
server_port: server.server_port,
|
server_port,
|
||||||
ok: false,
|
ok: false,
|
||||||
latency: None,
|
latency: None,
|
||||||
error: Some("DNS не вернул адреса".to_string()),
|
error: Some("DNS не вернул адреса".to_string()),
|
||||||
@@ -1228,9 +1258,9 @@ fn ping_subscription_server(server: &SubscriptionServer) -> PingServerResponse {
|
|||||||
match TcpStream::connect_timeout(&address, timeout) {
|
match TcpStream::connect_timeout(&address, timeout) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
return PingServerResponse {
|
return PingServerResponse {
|
||||||
tag: server.tag.clone(),
|
tag: tag.to_string(),
|
||||||
server: server.server.clone(),
|
server: server.to_string(),
|
||||||
server_port: server.server_port,
|
server_port,
|
||||||
ok: true,
|
ok: true,
|
||||||
latency: Some(started.elapsed().as_millis()),
|
latency: Some(started.elapsed().as_millis()),
|
||||||
error: None,
|
error: None,
|
||||||
@@ -1241,9 +1271,9 @@ fn ping_subscription_server(server: &SubscriptionServer) -> PingServerResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PingServerResponse {
|
PingServerResponse {
|
||||||
tag: server.tag.clone(),
|
tag: tag.to_string(),
|
||||||
server: server.server.clone(),
|
server: server.to_string(),
|
||||||
server_port: server.server_port,
|
server_port,
|
||||||
ok: false,
|
ok: false,
|
||||||
latency: None,
|
latency: None,
|
||||||
error: last_error,
|
error: last_error,
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ fn main() {
|
|||||||
commands::select_singbox_server,
|
commands::select_singbox_server,
|
||||||
commands::ping_singbox_server,
|
commands::ping_singbox_server,
|
||||||
commands::ping_all_singbox_servers,
|
commands::ping_all_singbox_servers,
|
||||||
|
commands::ping_proxy_target,
|
||||||
commands::generate_singbox_config,
|
commands::generate_singbox_config,
|
||||||
commands::apply_profiles,
|
commands::apply_profiles,
|
||||||
commands::get_logs,
|
commands::get_logs,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ use models::{
|
|||||||
use proxifyre::ProxiFyreAdapter;
|
use proxifyre::ProxiFyreAdapter;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::net::TcpListener;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::process::Command as ProcessCommand;
|
use std::process::Command as ProcessCommand;
|
||||||
@@ -127,6 +128,24 @@ fn resolve_preview_returns_structured_apps_without_filesystem_scan() {
|
|||||||
.any(|warning| warning.contains("Сканирование папок отложено")));
|
.any(|warning| warning.contains("Сканирование папок отложено")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ping_proxy_target_reports_open_tcp_endpoint() {
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
|
||||||
|
let port = listener.local_addr().expect("read local addr").port();
|
||||||
|
|
||||||
|
let result = commands::ping_proxy_target_endpoint(commands::PingProxyTargetInputDto {
|
||||||
|
host: "127.0.0.1".to_string(),
|
||||||
|
port,
|
||||||
|
})
|
||||||
|
.expect("ping should return response");
|
||||||
|
|
||||||
|
assert_eq!(result.tag, "external-proxy");
|
||||||
|
assert_eq!(result.server, "127.0.0.1");
|
||||||
|
assert_eq!(result.server_port, port);
|
||||||
|
assert!(result.ok);
|
||||||
|
assert!(result.latency.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn proxifyre_install_script_parses_as_powershell() {
|
fn proxifyre_install_script_parses_as_powershell() {
|
||||||
|
|||||||
@@ -184,6 +184,12 @@ export function pingAllSingBoxServers(): Promise<PingServerResponse[]> {
|
|||||||
return invoke<PingServerResponse[]>('ping_all_singbox_servers');
|
return invoke<PingServerResponse[]>('ping_all_singbox_servers');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pingProxyTarget(host: string, port: number): Promise<PingServerResponse> {
|
||||||
|
return invoke<PingServerResponse>('ping_proxy_target', {
|
||||||
|
input: { host, port },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function generateSingBoxConfig(): Promise<GenerateSingBoxConfigResponse> {
|
export function generateSingBoxConfig(): Promise<GenerateSingBoxConfigResponse> {
|
||||||
return invoke<GenerateSingBoxConfigResponse>('generate_singbox_config');
|
return invoke<GenerateSingBoxConfigResponse>('generate_singbox_config');
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
85
docs/goals/windows-client-three-panel-ui/EVIDENCE.md
Normal file
85
docs/goals/windows-client-three-panel-ui/EVIDENCE.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Доказательства трехпанельного интерфейса Windows Client
|
||||||
|
|
||||||
|
## Приемочные доказательства
|
||||||
|
|
||||||
|
Записать реальный артефакт, который доказывает результат с точки зрения пользователя.
|
||||||
|
|
||||||
|
Обязательные доказательства:
|
||||||
|
- `summary-panel.png`: read-only `Сводка` показывает статус системы, ProxiFyre, подключение, маршрут, активный сервер, количество приложений, config path state, dirty state и последнее событие.
|
||||||
|
- Browser state для `Сводка`: `summaryInputs = 0`, `summaryForbiddenButtons = []`, активная вкладка `panel-summary`.
|
||||||
|
- `proxifyre-panel.png`: панель `ProxiFyre` показывает статус компонента, setup action, service install action, список приложений, add controls, dirty/apply command row.
|
||||||
|
- `vpn-proxy-external-panel.png`: панель `VPN / Прокси` в режиме внешнего прокси показывает route switch, SOCKS5 input, TCP check state, route preview и apply/open actions.
|
||||||
|
- `vpn-proxy-local-panel.png`: панель `VPN / Прокси` в режиме локального прокси показывает Local sing-box empty/install state, route preview и dirty/apply state.
|
||||||
|
- `narrow-proxifyre-panel.png`: узкий viewport показывает tab bar, ProxiFyre card, app list controls и command area без overlap с log dock.
|
||||||
|
- Browser overlap state после фикса: desktop `commandBottom = 705.1875`, `logTop = 815.1875`, `overlaps = false`; narrow `appListBottom = 701.1875`, `logTop = 1072.1875`, `overlaps = false`.
|
||||||
|
- Root `src/web` и `src/server` не менялись.
|
||||||
|
|
||||||
|
Сервисные действия install/start/stop/uninstall проверены на уровне существующих handlers и сборки UI. Реальный elevated Windows service lane для ProxiFyre/Local sing-box в этой сессии не запускался: `implemented but unproven`.
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
Записать сфокусированные проверки, которые прошли, включая команды и важный вывод.
|
||||||
|
|
||||||
|
Ожидаемые команды:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd apps/windows-client
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tsc && vite build
|
||||||
|
1789 modules transformed
|
||||||
|
dist/index.html
|
||||||
|
dist/assets/index-Oqrmqzau.css
|
||||||
|
dist/assets/index-CJqhju7q.js
|
||||||
|
built in 1.31s
|
||||||
|
```
|
||||||
|
|
||||||
|
Если менялись Rust/Tauri commands:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd apps/windows-client/src-tauri
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
command_tests: 10 passed, including ping_proxy_target_reports_open_tcp_endpoint
|
||||||
|
component_detection_tests: 7 passed
|
||||||
|
domain_tests: 5 passed
|
||||||
|
helper_tests: 6 passed
|
||||||
|
proxifyre_adapter_tests: 6 passed
|
||||||
|
singbox_adapter_tests: 6 passed
|
||||||
|
singbox_command_tests: 8 passed
|
||||||
|
singbox_service_tests: 7 passed
|
||||||
|
storage_tests: 8 passed
|
||||||
|
subscription_tests: 7 passed
|
||||||
|
doc-tests: 0 passed
|
||||||
|
overall: passed, warnings only for existing dead-code/test module duplication
|
||||||
|
```
|
||||||
|
|
||||||
|
Browser preview:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Opened http://127.0.0.1:5174/
|
||||||
|
Tabs detected: Сводка, ProxiFyre, VPN / Прокси
|
||||||
|
Summary activePanel: panel-summary
|
||||||
|
ProxiFyre activePanel: panel-proxifyre
|
||||||
|
VPN/Proxy activePanel: panel-proxy
|
||||||
|
External route aria-pressed=true in external mode
|
||||||
|
Local route aria-pressed=true in local mode
|
||||||
|
```
|
||||||
|
|
||||||
|
## Заметки review
|
||||||
|
|
||||||
|
Записать PRE review, reviewer, maintainer или verifier findings, если они изменили результат.
|
||||||
|
|
||||||
|
- PRE review: aligned. No blockers. Main risk was weak UI evidence; resolved by browser screenshots and DOM state checks.
|
||||||
|
- POST plan review: aligned. The displaced monolithic screen path is removed from the active render path and replaced by three panels.
|
||||||
|
- Correctness review: summary has no mutating controls; ProxiFyre actions remain in ProxiFyre panel; route/proxy actions remain in VPN/Proxy panel; new `ping_proxy_target` is read-only and tested with a local TCP listener.
|
||||||
|
- Maintainability review: backend source of truth unchanged; no direct generated config writes from React; no root `src/web` or `src/server` edits.
|
||||||
|
- Residual risk: real elevated service actions were not manually executed in native Tauri/elevated Windows mode during this UI pass.
|
||||||
12
docs/goals/windows-client-three-panel-ui/GOAL.md
Normal file
12
docs/goals/windows-client-three-panel-ui/GOAL.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Goal: Трехпанельный интерфейс Windows Client
|
||||||
|
|
||||||
|
Use Krypton Execution to execute `docs/goals/windows-client-three-panel-ui/PLAN.md`.
|
||||||
|
|
||||||
|
Core rules:
|
||||||
|
- Treat `PLAN.md` as the source plan.
|
||||||
|
- Preserve intent, ownership, contract, cutover, evidence, and kill criteria.
|
||||||
|
- Summary panel must remain read-only: no apply, install, start, stop, delete, input, subscription, or route mutations there.
|
||||||
|
- Keep Rust/Tauri storage and commands as the source of truth; React may hold only UI state and derived display models.
|
||||||
|
- Do not add a new dominant path without deleting, redirecting, demoting, or shimming the displaced monolithic screen path.
|
||||||
|
- Capture acceptance evidence from the target perspective and record it in `EVIDENCE.md`.
|
||||||
|
- Say `implemented but unproven` if target-perspective or Windows service evidence cannot be captured.
|
||||||
423
docs/goals/windows-client-three-panel-ui/PLAN.md
Normal file
423
docs/goals/windows-client-three-panel-ui/PLAN.md
Normal file
@@ -0,0 +1,423 @@
|
|||||||
|
# План реализации трехпанельного интерфейса Windows Client
|
||||||
|
|
||||||
|
**Intent:** Перестроить текущий монолитный экран Windows-клиента в три переключаемые панели: read-only сводка, настройки ProxiFyre и настройки VPN/прокси.
|
||||||
|
**Current Behavior:** Сейчас `apps/windows-client/src/app/App.tsx` смешивает статус ProxiFyre, выбор маршрута, Local sing-box, список приложений, применение конфига и журнал событий на одном экране. Пользователь сразу видит все управляющие элементы, поэтому главная страница не отделяет состояние системы от действий.
|
||||||
|
**Expected Outcome:** Пользователь открывает приложение и сначала видит чистую сводку без редактирования. Управление ProxiFyre, список приложений и диагностика компонента находятся во второй панели. Выбор локального или внешнего прокси, подписка, серверы, ping и генерация VPN/proxy-конфига находятся в третьей панели.
|
||||||
|
**Target-Perspective Output:** Пользователь может за 5-10 секунд понять, работает ли система, запущен ли ProxiFyre, какой маршрут применен, локальный он или внешний, и какой файл конфига активен. Затем пользователь переключается во вкладку ProxiFyre для сервисных действий и списка приложений либо во вкладку VPN/Прокси для выбора маршрута и проверки соединения.
|
||||||
|
**Truth Owner:** Rust/Tauri команды и JSON-файлы под `C:\ProgramData\VpnProxy` остаются источником правды. React хранит только состояние текущей вкладки, черновики полей, loading/error/success состояния и производные view model для отображения.
|
||||||
|
**Contract Boundary:** UI вызывает существующие typed Tauri commands из `apps/windows-client/src/api/tauriCommands.ts`. Rust владеет профилями, targets, компонентами, local-singbox config/cache, generated config и применением ProxiFyre. Новые UI-панели не должны писать напрямую в derived/generated артефакты.
|
||||||
|
**Cutover:** Текущий единый экран в `App.tsx` заменяется shell-layout с тремя вкладками. Существующие функции загрузки, установки, запуска, выбора сервера, ping, apply и notifications переиспользуются, но раскладываются по панелям.
|
||||||
|
**Displaced Path:** Убирается доминирующий путь "все настройки на одной странице". Сводка не дублирует формы и кнопки apply; она только отображает состояние и ведет пользователя к нужной панели.
|
||||||
|
**Value Density:** Самый ценный срез: верхняя навигация по трем панелям плюс read-only сводка, которая корректно собирает текущее состояние из уже загружаемых данных. После этого переносим существующие настройки без изменения backend-контракта.
|
||||||
|
**Evidence Gate:** Приемка требует не только `npm run build`, но и доказательство глазами пользователя: скриншоты/состояния всех трех панелей, read-only поведение сводки, dirty/apply состояние на вкладках настроек, responsive-проверка.
|
||||||
|
**Acceptance Evidence:** `npm run build` проходит; при необходимости Rust tests проходят для нового ping/target DTO; в `EVIDENCE.md` записаны скриншоты или browser/Tauri state для трех панелей, а также проверка, что сводка не содержит изменяющих controls.
|
||||||
|
**Evidence Lane:** Доказательства исполнения будут фиксироваться в `docs/goals/windows-client-three-panel-ui/EVIDENCE.md`.
|
||||||
|
**Kill Criteria:** Нет input/button apply на сводке; нет второго источника состояния маршрута; нет скрытой установки компонентов при открытии вкладок; нет изменения root `src/web` или root `src/server`; нет расхождения терминов ProxiFyre / Local sing-box / внешний прокси.
|
||||||
|
**Architecture Slice:** Изменения сосредоточены в `apps/windows-client/src/app/App.tsx`, `apps/windows-client/src/styles/app.css`, при необходимости `apps/windows-client/src/api/tauriCommands.ts`, `apps/windows-client/src/domain/types.ts`, `apps/windows-client/src-tauri/src/commands.rs` и связанных tests для проверки внешнего target ping.
|
||||||
|
**Plan Review Gate:** Требуется PRE review перед исполнением.
|
||||||
|
|
||||||
|
## Краткий дизайн-brief
|
||||||
|
|
||||||
|
Продукт: Windows desktop utility для маршрутизации выбранных приложений через ProxiFyre и внешний или локальный proxy path.
|
||||||
|
|
||||||
|
Визуальный источник: текущий темный utilitarian-стиль `apps/windows-client/src/styles/app.css`: компактные панели, 4px radius, статусные точки, lucide icons, сдержанные borders, без landing/hero.
|
||||||
|
|
||||||
|
Интерактивность: полная. Вкладки, меню, установка/запуск/остановка, добавление приложений, выбор маршрута, подписка, выбор сервера, ping, генерация и apply должны работать через реальные текущие команды.
|
||||||
|
|
||||||
|
## Архитектурный срез
|
||||||
|
|
||||||
|
Файлы для создания:
|
||||||
|
- `docs/goals/windows-client-three-panel-ui/PLAN.md`
|
||||||
|
- `docs/goals/windows-client-three-panel-ui/GOAL.md`
|
||||||
|
- `docs/goals/windows-client-three-panel-ui/EVIDENCE.md`
|
||||||
|
|
||||||
|
Файлы для изменения:
|
||||||
|
- `apps/windows-client/src/app/App.tsx`
|
||||||
|
- `apps/windows-client/src/styles/app.css`
|
||||||
|
- `apps/windows-client/src/api/tauriCommands.ts`, только если понадобится новый внешний proxy ping command.
|
||||||
|
- `apps/windows-client/src/domain/types.ts`, только если понадобится новый DTO для внешнего proxy ping.
|
||||||
|
- `apps/windows-client/src-tauri/src/commands.rs`, только если понадобится новый внешний proxy ping command.
|
||||||
|
- `apps/windows-client/src-tauri/src/main.rs`, только для регистрации нового command.
|
||||||
|
- `apps/windows-client/src-tauri/tests/*`, только для нового command или сохранения существующих контрактов.
|
||||||
|
- `docs/goals/windows-client-three-panel-ui/EVIDENCE.md`, при исполнении.
|
||||||
|
|
||||||
|
Файлы, которых не касаться:
|
||||||
|
- `src/web/*`
|
||||||
|
- `src/server/*`
|
||||||
|
- Docker/compose/entrypoint файлы.
|
||||||
|
- Installer scripts, если UI-разрез не требует изменения install boundary.
|
||||||
|
- Старые goal packages, кроме ссылок в evidence при необходимости.
|
||||||
|
|
||||||
|
Источник правды:
|
||||||
|
- `C:\ProgramData\VpnProxy\config\profiles.json`
|
||||||
|
- `C:\ProgramData\VpnProxy\config\targets.json`
|
||||||
|
- `C:\ProgramData\VpnProxy\config\components.json`
|
||||||
|
- `C:\ProgramData\VpnProxy\config\local-singbox.json`
|
||||||
|
- `C:\ProgramData\VpnProxy\state\singbox-subscription-cache.json`
|
||||||
|
- `C:\ProgramData\VpnProxy\state\activity.json`
|
||||||
|
|
||||||
|
Производные артефакты:
|
||||||
|
- `C:\ProgramData\VpnProxy\generated\proxifyre-app-config.json`
|
||||||
|
- `C:\ProgramData\VpnProxy\generated\sing-box-config.json`
|
||||||
|
|
||||||
|
Путь чтения:
|
||||||
|
- React вызывает `get_saved_state`, `get_components`, `get_proxifyre_setup_status`, `get_singbox_status`, `get_singbox_setup_status`.
|
||||||
|
- Summary panel строит производную view model из уже загруженных `profiles`, `targets`, `components`, `setupStatus`, `singBoxStatus`, `generatedConfigPath`, `serverPings` и `hasUnappliedChanges`.
|
||||||
|
- ProxiFyre panel читает `proxyfier`, `setupStatus`, `items`, `loadedProfiles`, `generatedConfigPath`.
|
||||||
|
- VPN/Proxy panel читает `routeMode`, `proxyInput`, `singBoxStatus`, `singBoxSetupStatus`, `serverPings`, selected server и target.
|
||||||
|
|
||||||
|
Путь записи:
|
||||||
|
- ProxiFyre panel пишет только через существующие handlers: install/start/stop/uninstall ProxiFyre, add/remove app items, `updateConfig`.
|
||||||
|
- VPN/Proxy panel пишет только через route/subscription/server/config handlers: `changeRouteMode`, `changeProxyInput`, `saveSingBoxSubscription`, `fetchSingBoxSubscription`, `forgetSingBoxSubscription`, `selectSingBoxServer`, `ping*`, `generateSingBoxConfig`, service actions, `updateConfig`.
|
||||||
|
- Summary panel не пишет ничего, кроме глобального non-mutating refresh, если refresh остается в header.
|
||||||
|
|
||||||
|
Граница контракта:
|
||||||
|
- `App.tsx` отвечает за orchestration и view composition.
|
||||||
|
- Typed Tauri command wrappers отвечают за API shape.
|
||||||
|
- Rust commands отвечают за validation, persistence, service/check actions и generated config.
|
||||||
|
- CSS отвечает за layout, tab transitions, loading skeletons, menu/popover transitions и responsive behavior.
|
||||||
|
|
||||||
|
Точки интеграции:
|
||||||
|
- `routeMode === 'external'` показывает внешний/глобальный proxy target.
|
||||||
|
- `routeMode === 'local-singbox'` показывает Local sing-box path.
|
||||||
|
- `hasUnappliedChanges` остается общим индикатором для вкладок настроек.
|
||||||
|
- `log-dock` остается глобальным и не привязывается к одной вкладке.
|
||||||
|
|
||||||
|
Миграция и переключение:
|
||||||
|
- Сначала добавить `activePanel` и shell-вкладки без изменения бизнес-логики.
|
||||||
|
- Затем перенести JSX блоки в три render-функции или локальные компоненты в том же файле.
|
||||||
|
- После стабилизации можно вынести панели в отдельные файлы, но только если это уменьшит размер `App.tsx` без изменения контрактов.
|
||||||
|
|
||||||
|
Гейт приемочных доказательств:
|
||||||
|
- Доказательство должно показать каждую панель отдельно.
|
||||||
|
- Должно быть видно, что summary read-only.
|
||||||
|
- Должно быть видно, что actions доступны на вкладках настроек.
|
||||||
|
- Должно быть видно, что mobile layout не ломает tab bar, app list, server list и bottom log dock.
|
||||||
|
|
||||||
|
## Детальное описание интерфейса
|
||||||
|
|
||||||
|
### Общая оболочка
|
||||||
|
|
||||||
|
Верхняя часть экрана остается компактной, но становится навигационной:
|
||||||
|
- слева: `VPN Proxy` и заголовок `Прокси для приложений`;
|
||||||
|
- справа: кнопка `Обновить`, которая запускает текущий `refresh`;
|
||||||
|
- под заголовком: segmented tabs из трех пунктов: `Сводка`, `ProxiFyre`, `VPN / Прокси`.
|
||||||
|
|
||||||
|
Поведение вкладок:
|
||||||
|
- активная вкладка имеет более яркий border/background и status underline;
|
||||||
|
- при клике меняется только `activePanel`, данные не перезагружаются автоматически;
|
||||||
|
- переход между панелями: fade + легкий slide по Y на 6-8px, длительность 140-180ms;
|
||||||
|
- при `prefers-reduced-motion: reduce` transition отключается;
|
||||||
|
- keyboard support: tabs работают как buttons с `aria-pressed` или `role="tablist"`/`role="tab"`/`role="tabpanel`.
|
||||||
|
|
||||||
|
Глобальные состояния:
|
||||||
|
- `isLoading` показывает skeleton в содержимом активной панели;
|
||||||
|
- `isDetectingComponents` показывает проверку компонентов без блокировки переключения вкладок;
|
||||||
|
- `log-dock` остается закрепленным снизу и продолжает показывать success/error/info;
|
||||||
|
- dirty/apply bar появляется только на вкладках `ProxiFyre` и `VPN / Прокси`, не на read-only сводке.
|
||||||
|
|
||||||
|
### Панель 1: `Сводка`
|
||||||
|
|
||||||
|
Назначение: главная read-only страница, которая отвечает на вопрос "что сейчас происходит с системой".
|
||||||
|
|
||||||
|
Содержимое:
|
||||||
|
- `Статус системы`: одно агрегированное состояние `Работает`, `Требует внимания`, `Не настроено`, `Проверяю`.
|
||||||
|
- `ProxiFyre`: установлен/не установлен, запущен/остановлен, путь или первая проблема.
|
||||||
|
- `Подключение`: корректность текущего маршрута: ProxiFyre running, выбран target, для Local sing-box выбран сервер и служба запущена, для внешнего proxy валиден host/port и есть последний результат ping/check, если он уже запускался.
|
||||||
|
- `Маршрут`: `Локальный прокси` или `Внешний прокси`; рядом человекочитаемый endpoint: `127.0.0.1:1080`, LAN адрес Local sing-box или внешний `host:port`.
|
||||||
|
- `Активный сервер`: selected Local sing-box server tag, если применим; иначе `не используется`.
|
||||||
|
- `Приложения в профиле`: количество настроенных process/folder/exe items.
|
||||||
|
- `Примененный конфиг`: путь `generatedConfigPath` для ProxiFyre и, если есть, `singBoxStatus.generatedConfigPath`.
|
||||||
|
- `Состояние изменений`: `Все применено` или `Есть непримененные изменения`, но без кнопки apply.
|
||||||
|
- `Последнее событие`: последний элемент `logEntries` или activity, если он уже доступен на клиенте.
|
||||||
|
|
||||||
|
Визуально:
|
||||||
|
- это не форма, а read-only dashboard;
|
||||||
|
- вместо input используются value rows: label слева, value справа, status dot рядом;
|
||||||
|
- важные значения не спрятаны в tooltip;
|
||||||
|
- длинные пути переносятся через `overflow-wrap: anywhere`;
|
||||||
|
- при широкой ширине блоки идут сеткой 2-3 колонки, на узком экране складываются в один столбец.
|
||||||
|
|
||||||
|
Действия при нажатии:
|
||||||
|
- клик по строке `ProxiFyre` переключает на вкладку `ProxiFyre`;
|
||||||
|
- клик по строке `Маршрут` или `Активный сервер` переключает на вкладку `VPN / Прокси`;
|
||||||
|
- клик по `Приложения в профиле` переключает на вкладку `ProxiFyre` и фокусирует список приложений;
|
||||||
|
- никаких input, add/remove, install/start/stop, apply или clear на этой панели нет.
|
||||||
|
|
||||||
|
Анимации:
|
||||||
|
- при refresh status rows показывают мягкий shimmer/skeleton;
|
||||||
|
- status dot для `Проверяю` использует текущий spinner pattern;
|
||||||
|
- при смене агрегированного статуса value row подсвечивается на 400-600ms через background flash;
|
||||||
|
- предупреждение `Есть непримененные изменения` может слегка проявляться fade-in, но без кнопки.
|
||||||
|
|
||||||
|
### Панель 2: `ProxiFyre`
|
||||||
|
|
||||||
|
Назначение: диагностика и настройка того, что относится именно к ProxiFyre и маршрутизируемым приложениям.
|
||||||
|
|
||||||
|
Содержимое:
|
||||||
|
- главный статус ProxiFyre: найден/не найден, запущен/остановлен, path/problem;
|
||||||
|
- setup/modules list из `setupStatus.items`: binary/service/install folder/config capability и все, что возвращает backend;
|
||||||
|
- service controls: install, start, stop, uninstall через меню `MoreHorizontal`;
|
||||||
|
- `Список приложений`: настроенные process/folder/exe items, сгруппированные или помеченные типом;
|
||||||
|
- счетчик приложений и отдельные пустые состояния для "ничего не настроено";
|
||||||
|
- generated ProxiFyre config path;
|
||||||
|
- dirty/apply state для изменений списка приложений.
|
||||||
|
|
||||||
|
Визуально:
|
||||||
|
- верхний status block сохраняет текущий `finder-card` pattern, status-light и animated border glow;
|
||||||
|
- modules list разворачивается внутри панели через `Состав ProxiFyre` / `Что будет установлено`;
|
||||||
|
- список приложений становится основной частью панели, а не хвостом общей страницы;
|
||||||
|
- app rows имеют icon по типу: process, exe, folder;
|
||||||
|
- add-toolbar остается с icon buttons: process, exe, folder, с tooltip/title.
|
||||||
|
|
||||||
|
Действия при нажатии:
|
||||||
|
- `Установить`: вызывает текущий `installProxiFyrePackage`, показывает loading на кнопке и animated border;
|
||||||
|
- `Запустить`/`Остановить`: вызывает `setProxiFyreServiceRunning`, блокирует повторный клик на время action;
|
||||||
|
- `Еще` открывает popover с `Удалить ProxiFyre`; uninstall сохраняет текущий confirm boundary;
|
||||||
|
- `Состав ProxiFyre`: раскрывает/скрывает setup details;
|
||||||
|
- `Добавить процесс`: открывает inline input, Enter добавляет, Escape закрывает;
|
||||||
|
- `Добавить EXE`: открывает file picker через Tauri dialog;
|
||||||
|
- `Добавить папку`: открывает directory picker;
|
||||||
|
- `Удалить` у app row удаляет item и выставляет `hasUnappliedChanges`;
|
||||||
|
- `Применить в ProxiFyre`: валидирует items и текущий route, затем вызывает `updateConfig`;
|
||||||
|
- `Открыть конфиг`: открывает generated config location, если эта кнопка остается в ProxiFyre panel.
|
||||||
|
|
||||||
|
Анимации:
|
||||||
|
- поиск/установка/запуск используют существующий moving border glow;
|
||||||
|
- раскрытие modules list: height/opacity transition 160-220ms;
|
||||||
|
- добавление app row: fade/slide-in;
|
||||||
|
- удаление app row: быстрый opacity collapse, если реализация не усложнит state;
|
||||||
|
- dirty bar появляется slide-up/fade-in над command row;
|
||||||
|
- меню `Еще`: fade + scale 0.98 -> 1.
|
||||||
|
|
||||||
|
### Панель 3: `VPN / Прокси`
|
||||||
|
|
||||||
|
Назначение: выбор маршрута и настройка самого proxy/VPN path.
|
||||||
|
|
||||||
|
Содержимое:
|
||||||
|
- segmented control `Внешний прокси` / `Локальный прокси`;
|
||||||
|
- для `Внешний прокси`: input адреса `socks5://host:port` или `host:port`, validation hint, кнопка проверки доступности;
|
||||||
|
- для `Локальный прокси`: Local sing-box status, install/start/stop/uninstall, setup details, локальный и LAN endpoint, selected server, generated sing-box config path;
|
||||||
|
- subscription input: paste URL/link, load/update, clear;
|
||||||
|
- server list из `singBoxStatus.cache.servers`;
|
||||||
|
- ping controls: `Ping все`, per-server ping action, отображение latency/error рядом с server row;
|
||||||
|
- generate sing-box config action;
|
||||||
|
- route preview: `Выбранные приложения -> ProxiFyre -> внешний proxy` или `Выбранные приложения -> ProxiFyre -> Local sing-box -> selected server`;
|
||||||
|
- dirty/apply state для route/proxy/subscription/server changes.
|
||||||
|
|
||||||
|
Визуально:
|
||||||
|
- mode switch остается заметным вверху панели;
|
||||||
|
- внешний proxy block не должен выглядеть менее важным, потому что это полноценный route path;
|
||||||
|
- Local sing-box block визуально похож на ProxiFyre status block, но расположен только в VPN/Proxy panel;
|
||||||
|
- server list может оставаться grid, но row должен показывать selected dot, server name и ping badge;
|
||||||
|
- для плохого ping row получает amber/red border, для хорошего ping зеленый badge;
|
||||||
|
- route preview оформляется как read-only line/flow, без декоративной схемы.
|
||||||
|
|
||||||
|
Действия при нажатии:
|
||||||
|
- `Внешний прокси`: меняет `routeMode` на `external`, выставляет dirty state;
|
||||||
|
- ввод адреса proxy: обновляет `proxyInput`, выставляет dirty state, frontend validation показывает ошибки до apply;
|
||||||
|
- `Проверить`: парсит внешний proxy и вызывает новый или переиспользованный backend TCP connect check; результат сохраняется в UI state и отображается на этой панели и в summary;
|
||||||
|
- `Локальный прокси`: меняет `routeMode` на `local-singbox`, выставляет dirty state;
|
||||||
|
- `Установить Local sing-box`: вызывает текущий install handler;
|
||||||
|
- `Запустить`/`Остановить`: вызывает текущий service handler;
|
||||||
|
- `Состав Local sing-box`: раскрывает setup details;
|
||||||
|
- `Подробности`: показывает локальный endpoint, LAN, server, binary path, config path;
|
||||||
|
- `Загрузить`/`Обновить` подписку: вызывает текущий subscription flow;
|
||||||
|
- `Очистить`: вызывает forget flow, очищает server pings и selected server;
|
||||||
|
- click по server row: вызывает `selectSingBoxServer`, выставляет dirty state;
|
||||||
|
- `Ping все`: вызывает `pingAllSingBoxServers`;
|
||||||
|
- per-server ping: вызывает существующий `pingSingBoxServer(tag)`, если добавляем кнопку/иконку в row;
|
||||||
|
- `Конфиг`: вызывает `generateSingBoxConfig`;
|
||||||
|
- `Применить маршрут`: вызывает общий `updateConfig`.
|
||||||
|
|
||||||
|
Анимации:
|
||||||
|
- mode switch меняет активный segment без скачка высоты;
|
||||||
|
- при смене mode panel content cross-fade;
|
||||||
|
- ping badge показывает loading spinner только на проверяемых row;
|
||||||
|
- server row после ping обновляет latency с короткой подсветкой;
|
||||||
|
- subscription/server empty state появляется fade-in;
|
||||||
|
- generated config success подсвечивает config path на 400-600ms.
|
||||||
|
|
||||||
|
## Задачи исполнения
|
||||||
|
|
||||||
|
### Задача 1: Ввести shell вкладок и производные view models
|
||||||
|
|
||||||
|
Разрешенные файлы:
|
||||||
|
- `apps/windows-client/src/app/App.tsx`
|
||||||
|
- `apps/windows-client/src/styles/app.css`
|
||||||
|
|
||||||
|
Ожидаемый результат:
|
||||||
|
- `activePanel: 'summary' | 'proxifyre' | 'proxy'`.
|
||||||
|
- Общий header с tab control.
|
||||||
|
- Производные функции для summary: system status, route label, endpoint label, config labels, app count, issue list.
|
||||||
|
- Старые JSX-блоки временно остаются, но render path начинает разделяться.
|
||||||
|
|
||||||
|
Проверка:
|
||||||
|
- `cd apps/windows-client && npm run build`
|
||||||
|
|
||||||
|
Приемочные доказательства:
|
||||||
|
- Screenshot/state показывает три tab buttons и корректную активную вкладку.
|
||||||
|
|
||||||
|
Параллельное выполнение: нет, общий `App.tsx`.
|
||||||
|
|
||||||
|
### Задача 2: Реализовать read-only панель `Сводка`
|
||||||
|
|
||||||
|
Разрешенные файлы:
|
||||||
|
- `apps/windows-client/src/app/App.tsx`
|
||||||
|
- `apps/windows-client/src/styles/app.css`
|
||||||
|
|
||||||
|
Ожидаемый результат:
|
||||||
|
- Summary panel без input/apply/install/start/stop/delete.
|
||||||
|
- Статус системы, ProxiFyre, подключение, маршрут, активный server, apps count, config paths, dirty state.
|
||||||
|
- Навигационные клики по read-only строкам переводят на соответствующие вкладки.
|
||||||
|
|
||||||
|
Проверка:
|
||||||
|
- `cd apps/windows-client && npm run build`
|
||||||
|
- Manual DOM/visual check: на summary нет изменяющих controls.
|
||||||
|
|
||||||
|
Приемочные доказательства:
|
||||||
|
- Screenshot summary panel.
|
||||||
|
- Запись в evidence: какие элементы read-only и куда ведут navigation clicks.
|
||||||
|
|
||||||
|
Параллельное выполнение: нет.
|
||||||
|
|
||||||
|
### Задача 3: Перенести ProxiFyre и список приложений во вторую панель
|
||||||
|
|
||||||
|
Разрешенные файлы:
|
||||||
|
- `apps/windows-client/src/app/App.tsx`
|
||||||
|
- `apps/windows-client/src/styles/app.css`
|
||||||
|
|
||||||
|
Ожидаемый результат:
|
||||||
|
- ProxiFyre panel содержит status card, setup details, service actions, app add/remove, generated config path и apply/open config actions.
|
||||||
|
- Существующие handlers переиспользованы без изменения backend ownership.
|
||||||
|
- `hasUnappliedChanges` продолжает работать после add/remove.
|
||||||
|
|
||||||
|
Проверка:
|
||||||
|
- `cd apps/windows-client && npm run build`
|
||||||
|
|
||||||
|
Приемочные доказательства:
|
||||||
|
- Screenshot ProxiFyre panel с service status и apps list.
|
||||||
|
- Проверка add/remove вызывает dirty state.
|
||||||
|
|
||||||
|
Параллельное выполнение: нет.
|
||||||
|
|
||||||
|
### Задача 4: Перенести VPN/Proxy route settings в третью панель
|
||||||
|
|
||||||
|
Разрешенные файлы:
|
||||||
|
- `apps/windows-client/src/app/App.tsx`
|
||||||
|
- `apps/windows-client/src/styles/app.css`
|
||||||
|
- `apps/windows-client/src/api/tauriCommands.ts`, если нужен внешний target ping.
|
||||||
|
- `apps/windows-client/src/domain/types.ts`, если нужен внешний target ping DTO.
|
||||||
|
|
||||||
|
Ожидаемый результат:
|
||||||
|
- VPN/Proxy panel содержит route mode switch, external proxy editor, Local sing-box controls, subscription, server selection, ping, generate config, route preview и apply.
|
||||||
|
- Внешний proxy path не зависит от Local sing-box.
|
||||||
|
- Local sing-box UI не показывается на summary и не смешивается с ProxiFyre panel.
|
||||||
|
|
||||||
|
Проверка:
|
||||||
|
- `cd apps/windows-client && npm run build`
|
||||||
|
|
||||||
|
Приемочные доказательства:
|
||||||
|
- Screenshot external proxy mode.
|
||||||
|
- Screenshot local proxy mode с server list или empty state.
|
||||||
|
|
||||||
|
Параллельное выполнение: нет.
|
||||||
|
|
||||||
|
### Задача 5: Добавить или переиспользовать проверку доступности внешнего proxy target
|
||||||
|
|
||||||
|
Разрешенные файлы:
|
||||||
|
- `apps/windows-client/src-tauri/src/commands.rs`
|
||||||
|
- `apps/windows-client/src-tauri/src/main.rs`
|
||||||
|
- `apps/windows-client/src/api/tauriCommands.ts`
|
||||||
|
- `apps/windows-client/src/domain/types.ts`
|
||||||
|
- `apps/windows-client/src-tauri/tests/command_tests.rs` или отдельный focused test.
|
||||||
|
|
||||||
|
Ожидаемый результат:
|
||||||
|
- Если существующих данных недостаточно для "все ли окей с подключением" во внешнем proxy mode, добавить typed command наподобие `ping_proxy_target`.
|
||||||
|
- Command делает TCP connect check к host/port с timeout, возвращает `{ ok, latency, error }`.
|
||||||
|
- UI отображает результат в VPN/Proxy panel и summary.
|
||||||
|
- Это не заменяет apply и не пишет source config.
|
||||||
|
|
||||||
|
Проверка:
|
||||||
|
- `cd apps/windows-client/src-tauri && cargo test command`
|
||||||
|
- `cd apps/windows-client && npm run build`
|
||||||
|
|
||||||
|
Приемочные доказательства:
|
||||||
|
- Test или manual state показывает успешный/ошибочный result shape.
|
||||||
|
|
||||||
|
Параллельное выполнение: частично, если UI DTO names согласованы заранее; интеграция не parallel-safe.
|
||||||
|
|
||||||
|
### Задача 6: Привести CSS, анимации и responsive behavior
|
||||||
|
|
||||||
|
Разрешенные файлы:
|
||||||
|
- `apps/windows-client/src/styles/app.css`
|
||||||
|
- `apps/windows-client/src/app/App.tsx`, только className/ARIA adjustments.
|
||||||
|
|
||||||
|
Ожидаемый результат:
|
||||||
|
- Tab bar, panel transitions, setup expand/collapse, ping badges, dirty/apply bar и responsive grids.
|
||||||
|
- Нет перекрытия текста, длинные paths/servers переносятся.
|
||||||
|
- Mobile layout: tabs не ломаются, server list минимум 2 колонки или 1 колонка на очень узком экране, command row складывается.
|
||||||
|
- `prefers-reduced-motion` отключает декоративные transitions.
|
||||||
|
|
||||||
|
Проверка:
|
||||||
|
- `cd apps/windows-client && npm run build`
|
||||||
|
- Browser/Tauri visual check на desktop и узком viewport.
|
||||||
|
|
||||||
|
Приемочные доказательства:
|
||||||
|
- Screenshots desktop + narrow viewport.
|
||||||
|
|
||||||
|
Параллельное выполнение: нет, потому что зависит от финальной разметки.
|
||||||
|
|
||||||
|
### Задача 7: Финальная проверка и evidence
|
||||||
|
|
||||||
|
Разрешенные файлы:
|
||||||
|
- `docs/goals/windows-client-three-panel-ui/EVIDENCE.md`
|
||||||
|
- При необходимости `apps/windows-client/README.md`, если нужно зафиксировать новую навигацию.
|
||||||
|
|
||||||
|
Ожидаемый результат:
|
||||||
|
- Evidence содержит commands, результаты build/tests, screenshots/state checks и список остаточных рисков.
|
||||||
|
- Если Tauri service actions не проверялись на реальной Windows elevated среде, это явно помечено как `implemented but unproven` только для service lane.
|
||||||
|
|
||||||
|
Проверка:
|
||||||
|
- `git status --short`
|
||||||
|
- `cd apps/windows-client && npm run build`
|
||||||
|
- `cd apps/windows-client/src-tauri && cargo test`, если Rust command менялся.
|
||||||
|
|
||||||
|
Приемочные доказательства:
|
||||||
|
- Summary read-only verified.
|
||||||
|
- ProxiFyre actions verified.
|
||||||
|
- VPN/Proxy route switch and ping states verified.
|
||||||
|
- No root web/server changes.
|
||||||
|
|
||||||
|
Параллельное выполнение: нет.
|
||||||
|
|
||||||
|
## Prompt для PRE review
|
||||||
|
|
||||||
|
Использовать `C:\Users\PC\.agents\skills\krypton-planning\plan-reviewer-prompt.md` со следующими данными:
|
||||||
|
|
||||||
|
Файл плана:
|
||||||
|
- `docs/goals/windows-client-three-panel-ui/PLAN.md`
|
||||||
|
|
||||||
|
Исходный запрос:
|
||||||
|
- Разбить Windows client UI на три переключаемые панели: общая read-only сводка, настройки ProxiFyre, настройки VPN/proxy; подробно описать внешний вид, анимации и действия.
|
||||||
|
|
||||||
|
Контракт результата:
|
||||||
|
- Пользователь сначала видит статус системы и примененный route/config без изменения данных, затем переходит в нужную панель для ProxiFyre или VPN/proxy действий.
|
||||||
|
|
||||||
|
Архитектурный срез:
|
||||||
|
- Основной scope: `apps/windows-client/src/app/App.tsx` и `apps/windows-client/src/styles/app.css`; backend только для внешнего target ping, если текущих команд недостаточно.
|
||||||
|
|
||||||
|
Требование к приемочным доказательствам:
|
||||||
|
- Build/tests плюс screenshots/state proof трех панелей и read-only summary.
|
||||||
|
|
||||||
|
Известные non-goals:
|
||||||
|
- Не менять root `src/web`/`src/server`.
|
||||||
|
- Не переписывать Tauri backend без нужды.
|
||||||
|
- Не добавлять скрытую установку компонентов.
|
||||||
|
- Не делать summary редактируемой.
|
||||||
|
|
||||||
|
Опасные пути или слои:
|
||||||
|
- Дублирование source of truth в React.
|
||||||
|
- Изменение generated config напрямую.
|
||||||
|
- Смешивание Local sing-box controls обратно в ProxiFyre panel.
|
||||||
|
- Потеря external proxy path как полноценного режима.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
BIN
docs/goals/windows-client-three-panel-ui/proxifyre-panel.png
Normal file
BIN
docs/goals/windows-client-three-panel-ui/proxifyre-panel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
BIN
docs/goals/windows-client-three-panel-ui/summary-panel.png
Normal file
BIN
docs/goals/windows-client-three-panel-ui/summary-panel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
Reference in New Issue
Block a user