mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-14 00:55:01 +03:00
feat(frontend): confirm before stopping Xray and offer Start when it is stopped (#7104)
Stop sat next to Restart on the dashboard and fired on the first click. A mis-click cut every proxy connection, and when the panel itself is reached through that proxy the only way back was SSH (#7103). Stop now opens a confirmation with Cancel focused. When Xray is stopped the first button reads Start instead of Restart and Stop is disabled; in the crashed state Stop stays enabled, because setting the manual-stop flag is what halts the crash-recovery restart loop. Closes #7103
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Tag, Tooltip } from 'antd';
|
import { Button, Modal, Tag, Tooltip } from 'antd';
|
||||||
import {
|
import {
|
||||||
ApiOutlined,
|
ApiOutlined,
|
||||||
ArrowUpOutlined,
|
ArrowUpOutlined,
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
CloudServerOutlined,
|
CloudServerOutlined,
|
||||||
ControlOutlined,
|
ControlOutlined,
|
||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
|
PlayCircleOutlined,
|
||||||
PoweroffOutlined,
|
PoweroffOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
@@ -26,7 +27,7 @@ interface OverviewActionBarProps {
|
|||||||
panelVersion: string;
|
panelVersion: string;
|
||||||
latestVersion: string;
|
latestVersion: string;
|
||||||
updateAvailable: boolean;
|
updateAvailable: boolean;
|
||||||
onStopXray: () => void;
|
onStopXray: () => void | Promise<void>;
|
||||||
onRestartXray: () => void;
|
onRestartXray: () => void;
|
||||||
onOpenLogs: () => void;
|
onOpenLogs: () => void;
|
||||||
onOpenXrayLogs: () => void;
|
onOpenXrayLogs: () => void;
|
||||||
@@ -45,6 +46,9 @@ interface BarAction {
|
|||||||
text: string;
|
text: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
primary?: boolean;
|
primary?: boolean;
|
||||||
|
danger?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
hint?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const XRAY_STATE_KEYS: Record<string, string> = {
|
const XRAY_STATE_KEYS: Record<string, string> = {
|
||||||
@@ -73,16 +77,18 @@ export default function OverviewActionBar({
|
|||||||
onOpenVersionSwitch,
|
onOpenVersionSwitch,
|
||||||
}: OverviewActionBarProps) {
|
}: OverviewActionBarProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [modal, modalContextHolder] = Modal.useModal();
|
||||||
const stateText = t(XRAY_STATE_KEYS[status.xray.state] ?? 'pages.index.xrayStatusUnknown');
|
const stateText = t(XRAY_STATE_KEYS[status.xray.state] ?? 'pages.index.xrayStatusUnknown');
|
||||||
const hasVersion = !!status.xray.version && status.xray.version !== 'Unknown';
|
const hasVersion = !!status.xray.version && status.xray.version !== 'Unknown';
|
||||||
const size = isMobile ? ('small' as const) : ('middle' as const);
|
const size = isMobile ? ('small' as const) : ('middle' as const);
|
||||||
|
const isStopped = status.xray.state === 'stop';
|
||||||
|
|
||||||
const actionGroups: BarAction[][] = [
|
const actionGroups: BarAction[][] = [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
key: 'restart',
|
key: 'restart',
|
||||||
icon: <ReloadOutlined aria-hidden />,
|
icon: isStopped ? <PlayCircleOutlined aria-hidden /> : <ReloadOutlined aria-hidden />,
|
||||||
text: t('pages.index.restartXray'),
|
text: t(isStopped ? 'pages.index.startXray' : 'pages.index.restartXray'),
|
||||||
onClick: onRestartXray,
|
onClick: onRestartXray,
|
||||||
primary: true,
|
primary: true,
|
||||||
},
|
},
|
||||||
@@ -90,7 +96,19 @@ export default function OverviewActionBar({
|
|||||||
key: 'stop',
|
key: 'stop',
|
||||||
icon: <PoweroffOutlined aria-hidden />,
|
icon: <PoweroffOutlined aria-hidden />,
|
||||||
text: t('pages.index.stopXray'),
|
text: t('pages.index.stopXray'),
|
||||||
onClick: onStopXray,
|
danger: true,
|
||||||
|
disabled: isStopped,
|
||||||
|
hint: isStopped ? t('pages.index.stopXrayDisabledHint') : undefined,
|
||||||
|
onClick: () =>
|
||||||
|
modal.confirm({
|
||||||
|
title: t('pages.index.stopXrayConfirmTitle'),
|
||||||
|
content: t('pages.index.stopXrayConfirmContent'),
|
||||||
|
okText: t('pages.index.stopXrayConfirmOk'),
|
||||||
|
okType: 'danger',
|
||||||
|
cancelText: t('cancel'),
|
||||||
|
focusable: { autoFocusButton: 'cancel' },
|
||||||
|
onOk: onStopXray,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -165,6 +183,7 @@ export default function OverviewActionBar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ov-bar">
|
<div className="ov-bar">
|
||||||
|
{modalContextHolder}
|
||||||
{status.xray.errorMsg ? (
|
{status.xray.errorMsg ? (
|
||||||
<Tooltip title={<span className="ov-error-detail">{status.xray.errorMsg}</span>}>
|
<Tooltip title={<span className="ov-error-detail">{status.xray.errorMsg}</span>}>
|
||||||
{statePill}
|
{statePill}
|
||||||
@@ -204,18 +223,21 @@ export default function OverviewActionBar({
|
|||||||
<Fragment key={group[0].key}>
|
<Fragment key={group[0].key}>
|
||||||
{groupIndex > 0 && <span className="ov-bar-sep" />}
|
{groupIndex > 0 && <span className="ov-bar-sep" />}
|
||||||
{group.map((action) => (
|
{group.map((action) => (
|
||||||
<Button
|
<Tooltip key={action.key} title={action.hint}>
|
||||||
key={action.key}
|
<Button
|
||||||
type={action.primary ? undefined : 'text'}
|
type={action.primary ? undefined : 'text'}
|
||||||
color={action.primary ? 'primary' : undefined}
|
color={action.primary ? 'primary' : undefined}
|
||||||
variant={action.primary ? 'outlined' : undefined}
|
variant={action.primary ? 'outlined' : undefined}
|
||||||
size={size}
|
size={size}
|
||||||
icon={action.icon}
|
danger={action.danger}
|
||||||
aria-label={action.text}
|
disabled={action.disabled}
|
||||||
onClick={action.onClick}
|
icon={action.icon}
|
||||||
>
|
aria-label={action.text}
|
||||||
{isMobile ? undefined : action.text}
|
onClick={action.onClick}
|
||||||
</Button>
|
>
|
||||||
|
{isMobile ? undefined : action.text}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { Status } from '@/models/status';
|
||||||
|
import OverviewActionBar from '@/pages/index/OverviewActionBar';
|
||||||
|
|
||||||
|
import { renderWithProviders } from './test-utils';
|
||||||
|
|
||||||
|
const noop = () => {};
|
||||||
|
|
||||||
|
function renderBar(
|
||||||
|
state: string,
|
||||||
|
{
|
||||||
|
onStopXray = noop,
|
||||||
|
onRestartXray = noop,
|
||||||
|
}: { onStopXray?: () => void; onRestartXray?: () => void } = {},
|
||||||
|
) {
|
||||||
|
const status = new Status();
|
||||||
|
status.xray.state = state;
|
||||||
|
renderWithProviders(
|
||||||
|
<OverviewActionBar
|
||||||
|
status={status}
|
||||||
|
isMobile={false}
|
||||||
|
accessLogEnable={false}
|
||||||
|
panelVersion="v3.10.1"
|
||||||
|
latestVersion="v3.10.1"
|
||||||
|
updateAvailable={false}
|
||||||
|
onStopXray={onStopXray}
|
||||||
|
onRestartXray={onRestartXray}
|
||||||
|
onOpenLogs={noop}
|
||||||
|
onOpenXrayLogs={noop}
|
||||||
|
onOpenAmneziaWGLogs={noop}
|
||||||
|
onOpenConfig={noop}
|
||||||
|
onOpenBackup={noop}
|
||||||
|
onOpenSystemHistory={noop}
|
||||||
|
onOpenXrayMetrics={noop}
|
||||||
|
onOpenPanelUpdate={noop}
|
||||||
|
onOpenVersionSwitch={noop}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop used to fire on the first click, so a mis-click killed every proxy connection (#7103).
|
||||||
|
describe('OverviewActionBar stop confirmation', () => {
|
||||||
|
it('does not stop Xray until the dialog is confirmed', async () => {
|
||||||
|
const onStopXray = vi.fn();
|
||||||
|
renderBar('running', { onStopXray });
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Stop' }));
|
||||||
|
expect(await screen.findAllByText('Stop Xray?')).not.toHaveLength(0);
|
||||||
|
expect(onStopXray).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
||||||
|
expect(onStopXray).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Stop' }));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getAllByRole('button', { name: 'Stop Xray' })).not.toHaveLength(0),
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getAllByRole('button', { name: 'Stop Xray' }).at(-1)!);
|
||||||
|
await waitFor(() => expect(onStopXray).toHaveBeenCalledOnce());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stop on a stopped core only errors and sets the manual-stop flag. In the crash
|
||||||
|
// state Stop is the one dashboard control that halts the restart loop.
|
||||||
|
describe('OverviewActionBar start / disabled stop', () => {
|
||||||
|
it('offers Start and disables Stop when the core is stopped', () => {
|
||||||
|
const onRestartXray = vi.fn();
|
||||||
|
renderBar('stop', { onRestartXray });
|
||||||
|
|
||||||
|
expect(screen.queryByRole('button', { name: 'Restart' })).toBeNull();
|
||||||
|
expect((screen.getByRole('button', { name: 'Stop' }) as HTMLButtonElement).disabled).toBe(true);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Start' }));
|
||||||
|
expect(onRestartXray).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps Restart and leaves Stop usable when the core errored', async () => {
|
||||||
|
const onStopXray = vi.fn();
|
||||||
|
renderBar('error', { onStopXray });
|
||||||
|
|
||||||
|
expect(screen.getByRole('button', { name: 'Restart' })).toBeTruthy();
|
||||||
|
const stop = screen.getByRole('button', { name: 'Stop' }) as HTMLButtonElement;
|
||||||
|
expect(stop.disabled).toBe(false);
|
||||||
|
fireEvent.click(stop);
|
||||||
|
fireEvent.click(await screen.findByRole('button', { name: 'Stop Xray' }));
|
||||||
|
await waitFor(() => expect(onStopXray).toHaveBeenCalledOnce());
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "إيقاف",
|
"stopXray": "إيقاف",
|
||||||
"restartXray": "إعادة تشغيل",
|
"restartXray": "إعادة تشغيل",
|
||||||
|
"startXray": "تشغيل",
|
||||||
|
"stopXrayDisabledHint": "Xray غير قيد التشغيل",
|
||||||
|
"stopXrayConfirmTitle": "إيقاف Xray؟",
|
||||||
|
"stopXrayConfirmContent": "سيؤدي إيقاف Xray إلى قطع جميع اتصالات البروكسي فورًا. هل تريد المتابعة؟",
|
||||||
|
"stopXrayConfirmOk": "إيقاف Xray",
|
||||||
"xraySwitch": "النسخة",
|
"xraySwitch": "النسخة",
|
||||||
"xrayUpdates": "تحديثات Xray",
|
"xrayUpdates": "تحديثات Xray",
|
||||||
"xraySwitchClickDesk": "اختار بحذر، النسخ القديمة ممكن ما تتوافقش مع الإعدادات الحالية.",
|
"xraySwitchClickDesk": "اختار بحذر، النسخ القديمة ممكن ما تتوافقش مع الإعدادات الحالية.",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "Stop",
|
"stopXray": "Stop",
|
||||||
"restartXray": "Restart",
|
"restartXray": "Restart",
|
||||||
|
"startXray": "Start",
|
||||||
|
"stopXrayDisabledHint": "Xray is not running",
|
||||||
|
"stopXrayConfirmTitle": "Stop Xray?",
|
||||||
|
"stopXrayConfirmContent": "Stopping Xray will immediately interrupt all proxy connections. Are you sure you want to continue?",
|
||||||
|
"stopXrayConfirmOk": "Stop Xray",
|
||||||
"xraySwitch": "Version",
|
"xraySwitch": "Version",
|
||||||
"xrayUpdates": "Xray Updates",
|
"xrayUpdates": "Xray Updates",
|
||||||
"xraySwitchClickDesk": "Choose carefully, as older versions may not be compatible with current configurations.",
|
"xraySwitchClickDesk": "Choose carefully, as older versions may not be compatible with current configurations.",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "Detener",
|
"stopXray": "Detener",
|
||||||
"restartXray": "Reiniciar",
|
"restartXray": "Reiniciar",
|
||||||
|
"startXray": "Iniciar",
|
||||||
|
"stopXrayDisabledHint": "Xray no se está ejecutando",
|
||||||
|
"stopXrayConfirmTitle": "¿Detener Xray?",
|
||||||
|
"stopXrayConfirmContent": "Detener Xray interrumpirá de inmediato todas las conexiones proxy. ¿Seguro que quieres continuar?",
|
||||||
|
"stopXrayConfirmOk": "Detener Xray",
|
||||||
"xraySwitch": "Versión",
|
"xraySwitch": "Versión",
|
||||||
"xrayUpdates": "Actualizaciones de Xray",
|
"xrayUpdates": "Actualizaciones de Xray",
|
||||||
"xraySwitchClickDesk": "Elige sabiamente, ya que las versiones anteriores pueden no ser compatibles con las configuraciones actuales.",
|
"xraySwitchClickDesk": "Elige sabiamente, ya que las versiones anteriores pueden no ser compatibles con las configuraciones actuales.",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "توقف",
|
"stopXray": "توقف",
|
||||||
"restartXray": "راهاندازی مجدد",
|
"restartXray": "راهاندازی مجدد",
|
||||||
|
"startXray": "شروع",
|
||||||
|
"stopXrayDisabledHint": "Xray در حال اجرا نیست",
|
||||||
|
"stopXrayConfirmTitle": "Xray متوقف شود؟",
|
||||||
|
"stopXrayConfirmContent": "توقف Xray بلافاصله همه اتصالات پراکسی را قطع میکند. آیا مطمئن هستید؟",
|
||||||
|
"stopXrayConfirmOk": "توقف Xray",
|
||||||
"xraySwitch": "نسخه",
|
"xraySwitch": "نسخه",
|
||||||
"xrayUpdates": "بهروزرسانیهای Xray",
|
"xrayUpdates": "بهروزرسانیهای Xray",
|
||||||
"xraySwitchClickDesk": "لطفا بادقت انتخاب کنید. درصورت انتخاب نسخه قدیمیتر، امکان ناهماهنگی با پیکربندی فعلی وجود دارد",
|
"xraySwitchClickDesk": "لطفا بادقت انتخاب کنید. درصورت انتخاب نسخه قدیمیتر، امکان ناهماهنگی با پیکربندی فعلی وجود دارد",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "Hentikan",
|
"stopXray": "Hentikan",
|
||||||
"restartXray": "Mulai ulang",
|
"restartXray": "Mulai ulang",
|
||||||
|
"startXray": "Mulai",
|
||||||
|
"stopXrayDisabledHint": "Xray tidak berjalan",
|
||||||
|
"stopXrayConfirmTitle": "Hentikan Xray?",
|
||||||
|
"stopXrayConfirmContent": "Menghentikan Xray akan langsung memutus semua koneksi proxy. Yakin ingin melanjutkan?",
|
||||||
|
"stopXrayConfirmOk": "Hentikan Xray",
|
||||||
"xraySwitch": "Versi",
|
"xraySwitch": "Versi",
|
||||||
"xrayUpdates": "Pembaruan Xray",
|
"xrayUpdates": "Pembaruan Xray",
|
||||||
"xraySwitchClickDesk": "Pilih dengan hati-hati, karena versi yang lebih lama mungkin tidak kompatibel dengan konfigurasi saat ini.",
|
"xraySwitchClickDesk": "Pilih dengan hati-hati, karena versi yang lebih lama mungkin tidak kompatibel dengan konfigurasi saat ini.",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "停止",
|
"stopXray": "停止",
|
||||||
"restartXray": "再起動",
|
"restartXray": "再起動",
|
||||||
|
"startXray": "開始",
|
||||||
|
"stopXrayDisabledHint": "Xray は実行されていません",
|
||||||
|
"stopXrayConfirmTitle": "Xray を停止しますか?",
|
||||||
|
"stopXrayConfirmContent": "Xray を停止すると、すべてのプロキシ接続が直ちに切断されます。続行しますか?",
|
||||||
|
"stopXrayConfirmOk": "Xray を停止",
|
||||||
"xraySwitch": "バージョン",
|
"xraySwitch": "バージョン",
|
||||||
"xrayUpdates": "Xrayの更新",
|
"xrayUpdates": "Xrayの更新",
|
||||||
"xraySwitchClickDesk": "慎重に選択してください。古いバージョンは現在の設定と互換性がない可能性があります。",
|
"xraySwitchClickDesk": "慎重に選択してください。古いバージョンは現在の設定と互換性がない可能性があります。",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "Parar",
|
"stopXray": "Parar",
|
||||||
"restartXray": "Reiniciar",
|
"restartXray": "Reiniciar",
|
||||||
|
"startXray": "Iniciar",
|
||||||
|
"stopXrayDisabledHint": "O Xray não está em execução",
|
||||||
|
"stopXrayConfirmTitle": "Parar o Xray?",
|
||||||
|
"stopXrayConfirmContent": "Parar o Xray interromperá imediatamente todas as conexões de proxy. Tem certeza de que deseja continuar?",
|
||||||
|
"stopXrayConfirmOk": "Parar o Xray",
|
||||||
"xraySwitch": "Versão",
|
"xraySwitch": "Versão",
|
||||||
"xrayUpdates": "Atualizações do Xray",
|
"xrayUpdates": "Atualizações do Xray",
|
||||||
"xraySwitchClickDesk": "Escolha com cuidado, pois versões mais antigas podem não ser compatíveis com as configurações atuais.",
|
"xraySwitchClickDesk": "Escolha com cuidado, pois versões mais antigas podem não ser compatíveis com as configurações atuais.",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "Стоп",
|
"stopXray": "Стоп",
|
||||||
"restartXray": "Перезапуск",
|
"restartXray": "Перезапуск",
|
||||||
|
"startXray": "Запуск",
|
||||||
|
"stopXrayDisabledHint": "Xray не запущен",
|
||||||
|
"stopXrayConfirmTitle": "Остановить Xray?",
|
||||||
|
"stopXrayConfirmContent": "Остановка Xray немедленно разорвёт все прокси-соединения. Продолжить?",
|
||||||
|
"stopXrayConfirmOk": "Остановить Xray",
|
||||||
"xraySwitch": "Выбор версии",
|
"xraySwitch": "Выбор версии",
|
||||||
"xrayUpdates": "Обновления Xray",
|
"xrayUpdates": "Обновления Xray",
|
||||||
"xraySwitchClickDesk": "Важно: старые версии могут не поддерживать текущие настройки",
|
"xraySwitchClickDesk": "Важно: старые версии могут не поддерживать текущие настройки",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "Zastaviť",
|
"stopXray": "Zastaviť",
|
||||||
"restartXray": "Reštartovať",
|
"restartXray": "Reštartovať",
|
||||||
|
"startXray": "Spustiť",
|
||||||
|
"stopXrayDisabledHint": "Xray nebeží",
|
||||||
|
"stopXrayConfirmTitle": "Zastaviť Xray?",
|
||||||
|
"stopXrayConfirmContent": "Zastavenie Xray okamžite preruší všetky proxy spojenia. Naozaj chcete pokračovať?",
|
||||||
|
"stopXrayConfirmOk": "Zastaviť Xray",
|
||||||
"xraySwitch": "Verzia",
|
"xraySwitch": "Verzia",
|
||||||
"xrayUpdates": "Aktualizácie Xray",
|
"xrayUpdates": "Aktualizácie Xray",
|
||||||
"xraySwitchClickDesk": "Verziu vyberajte opatrne. Staršie verzie nemusia podporovať aktuálnu konfiguráciu.",
|
"xraySwitchClickDesk": "Verziu vyberajte opatrne. Staršie verzie nemusia podporovať aktuálnu konfiguráciu.",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "Durdur",
|
"stopXray": "Durdur",
|
||||||
"restartXray": "Yeniden Başlat",
|
"restartXray": "Yeniden Başlat",
|
||||||
|
"startXray": "Başlat",
|
||||||
|
"stopXrayDisabledHint": "Xray çalışmıyor",
|
||||||
|
"stopXrayConfirmTitle": "Xray durdurulsun mu?",
|
||||||
|
"stopXrayConfirmContent": "Xray'i durdurmak tüm proxy bağlantılarını anında keser. Devam etmek istediğinizden emin misiniz?",
|
||||||
|
"stopXrayConfirmOk": "Xray'i durdur",
|
||||||
"xraySwitch": "Sürüm",
|
"xraySwitch": "Sürüm",
|
||||||
"xrayUpdates": "Xray Güncellemeleri",
|
"xrayUpdates": "Xray Güncellemeleri",
|
||||||
"xraySwitchClickDesk": "Dikkatli seçin, eski sürümler mevcut yapılandırmalarla uyumlu olmayabilir.",
|
"xraySwitchClickDesk": "Dikkatli seçin, eski sürümler mevcut yapılandırmalarla uyumlu olmayabilir.",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "Стоп",
|
"stopXray": "Стоп",
|
||||||
"restartXray": "Перезапуск",
|
"restartXray": "Перезапуск",
|
||||||
|
"startXray": "Запуск",
|
||||||
|
"stopXrayDisabledHint": "Xray не запущено",
|
||||||
|
"stopXrayConfirmTitle": "Зупинити Xray?",
|
||||||
|
"stopXrayConfirmContent": "Зупинка Xray негайно розірве всі проксі-з'єднання. Продовжити?",
|
||||||
|
"stopXrayConfirmOk": "Зупинити Xray",
|
||||||
"xraySwitch": "Версія",
|
"xraySwitch": "Версія",
|
||||||
"xrayUpdates": "Оновлення Xray",
|
"xrayUpdates": "Оновлення Xray",
|
||||||
"xraySwitchClickDesk": "Вибирайте уважно, оскільки старіші версії можуть бути несумісними з поточними конфігураціями.",
|
"xraySwitchClickDesk": "Вибирайте уважно, оскільки старіші версії можуть бути несумісними з поточними конфігураціями.",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "Dừng",
|
"stopXray": "Dừng",
|
||||||
"restartXray": "Khởi động lại",
|
"restartXray": "Khởi động lại",
|
||||||
|
"startXray": "Khởi động",
|
||||||
|
"stopXrayDisabledHint": "Xray không chạy",
|
||||||
|
"stopXrayConfirmTitle": "Dừng Xray?",
|
||||||
|
"stopXrayConfirmContent": "Dừng Xray sẽ ngắt ngay lập tức mọi kết nối proxy. Bạn có chắc muốn tiếp tục?",
|
||||||
|
"stopXrayConfirmOk": "Dừng Xray",
|
||||||
"xraySwitch": "Phiên bản",
|
"xraySwitch": "Phiên bản",
|
||||||
"xrayUpdates": "Cập nhật Xray",
|
"xrayUpdates": "Cập nhật Xray",
|
||||||
"xraySwitchClickDesk": "Hãy lựa chọn thận trọng, vì các phiên bản cũ có thể không tương thích với các cấu hình hiện tại.",
|
"xraySwitchClickDesk": "Hãy lựa chọn thận trọng, vì các phiên bản cũ có thể không tương thích với các cấu hình hiện tại.",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "停止",
|
"stopXray": "停止",
|
||||||
"restartXray": "重启",
|
"restartXray": "重启",
|
||||||
|
"startXray": "启动",
|
||||||
|
"stopXrayDisabledHint": "Xray 未运行",
|
||||||
|
"stopXrayConfirmTitle": "停止 Xray?",
|
||||||
|
"stopXrayConfirmContent": "停止 Xray 将立即中断所有代理连接。确定要继续吗?",
|
||||||
|
"stopXrayConfirmOk": "停止 Xray",
|
||||||
"xraySwitch": "版本",
|
"xraySwitch": "版本",
|
||||||
"xrayUpdates": "Xray 更新",
|
"xrayUpdates": "Xray 更新",
|
||||||
"xraySwitchClickDesk": "请谨慎选择,因为较旧版本可能与当前配置不兼容",
|
"xraySwitchClickDesk": "请谨慎选择,因为较旧版本可能与当前配置不兼容",
|
||||||
|
|||||||
@@ -151,6 +151,11 @@
|
|||||||
"xrayStatus": "Xray",
|
"xrayStatus": "Xray",
|
||||||
"stopXray": "停止",
|
"stopXray": "停止",
|
||||||
"restartXray": "重新啟動",
|
"restartXray": "重新啟動",
|
||||||
|
"startXray": "啟動",
|
||||||
|
"stopXrayDisabledHint": "Xray 未執行",
|
||||||
|
"stopXrayConfirmTitle": "停止 Xray?",
|
||||||
|
"stopXrayConfirmContent": "停止 Xray 將立即中斷所有代理連線。確定要繼續嗎?",
|
||||||
|
"stopXrayConfirmOk": "停止 Xray",
|
||||||
"xraySwitch": "版本",
|
"xraySwitch": "版本",
|
||||||
"xrayUpdates": "Xray 更新",
|
"xrayUpdates": "Xray 更新",
|
||||||
"xraySwitchClickDesk": "請謹慎選擇,因為較舊版本可能與當前配置不相容",
|
"xraySwitchClickDesk": "請謹慎選擇,因為較舊版本可能與當前配置不相容",
|
||||||
|
|||||||
Reference in New Issue
Block a user