Add admin restart flow and loading indicators
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { Cpu, FileCode2, FolderOpen, Gauge, Link2, Power, Trash2, Wand2 } from 'lucide-react';
|
||||
import { Cpu, FileCode2, FolderOpen, Gauge, Link2, Power, ShieldAlert, Trash2, Wand2 } from 'lucide-react';
|
||||
import {
|
||||
applyProfiles,
|
||||
fetchSingBoxSubscription,
|
||||
forgetSingBoxSubscription,
|
||||
generateSingBoxConfig,
|
||||
getAdminStatus,
|
||||
getComponents,
|
||||
getProxiFyreSetupStatus,
|
||||
getSavedState,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
pingAllSingBoxServers,
|
||||
pingProxyTarget,
|
||||
pingSingBoxServer,
|
||||
restartAsAdmin,
|
||||
saveProfile,
|
||||
saveSingBoxSubscription,
|
||||
saveTarget,
|
||||
@@ -26,6 +28,7 @@ import {
|
||||
stopSingBoxService,
|
||||
uninstallProxiFyre,
|
||||
uninstallSingBox,
|
||||
type AdminStatusResponse,
|
||||
type ApplyProfilesResponse,
|
||||
type LocalSingBoxStatusResponse,
|
||||
type PingServerResponse,
|
||||
@@ -35,7 +38,7 @@ import {
|
||||
type SingBoxSetupStatus,
|
||||
} from '../api/tauriCommands';
|
||||
import type { ComponentStatus, Profile, ProfileItemInput, ProfileItemType, SubscriptionServer, Target } from '../domain/types';
|
||||
import { Button, DetailsPopover, IconButton, LogDock, ServiceControlRow, Tabs } from '../ui';
|
||||
import { BusyRing, Button, DetailsPopover, IconButton, LogDock, ServiceControlRow, Tabs } from '../ui';
|
||||
import { getApplyReadiness } from './readiness';
|
||||
import { serviceControlState } from './viewModel';
|
||||
|
||||
@@ -180,6 +183,7 @@ export function App() {
|
||||
const [subscriptionInput, setSubscriptionInput] = useState('');
|
||||
const [serverPings, setServerPings] = useState<Record<string, PingServerResponse>>({});
|
||||
const [proxyCheck, setProxyCheck] = useState<ProxyTargetCheckResponse | null>(null);
|
||||
const [adminStatus, setAdminStatus] = useState<AdminStatusResponse | null>(null);
|
||||
const [generatedConfigPath, setGeneratedConfigPath] = useState('');
|
||||
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
|
||||
const [activeLogId, setActiveLogId] = useState<string | null>(null);
|
||||
@@ -187,6 +191,7 @@ export function App() {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isDetectingComponents, setIsDetectingComponents] = useState(true);
|
||||
const [isApplying, setIsApplying] = useState(false);
|
||||
const [isRestartingAsAdmin, setIsRestartingAsAdmin] = useState(false);
|
||||
const [isProxyChecking, setIsProxyChecking] = useState(false);
|
||||
const [serverPingTag, setServerPingTag] = useState<string | null>(null);
|
||||
const [serviceAction, setServiceAction] = useState<ProxiFyreAction | null>(null);
|
||||
@@ -259,6 +264,7 @@ export function App() {
|
||||
}, [activeLogId]);
|
||||
|
||||
async function refresh() {
|
||||
void refreshAdminStatus();
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const saved = await getSavedState();
|
||||
@@ -276,6 +282,30 @@ export function App() {
|
||||
void refreshComponents();
|
||||
}
|
||||
|
||||
async function refreshAdminStatus() {
|
||||
try {
|
||||
const status = await getAdminStatus();
|
||||
setAdminStatus(status);
|
||||
} catch {
|
||||
setAdminStatus(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function restartApplicationAsAdmin() {
|
||||
setIsRestartingAsAdmin(true);
|
||||
try {
|
||||
await restartAsAdmin();
|
||||
setIsRestartingAsAdmin(false);
|
||||
} catch (error) {
|
||||
setIsRestartingAsAdmin(false);
|
||||
showNotice({
|
||||
kind: 'error',
|
||||
title: 'Перезапуск отменен',
|
||||
text: errorMessage(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshComponents() {
|
||||
setIsDetectingComponents(true);
|
||||
try {
|
||||
@@ -933,6 +963,33 @@ export function App() {
|
||||
setActiveLogId(entry.id);
|
||||
}
|
||||
|
||||
function renderAdminPrompt() {
|
||||
if (!adminStatus?.canRestartElevated) return null;
|
||||
|
||||
return (
|
||||
<aside className="admin-prompt" aria-label="Права администратора">
|
||||
<span className="admin-prompt-icon" aria-hidden="true">
|
||||
<ShieldAlert size={18} strokeWidth={1.9} />
|
||||
</span>
|
||||
<div className="admin-prompt-copy">
|
||||
<strong>Один запрос прав вместо серии UAC</strong>
|
||||
<span>{adminStatus.message}</span>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="admin-prompt-action"
|
||||
onClick={() => void restartApplicationAsAdmin()}
|
||||
loading={isRestartingAsAdmin}
|
||||
loadingLabel="Открываю UAC"
|
||||
>
|
||||
Перезапустить с правами
|
||||
</Button>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTabs() {
|
||||
const tabs: Array<{ id: PanelId; label: string }> = [
|
||||
{ id: 'summary', label: 'Сводка' },
|
||||
@@ -1176,6 +1233,7 @@ export function App() {
|
||||
|
||||
return (
|
||||
<div className={`summary-status-control ${systemSummary.tone} ${running ? 'on' : 'off'}`}>
|
||||
{systemSummary.tone === 'checking' ? <BusyRing /> : null}
|
||||
<span className={`summary-status-dot ${systemSummary.tone}`} aria-hidden="true" />
|
||||
<div className="summary-status-copy">
|
||||
<span>Состояние маршрута</span>
|
||||
@@ -1504,6 +1562,7 @@ export function App() {
|
||||
|
||||
return (
|
||||
<section className={`proxy-overview ${check.tone}`} aria-label="Состояние прокси">
|
||||
{check.tone === 'checking' ? <BusyRing /> : null}
|
||||
{renderConnectionCheck(check)}
|
||||
</section>
|
||||
);
|
||||
@@ -1607,6 +1666,7 @@ export function App() {
|
||||
</header>
|
||||
|
||||
{renderTabs()}
|
||||
{renderAdminPrompt()}
|
||||
<div className={`tab-panel-frame swipe-${tabTransitionDirection}`} key={activePanel}>
|
||||
{renderActivePanel()}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user