mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-01-13 01:02:46 +00:00
feat: New structure panel, new theme
This commit is contained in:
parent
beac0cdf67
commit
fa7759280b
9 changed files with 157 additions and 12 deletions
2
web/assets/css/custom.min.css
vendored
2
web/assets/css/custom.min.css
vendored
File diff suppressed because one or more lines are too long
|
|
@ -7,7 +7,8 @@
|
|||
<a-layout id="content-layout">
|
||||
<a-layout-content :style="{ padding: '24px 16px' }">
|
||||
<a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||
<a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-if="loadingStates.fetched">
|
||||
<transition name="list" appear>
|
||||
<a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-if="loadingStates.fetched">
|
||||
<a-col>
|
||||
<a-card size="small" :style="{ padding: '16px' }" hoverable>
|
||||
<h2>{{ i18n "pages.clients.title" }}</h2>
|
||||
|
|
@ -133,6 +134,7 @@
|
|||
<a-spin tip='{{ i18n "loading" }}'></a-spin>
|
||||
</a-card>
|
||||
</a-row>
|
||||
</transition>
|
||||
</a-spin>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<a-theme-switch></a-theme-switch>
|
||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="activeTab"
|
||||
@click="({key}) => openLink(key)">
|
||||
<a-menu-item v-for="tab in tabs" :key="tab.key">
|
||||
<a-menu-item v-for="tab in tabs" :key="tab.key" :data-menu-key="tab.key">
|
||||
<a-icon :type="tab.icon"></a-icon>
|
||||
<span v-text="tab.title"></span>
|
||||
</a-menu-item>
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
<a-theme-switch></a-theme-switch>
|
||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="activeTab"
|
||||
@click="({key}) => openLink(key)">
|
||||
<a-menu-item v-for="tab in tabs" :key="tab.key">
|
||||
<a-menu-item v-for="tab in tabs" :key="tab.key" :data-menu-key="tab.key">
|
||||
<a-icon :type="tab.icon"></a-icon>
|
||||
<span v-text="tab.title"></span>
|
||||
</a-menu-item>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<a-menu-item id="change-theme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOff()">
|
||||
<span>{{ i18n "menu.dark" }}</span>
|
||||
<a-switch :style="{ marginLeft: '2px' }" size="small" :default-checked="themeSwitcher.isDarkTheme"
|
||||
@change="themeSwitcher.toggleTheme()"></a-switch>
|
||||
:disabled="themeSwitcher.isGlassMorphism" @change="themeSwitcher.toggleTheme()"></a-switch>
|
||||
</a-menu-item>
|
||||
<a-menu-item id="change-theme-ultra" v-if="themeSwitcher.isDarkTheme" class="ant-menu-theme-switch"
|
||||
@mousedown="themeSwitcher.animationsOffUltra()">
|
||||
|
|
@ -17,6 +17,12 @@
|
|||
<a-checkbox :style="{ marginLeft: '2px' }" :checked="themeSwitcher.isUltra"
|
||||
@click="themeSwitcher.toggleUltra()"></a-checkbox>
|
||||
</a-menu-item>
|
||||
<a-menu-item id="change-theme-glass" class="ant-menu-theme-switch"
|
||||
@mousedown="themeSwitcher.animationsOffGlass()">
|
||||
<span>{{ i18n "menu.glassMorphism" }}</span>
|
||||
<a-switch :style="{ marginLeft: '2px' }" size="small" :default-checked="themeSwitcher.isGlassMorphism"
|
||||
@change="themeSwitcher.toggleGlassMorphism()"></a-switch>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</a-menu>
|
||||
</template>
|
||||
|
|
@ -26,13 +32,17 @@
|
|||
<template>
|
||||
<a-space @mousedown="themeSwitcher.animationsOff()" id="change-theme" direction="vertical" :size="10" :style="{ width: '100%' }">
|
||||
<a-space direction="horizontal" size="small">
|
||||
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
|
||||
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" :disabled="themeSwitcher.isGlassMorphism" @change="themeSwitcher.toggleTheme()"></a-switch>
|
||||
<span>{{ i18n "menu.dark" }}</span>
|
||||
</a-space>
|
||||
<a-space v-if="themeSwitcher.isDarkTheme" direction="horizontal" size="small">
|
||||
<a-checkbox :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()"></a-checkbox>
|
||||
<span>{{ i18n "menu.ultraDark" }}</span>
|
||||
</a-space>
|
||||
<a-space direction="horizontal" size="small">
|
||||
<a-switch size="small" :default-checked="themeSwitcher.isGlassMorphism" @change="themeSwitcher.toggleGlassMorphism()"></a-switch>
|
||||
<span>{{ i18n "menu.glassMorphism" }}</span>
|
||||
</a-space>
|
||||
</a-space>
|
||||
</template>
|
||||
{{end}}
|
||||
|
|
@ -40,10 +50,34 @@
|
|||
{{define "component/aThemeSwitch"}}
|
||||
<script>
|
||||
function createThemeSwitcher() {
|
||||
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||
const isUltra = localStorage.getItem('isUltraDarkThemeEnabled') === 'true';
|
||||
if (isUltra) {
|
||||
document.documentElement.setAttribute('data-theme', 'ultra-dark');
|
||||
let isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||
let isUltra = localStorage.getItem('isUltraDarkThemeEnabled') === 'true';
|
||||
// Glass Morphism включен по умолчанию, если не установлено явно
|
||||
let isGlassMorphism = localStorage.getItem('isGlassMorphismEnabled');
|
||||
if (isGlassMorphism === null) {
|
||||
isGlassMorphism = true; // По умолчанию включен
|
||||
localStorage.setItem('isGlassMorphismEnabled', 'true');
|
||||
} else {
|
||||
isGlassMorphism = isGlassMorphism === 'true';
|
||||
}
|
||||
// Если включен Glass Morphism, отключаем темную тему
|
||||
if (isGlassMorphism) {
|
||||
isDarkTheme = false;
|
||||
isUltra = false;
|
||||
localStorage.setItem('dark-mode', 'false');
|
||||
localStorage.setItem('isUltraDarkThemeEnabled', 'false');
|
||||
document.documentElement.setAttribute('data-glass-morphism', 'true');
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
} else {
|
||||
// Если включена темная тема, отключаем Glass Morphism
|
||||
if (isDarkTheme) {
|
||||
isGlassMorphism = false;
|
||||
localStorage.setItem('isGlassMorphismEnabled', 'false');
|
||||
document.documentElement.removeAttribute('data-glass-morphism');
|
||||
}
|
||||
if (isUltra) {
|
||||
document.documentElement.setAttribute('data-theme', 'ultra-dark');
|
||||
}
|
||||
}
|
||||
const theme = isDarkTheme ? 'dark' : 'light';
|
||||
document.querySelector('body').setAttribute('class', theme);
|
||||
|
|
@ -68,13 +102,33 @@
|
|||
document.documentElement.removeAttribute('data-theme-animations');
|
||||
});
|
||||
},
|
||||
animationsOffGlass() {
|
||||
document.documentElement.setAttribute('data-theme-animations', 'off');
|
||||
const themeAnimationsGlass = document.querySelector('#change-theme-glass');
|
||||
themeAnimationsGlass.addEventListener('mouseleave', () => {
|
||||
document.documentElement.removeAttribute('data-theme-animations');
|
||||
});
|
||||
themeAnimationsGlass.addEventListener('touchend', () => {
|
||||
document.documentElement.removeAttribute('data-theme-animations');
|
||||
});
|
||||
},
|
||||
isDarkTheme,
|
||||
isUltra,
|
||||
isGlassMorphism,
|
||||
get currentTheme() {
|
||||
return this.isDarkTheme ? 'dark' : 'light';
|
||||
},
|
||||
toggleTheme() {
|
||||
if (this.isGlassMorphism) {
|
||||
return; // Не позволяем включать темную тему когда включен Glass Morphism
|
||||
}
|
||||
this.isDarkTheme = !this.isDarkTheme;
|
||||
if (this.isDarkTheme) {
|
||||
// Если включаем темную тему, отключаем Glass Morphism
|
||||
this.isGlassMorphism = false;
|
||||
document.documentElement.removeAttribute('data-glass-morphism');
|
||||
localStorage.setItem('isGlassMorphismEnabled', 'false');
|
||||
}
|
||||
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||
document.querySelector('body').setAttribute('class', this.isDarkTheme ? 'dark' : 'light');
|
||||
document.getElementById('message').className = themeSwitcher.currentTheme;
|
||||
|
|
@ -87,6 +141,23 @@
|
|||
document.documentElement.removeAttribute('data-theme');
|
||||
}
|
||||
localStorage.setItem('isUltraDarkThemeEnabled', this.isUltra.toString());
|
||||
},
|
||||
toggleGlassMorphism() {
|
||||
this.isGlassMorphism = !this.isGlassMorphism;
|
||||
if (this.isGlassMorphism) {
|
||||
// Если включаем Glass Morphism, отключаем темную тему
|
||||
this.isDarkTheme = false;
|
||||
document.querySelector('body').setAttribute('class', 'light');
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
this.isUltra = false;
|
||||
localStorage.setItem('dark-mode', 'false');
|
||||
localStorage.setItem('isUltraDarkThemeEnabled', 'false');
|
||||
document.documentElement.setAttribute('data-glass-morphism', 'true');
|
||||
document.getElementById('message').className = 'light';
|
||||
} else {
|
||||
document.documentElement.removeAttribute('data-glass-morphism');
|
||||
}
|
||||
localStorage.setItem('isGlassMorphismEnabled', this.isGlassMorphism.toString());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
<a-sidebar></a-sidebar>
|
||||
<a-layout id="content-layout">
|
||||
<a-layout-content :style="{ padding: '24px 16px' }">
|
||||
<a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-if="loadingStates.fetched && multiNodeMode">
|
||||
<transition name="list" appear>
|
||||
<a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-if="loadingStates.fetched && multiNodeMode">
|
||||
<a-col>
|
||||
<a-card size="small" :style="{ padding: '16px' }" hoverable>
|
||||
<h2>{{ i18n "pages.hosts.title" }}</h2>
|
||||
|
|
@ -69,6 +70,7 @@
|
|||
<a-spin tip='{{ i18n "loading" }}'></a-spin>
|
||||
</a-card>
|
||||
</a-row>
|
||||
</transition>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@
|
|||
this.loadingStates.spinning = true;
|
||||
const msg = await HttpUtil.post('/login', this.user);
|
||||
if (msg.success) {
|
||||
// Устанавливаем флаг для показа popup "Что нового?" после логина
|
||||
sessionStorage.setItem('showWhatsNew', 'true');
|
||||
location.href = basePath + 'panel/';
|
||||
}
|
||||
this.loadingStates.spinning = false;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
<a-sidebar></a-sidebar>
|
||||
<a-layout id="content-layout">
|
||||
<a-layout-content :style="{ padding: '24px 16px' }">
|
||||
<a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-if="loadingStates.fetched">
|
||||
<transition name="list" appear>
|
||||
<a-row :gutter="[isMobile ? 8 : 16, isMobile ? 0 : 12]" v-if="loadingStates.fetched">
|
||||
<a-col>
|
||||
<a-card size="small" :style="{ padding: '16px' }" hoverable>
|
||||
<h2>{{ i18n "pages.nodes.title" }}</h2>
|
||||
|
|
@ -95,6 +96,7 @@
|
|||
<a-spin tip='{{ i18n "loading" }}'></a-spin>
|
||||
</a-card>
|
||||
</a-row>
|
||||
</transition>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
|
|
|
|||
|
|
@ -740,6 +740,7 @@
|
|||
"theme" = "Theme"
|
||||
"dark" = "Dark"
|
||||
"ultraDark" = "Ultra Dark"
|
||||
"glassMorphism" = "Glass Morphism"
|
||||
"dashboard" = "Overview"
|
||||
"inbounds" = "Inbounds"
|
||||
"clients" = "Clients"
|
||||
|
|
@ -749,6 +750,38 @@
|
|||
"hosts" = "Hosts"
|
||||
"logout" = "Log Out"
|
||||
"link" = "Manage"
|
||||
"tutorial" = "Tutorial"
|
||||
"restartTutorial" = "Restart Tutorial"
|
||||
|
||||
[tutorial]
|
||||
"title" = "Web Panel Menu Guide"
|
||||
"next" = "Next"
|
||||
"prev" = "Previous"
|
||||
"skip" = "Skip"
|
||||
"finish" = "Finish"
|
||||
"step" = "Step"
|
||||
"of" = "of"
|
||||
"dashboardTitle" = "1. Panel"
|
||||
"dashboardDesc" = "Main interface for managing the entire system. Through the panel we:\n\n• Configure and control nodes (servers with xray)\n• Manage clients\n• Configure inbounds"
|
||||
"dashboardHint" = "Panel is the control center. Here you create nodes, inbounds and assign clients."
|
||||
"nodeTitle" = "2. Node"
|
||||
"nodeDesc" = "Separate Xray core with API for communication with the panel. The node handles connections and serves as the 'brain' for inbounds."
|
||||
"nodeHint" = "Node is the server core. The panel communicates with it via API to manage configs and connections."
|
||||
"inboundTitle" = "3. Inbound"
|
||||
"inboundDesc" = "Configuration or profile for a node.\n\n• Creates a connection to a node\n• Subscribes to one or more nodes"
|
||||
"inboundHint" = "Inbound is a connection profile. Through it, clients get access to the required nodes."
|
||||
"clientTitle" = "4. Client"
|
||||
"clientDesc" = "System user who can be assigned one or more inbounds.\n\nFor example:\n• Inbound 1 → whitelist node\n• Inbound 2 → regular foreign server\n\nClient can use any or all inbounds assigned to them"
|
||||
"clientHint" = "Client is a user. Assign inbounds to them so they can connect to the required nodes."
|
||||
"hostTitle" = "5. Hosts"
|
||||
"hostDesc" = "External addresses for connection.\n\n• Proxy balancer that hides direct node addresses\n• Can distribute load between multiple nodes\n• Replace node address with the required host in inbound\n\nExample:\nInbound connects to a balancer host, which then distributes the connection to real nodes"
|
||||
"hostHint" = "Hosts are virtual addresses. Use them for load balancing and hiding real servers."
|
||||
"settingsTitle" = "6. Panel Settings"
|
||||
"settingsDesc" = "Section for general panel configuration.\n\n• Enable/disable various features\n• Configure panel appearance and behavior"
|
||||
"settingsHint" = "Panel Settings — here you can manage features and panel configuration."
|
||||
"xrayTitle" = "7. Xray Configuration"
|
||||
"xrayDesc" = "Section for fine-tuning Xray core.\n\n• Routing\n• Connection parameters and traffic routing\n• Additional advanced node configs"
|
||||
"xrayHint" = "Xray Configuration — for advanced core configuration, routing management and other node parameters."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "The parameters have been changed."
|
||||
|
|
|
|||
|
|
@ -740,6 +740,7 @@
|
|||
"theme" = "Тема"
|
||||
"dark" = "Темная"
|
||||
"ultraDark" = "Очень темная"
|
||||
"glassMorphism" = "Glass Morphism"
|
||||
"dashboard" = "Обзор"
|
||||
"inbounds" = "Подключения"
|
||||
"clients" = "Клиенты"
|
||||
|
|
@ -749,6 +750,38 @@
|
|||
"hosts" = "Хосты"
|
||||
"logout" = "Выйти"
|
||||
"link" = "Управление"
|
||||
"tutorial" = "Обучалка"
|
||||
"restartTutorial" = "Повторить обучение"
|
||||
|
||||
[tutorial]
|
||||
"title" = "Инструкция по меню веб-панели"
|
||||
"next" = "Далее"
|
||||
"prev" = "Назад"
|
||||
"skip" = "Пропустить"
|
||||
"finish" = "Завершить"
|
||||
"step" = "Шаг"
|
||||
"of" = "из"
|
||||
"dashboardTitle" = "1. Панель"
|
||||
"dashboardDesc" = "Главный интерфейс для управления всей системой. Через панель мы:\n\n• Настраиваем и контролируем ноды (серверы с xray)\n• Управляем клиентами\n• Настраиваем инбаунды"
|
||||
"dashboardHint" = "Панель — это центр управления. Здесь создаются ноды, инбаунды и назначаются клиенты."
|
||||
"nodeTitle" = "2. Нода"
|
||||
"nodeDesc" = "Отдельное ядро Xray с API для связи с панелью. Нода выполняет работу по обработке подключений и служит «мозгом» для инбаундов."
|
||||
"nodeHint" = "Нода — это серверное ядро. Панель взаимодействует с ним через API, чтобы управлять конфигами и подключениями."
|
||||
"inboundTitle" = "3. Инбаунд"
|
||||
"inboundDesc" = "Конфигурация или профиль для ноды.\n\n• Создаётся подключение к ноде\n• Подписывается на одну или несколько нод"
|
||||
"inboundHint" = "Инбаунд — это профиль подключения. Через него клиенты получают доступ к нужным нодам."
|
||||
"clientTitle" = "4. Клиент"
|
||||
"clientDesc" = "Пользователь системы, которому можно назначать один или несколько инбаундов.\n\nНапример:\n• Инбаунд 1 → нода из белого списка\n• Инбаунд 2 → обычный забугорный сервер\n\nКлиент может использовать любой или все инбаунды, которые ему назначены"
|
||||
"clientHint" = "Клиент — это пользователь. Назначайте ему инбаунды, чтобы он мог подключаться к нужным нодам."
|
||||
"hostTitle" = "5. Хосты"
|
||||
"hostDesc" = "Внешние адреса для подключения.\n\n• Прокси-балансир, скрывающий прямые адреса нод\n• Можно распределять нагрузку между несколькими нодами\n• Подменяем адрес ноды на нужный хост в инбаунде\n\nПример:\nИнбаунд подключается к хосту-балансиру, а тот уже распределяет подключение на реальные ноды"
|
||||
"hostHint" = "Хосты — это виртуальные адреса. Используйте их для балансировки нагрузки и скрытия реальных серверов."
|
||||
"settingsTitle" = "6. Настройки панели"
|
||||
"settingsDesc" = "Раздел для общей конфигурации панели.\n\n• Включение/отключение различных функций\n• Настройка внешнего вида и поведения панели"
|
||||
"settingsHint" = "Настройки панели — здесь вы можете управлять функциями и конфигурацией самой панели."
|
||||
"xrayTitle" = "7. Конфигурация Xray"
|
||||
"xrayDesc" = "Раздел для тонкой настройки ядра Xray.\n\n• Роутинг\n• Параметры соединений и маршрутизации трафика\n• Дополнительные продвинутые конфиги ноды"
|
||||
"xrayHint" = "Конфигурация Xray — для продвинутой конфигурации ядра, управления роутингом и другими параметрами ноды."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Настройки изменены"
|
||||
|
|
|
|||
Loading…
Reference in a new issue