diff --git a/API/utils/xui.py b/API/utils/xui.py
index 77628ec..71d80a5 100644
--- a/API/utils/xui.py
+++ b/API/utils/xui.py
@@ -12,7 +12,7 @@ class Configs:
self.client_email = client_email
self.display_name = display_name
- async def legacy_payload(self, data: dict,) -> dict:
+ def legacy_payload(self, data: dict,) -> dict:
inbounds = data.get("obj")
inbound = next((i for i in inbounds if i.get("id") == self.inbound_id), None)
if not inbound:
@@ -44,7 +44,7 @@ class Configs:
})
}
- async def legacy_config(self, data: dict) -> str:
+ def legacy_config(self, data: dict) -> str:
inbounds = data.get("obj")
inbound = next((i for i in inbounds if i.get("id") == self.inbound_id), None)
if not inbound:
diff --git a/Frontend/Nginx/spectralvpn_api.nginx b/Frontend/Nginx/spectralvpn_api.nginx
index 3abfedb..9f7c97f 100644
--- a/Frontend/Nginx/spectralvpn_api.nginx
+++ b/Frontend/Nginx/spectralvpn_api.nginx
@@ -5,24 +5,30 @@ server {
ssl_certificate /etc/letsencrypt/live/spectralvpn.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/spectralvpn.ru/privkey.pem;
-
+
location / {
if ($request_method = 'OPTIONS') {
- add_header 'Access-Control-Allow-Origin' 'https://spectralvpn.ru';
- add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, OPTIONS';
- add_header 'Access-Control-Allow-Headers' 'Content-Type';
- add_header 'Access-Control-Max-Age' 86400;
+ add_header 'Access-Control-Allow-Origin' 'https://spectralvpn.ru' always;
+ add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, OPTIONS' always;
+ add_header 'Access-Control-Allow-Headers' 'X-API-KEY, Content-Type, Authorization' always;
+ add_header 'Access-Control-Max-Age' 86400 always;
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
return 204;
}
- proxy_pass http://api:8000;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- proxy_set_header X-Forwarded-Host $host;
- proxy_set_header X-Forwarded-Port $server_port;
+ add_header 'Access-Control-Allow-Origin' 'https://spectralvpn.ru' always;
+ add_header 'Access-Control-Allow-Credentials' 'true' always;
+ add_header 'Access-Control-Expose-Headers' '*' always; # если нужно читать заголовки ответа
- add_header Access-Control-Allow-Origin "https://spectralvpn.ru" always;
+ proxy_pass http://api:8000;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Forwarded-Host $host;
+ proxy_set_header X-Forwarded-Port $server_port;
+
+ proxy_hide_header Access-Control-Allow-Origin;
+ proxy_hide_header Access-Control-Allow-Credentials;
}
}
\ No newline at end of file
diff --git a/Frontend/Web/JS/control-panel.js b/Frontend/Web/JS/control-panel.js
index 4f072f4..e4776c2 100644
--- a/Frontend/Web/JS/control-panel.js
+++ b/Frontend/Web/JS/control-panel.js
@@ -4,46 +4,77 @@ const getToken = () => localStorage.getItem("access_token");
const showNotification = (message, type = "success") => {
const color = type === "success" ? "#00ffff" : "#ff5555";
- const notification = document.createElement("div");
- notification.style.cssText = `
- position: fixed; top: 20px; right: 20px; padding: 15px 25px; border-radius: 12px;
- background: rgba(0,0,0,0.95); border: 1px solid ${color}; color: ${color};
- z-index: 10000; font-weight: 500; box-shadow: 0 4px 15px rgba(0,0,0,0.5);
+ const notif = document.createElement("div");
+ notif.style.cssText = `
+ position: fixed; top: 20px; right: 20px; padding: 14px 24px; border-radius: 12px;
+ background: rgba(0,0,0,0.96); border: 1px solid ${color}; color: ${color};
+ z-index: 10000; font-weight: 500; box-shadow: 0 4px 20px rgba(0,0,0,0.6);
`;
- notification.textContent = message;
- document.body.appendChild(notification);
- setTimeout(() => notification.remove(), 4000);
+ notif.textContent = message;
+ document.body.appendChild(notif);
+ setTimeout(() => notif.remove(), 4500);
};
+async function apiRequest(endpoint, options = {}) {
+ const token = getToken();
+ if (!token) {
+ showLoginModal();
+ return null;
+ }
+
+ const res = await fetch(`${API_BASE}${endpoint}`, {
+ ...options,
+ headers: {
+ "X-API-KEY": token,
+ "Content-Type": "application/json",
+ ...options.headers
+ }
+ });
+
+ if (res.status === 401) {
+ localStorage.removeItem("access_token");
+ showNotification("Сессия истекла. Войдите заново.", "error");
+ showLoginModal();
+ return null;
+ }
+
+ return res;
+}
+
+// Показать окно логина
+function showLoginModal() {
+ document.getElementById("loginModal").classList.add("active");
+ document.getElementById("mainContent").classList.add("hidden");
+ document.getElementById("userInfo").style.display = "none";
+}
+
+// Скрыть окно логина и показать кабинет
+function hideLoginModal() {
+ document.getElementById("loginModal").classList.remove("active");
+ document.getElementById("mainContent").classList.remove("hidden");
+ document.getElementById("userInfo").style.display = "flex";
+}
+
async function loadPanel() {
const token = getToken();
if (!token) {
- window.location.href = "register.html";
+ showLoginModal();
return;
}
- try {
- const res = await fetch(`${API_BASE}/config/get_info`, {
- method: "GET",
- headers: { "X-API-KEY": token }
- });
+ const res = await apiRequest("/config/get_info", { method: "GET" });
+ if (!res) return;
- if (res.status === 401) {
- localStorage.removeItem("access_token");
- window.location.href = "register.html";
- return;
- }
-
- if (!res.ok) throw new Error("Failed to load configs");
-
- const data = await res.json();
- document.getElementById("userEmail").textContent = "Аккаунт активен";
- renderConfigs(data.configs || []);
-
- } catch (err) {
- console.error(err);
+ if (!res.ok) {
showNotification("Ошибка загрузки данных", "error");
+ return;
}
+
+ const data = await res.json();
+
+ document.getElementById("userEmail").textContent = data.email || "Аккаунт активен";
+ hideLoginModal(); // ← Важно!
+ renderConfigs(data.configs || []);
}
function renderConfigs(configs) {
@@ -51,7 +82,7 @@ function renderConfigs(configs) {
container.innerHTML = "";
if (configs.length === 0) {
- container.innerHTML = `
+ container.innerHTML = `
У вас пока нет конфигураций.
Нажмите кнопку ниже, чтобы создать первую.
`;
return;
@@ -63,22 +94,20 @@ function renderConfigs(configs) {
const card = document.createElement("div");
card.className = "url-card";
card.innerHTML = `
-
+
${cfg.name}
-
- Использовано: ${trafficGB} ГБ
-
+
Использовано: ${trafficGB} ГБ
-
-
+
+
`;
card.querySelector(".btn-copy").addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(cfg.config);
- showNotification(`Конфиг "${cfg.name}" скопирован в буфер`);
+ showNotification(`Конфиг "${cfg.name}" скопирован`);
} catch (e) {
showNotification("Не удалось скопировать", "error");
}
@@ -87,25 +116,16 @@ function renderConfigs(configs) {
card.querySelector(".btn-delete").addEventListener("click", async () => {
if (!confirm(`Удалить конфигурацию "${cfg.name}"?`)) return;
- const token = getToken();
- try {
- const res = await fetch(`${API_BASE}/config/delete`, {
- method: "DELETE",
- headers: {
- "X-API-KEY": token,
- "Content-Type": "application/json"
- },
- body: JSON.stringify({ name: cfg.name })
- });
+ const res = await apiRequest("/config/delete", {
+ method: "DELETE",
+ body: JSON.stringify({ name: cfg.name })
+ });
- if (res.ok) {
- card.remove();
- showNotification(`Конфиг "${cfg.name}" удалён`, "error");
- } else {
- showNotification("Не удалось удалить конфиг", "error");
- }
- } catch (e) {
- showNotification("Ошибка сети", "error");
+ if (res && res.ok) {
+ showNotification(`Конфиг "${cfg.name}" удалён`);
+ loadPanel();
+ } else {
+ showNotification("Не удалось удалить конфиг", "error");
}
});
@@ -113,61 +133,126 @@ function renderConfigs(configs) {
});
}
-document.getElementById("addConfigBtn").addEventListener("click", async () => {
- const name = prompt("Введите название конфигурации (например: Телефон, Ноутбук, Рабочий):");
- if (!name || !name.trim()) return;
+// ==================== ЛОГИН ====================
+document.getElementById("loginSubmit").addEventListener("click", async () => {
+ const email = document.getElementById("loginEmail").value.trim();
+ const password = document.getElementById("loginPassword").value;
+ const errorEl = document.getElementById("loginError");
- const token = getToken();
+ errorEl.textContent = "";
+
+ if (!email || !password) {
+ errorEl.textContent = "Введите email и пароль";
+ return;
+ }
try {
- const res = await fetch(`${API_BASE}/config/add`, {
+ const res = await fetch(`${API_BASE}/user/login`, {
method: "POST",
- headers: {
- "X-API-KEY": token,
- "Content-Type": "application/json"
- },
- body: JSON.stringify({ name: name.trim() })
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email, password })
});
const data = await res.json();
- if (res.ok) {
- showNotification(`Конфиг "${name}" успешно создан!`);
- loadPanel();
+ if (res.ok && data.access_token) {
+ localStorage.setItem("access_token", data.access_token);
+ showNotification("Вход выполнен успешно!");
+ document.getElementById("loginEmail").value = "";
+ document.getElementById("loginPassword").value = "";
+ loadPanel(); // ← Главное исправление
} else {
- showNotification(data.detail || "Ошибка при создании конфига", "error");
+ errorEl.textContent = data.detail || "Неверный email или пароль";
}
- } catch (e) {
- showNotification("Нет связи с сервером", "error");
+ } catch (err) {
+ console.error(err);
+ errorEl.textContent = "Нет связи с сервером. Попробуйте позже.";
}
});
-document.getElementById("deleteAccountBtn").addEventListener("click", async () => {
- if (!confirm("Вы уверены, что хотите удалить аккаунт? Это действие необратимо!")) return;
+// ==================== Создание конфига ====================
+document.getElementById("addConfigBtn").addEventListener("click", () => {
+ document.getElementById("addModal").classList.add("active");
+ document.getElementById("configName").focus();
+});
+
+document.getElementById("addCancel").addEventListener("click", () => {
+ document.getElementById("addModal").classList.remove("active");
+ document.getElementById("addError").textContent = "";
+});
+
+document.getElementById("addSubmit").addEventListener("click", async () => {
+ const name = document.getElementById("configName").value.trim();
+ const errorEl = document.getElementById("addError");
+
+ if (!name) {
+ errorEl.textContent = "Введите название конфигурации";
+ return;
+ }
+
+ const res = await apiRequest("/config/add", {
+ method: "POST",
+ body: JSON.stringify({ name })
+ });
+
+ if (res && res.ok) {
+ showNotification(`Конфигурация "${name}" создана!`);
+ document.getElementById("addModal").classList.remove("active");
+ document.getElementById("configName").value = "";
+ loadPanel();
+ } else {
+ const errData = await res.json().catch(() => ({}));
+ errorEl.textContent = errData.detail || "Ошибка при создании";
+ }
+});
+
+// ==================== Выход ====================
+document.getElementById("logoutBtn").addEventListener("click", async () => {
+ if (!confirm("Выйти из аккаунта?")) return;
const token = getToken();
- try {
- const res = await fetch(`${API_BASE}/user/delete`, {
- method: "DELETE",
- headers: { "X-API-KEY": token }
- });
+ if (token) {
+ await fetch(`${API_BASE}/user/logout`, {
+ method: "POST",
+ headers: { "X-API-KEY": token, "Content-Type": "application/json" },
+ body: JSON.stringify({ token_to_revoke: token })
+ }).catch(() => {});
+ }
- if (res.ok) {
- localStorage.removeItem("access_token");
- alert("Аккаунт успешно удалён.");
- window.location.href = "index.html";
- } else {
- showNotification("Не удалось удалить аккаунт", "error");
- }
- } catch (e) {
- showNotification("Ошибка сети", "error");
+ localStorage.removeItem("access_token");
+ showLoginModal();
+});
+
+// ==================== Удаление аккаунта ====================
+document.getElementById("deleteAccountBtn").addEventListener("click", async () => {
+ if (!confirm("Вы уверены? Это действие необратимо!")) return;
+
+ const res = await apiRequest("/user/delete", { method: "DELETE" });
+
+ if (res && res.ok) {
+ localStorage.removeItem("access_token");
+ alert("Аккаунт успешно удалён.");
+ showLoginModal();
+ } else {
+ showNotification("Не удалось удалить аккаунт", "error");
}
});
-document.getElementById("logoutBtn").addEventListener("click", () => {
- if (confirm("Выйти из аккаунта?")) {
- localStorage.removeItem("access_token");
- window.location.href = "index.html";
+// Табы "Как подключиться"
+document.querySelectorAll(".tab-btn").forEach(btn => {
+ btn.addEventListener("click", () => {
+ document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
+ btn.classList.add("active");
+
+ document.querySelectorAll(".instructions").forEach(instr => instr.classList.add("hidden"));
+ document.getElementById(btn.dataset.platform + "-instructions").classList.remove("hidden");
+ });
+});
+
+// Закрытие модалки создания конфига кликом вне
+document.getElementById("addModal").addEventListener("click", (e) => {
+ if (e.target === document.getElementById("addModal")) {
+ document.getElementById("addModal").classList.remove("active");
}
});
diff --git a/Frontend/Web/Styles/control-panel.css b/Frontend/Web/Styles/control-panel.css
index 70d2f66..829b5a7 100644
--- a/Frontend/Web/Styles/control-panel.css
+++ b/Frontend/Web/Styles/control-panel.css
@@ -1,261 +1,294 @@
-:root{
+:root {
--bg: #000;
--card: #111;
--text: #e0e0e0;
- --accent: cyan;
+ --accent: #00ffff;
--border: #333;
+ --danger: #ff4444;
}
-*{margin: 0; padding: 0; box-sizing: border-box;}
-body{background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; min-height: 100vh;}
+* { margin: 0; padding: 0; box-sizing: border-box; }
+body {
+ background: var(--bg);
+ color: var(--text);
+ font-family: 'Inter', sans-serif;
+ min-height: 100vh;
+ line-height: 1.5;
+}
-.container{max-width: 800px; margin: 0 auto; padding: 20px;}
+.container { max-width: 820px; margin: 0 auto; padding: 20px; }
-header{
+header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
- border-bottom: 1px solid #333;
+ border-bottom: 1px solid var(--border);
margin-bottom: 40px;
flex-wrap: wrap;
+ gap: 15px;
}
-header h2 {font-size: 24pt;}
-header h2 b {color: var(--accent);}
+header h2 { font-size: 28px; }
+header h2 b { color: var(--accent); }
-.user-info{
+.user-info {
display: flex;
align-items: center;
gap: 15px;
- font-size: 14pt;
+ font-size: 15px;
}
-#logoutBtn{
+#logoutBtn {
background: transparent;
border: 1px solid var(--accent);
color: var(--accent);
- padding: 8px 16px;
+ padding: 8px 18px;
border-radius: 8px;
cursor: pointer;
transition: 0.3s;
}
-#logoutBtn:hover{
+#logoutBtn:hover {
background: var(--accent);
color: #000;
}
-h1{
- font-size: 28pt;
- margin-bottom: 30px;
+h1 {
+ font-size: 26px;
+ margin-bottom: 25px;
text-align: center;
}
-.urls-list{
+.urls-list {
display: grid;
- gap: 15px;
+ gap: 16px;
margin-bottom: 30px;
}
-.url-card{
+.url-card {
background: var(--card);
- border: 1px solid #333;
- border-radius: 12px;
+ border: 1px solid var(--border);
+ border-radius: 14px;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
- transition: 0.3s;
+ transition: all 0.3s ease;
}
-.url-card:hover{
+.url-card:hover {
border-color: var(--accent);
- box-shadow: 0 0 15px rgba(0, 255, 255, 0.2);
+ box-shadow: 0 0 20px rgba(0, 255, 255, 0.15);
}
-.url-name{
+.url-info {
+ flex: 1;
+}
+
+.url-name {
font-weight: 600;
color: var(--accent);
+ font-size: 17px;
}
-.url-actions{
+.traffic {
+ font-size: 13.5px;
+ color: #888;
+ margin-top: 4px;
+}
+
+.url-actions {
display: flex;
gap: 10px;
}
-.btn-copy, .btn-delete{
- padding: 8px 16px;
+.btn {
+ padding: 9px 18px;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: 0.3s;
+ border: none;
}
-.btn-copy{
+.btn-copy {
background: transparent;
border: 1px solid var(--accent);
color: var(--accent);
}
-.btn-copy:hover{
+.btn-copy:hover {
background: var(--accent);
color: #000;
}
-.btn-delete{
- background: #330000;
+.btn-delete {
+ background: #2a0a0a;
border: 1px solid #800;
- color: #f88;
+ color: #ff8888;
}
-.btn-delete:hover{
+.btn-delete:hover {
background: #800;
color: white;
}
-.add-btn{
+.add-btn {
width: 100%;
- padding: 16px;
+ padding: 18px;
background: transparent;
border: 2px dashed var(--accent);
color: var(--accent);
- font-size: 16pt;
+ font-size: 17px;
+ font-weight: 600;
border-radius: 12px;
cursor: pointer;
- transition:hover{
- background: rgba(0, 255, 255, 0.1);
- }
+ transition: 0.3s;
}
-.modal{
- display: none;
- position: fixed;
- top: 0; left: 0; width: 100%; height: 100%;
- background: rgba(0,0,0,0.8);
- justify-content: center;
- align-items: center;
- z-index: 1000;
+.add-btn:hover {
+ background: rgba(0, 255, 255, 0.08);
}
-.modal.active{display: flex;}
-
-.modal-content{
- background: #111;
- padding: 30px;
- border-radius: 16px;
- width: 90%;
- max-width: 400px;
- border: 1px solid var(--accent);
+.danger-zone {
+ margin-top: 60px;
text-align: center;
}
-.modal-content input{
- width: 100%;
- padding: 14px;
- margin: 10px 0;
- background: #222;
- border: 1px solid #444;
+.delete-account-btn {
+ background: #3a0a0a;
+ color: var(--danger);
+ border: 1px solid #a00;
+ padding: 12px 28px;
border-radius: 10px;
+ cursor: pointer;
+ font-size: 15px;
+}
+
+.delete-account-btn:hover {
+ background: #600;
color: white;
}
-.modal-content .error{
- color: #ff6b6b;
- margin: 10px 0;
- min-height: 20px;
+/* Модальное окно */
+.modal {
+ display: none;
+ position: fixed;
+ inset: 0;
+ background: rgba(0,0,0,0.85);
+ justify-content: center;
+ align-items: center;
+ z-index: 2000;
}
-.buttons{
+.modal.active { display: flex; }
+
+.modal-content {
+ background: #111;
+ padding: 32px;
+ border-radius: 16px;
+ width: 90%;
+ max-width: 420px;
+ border: 1px solid var(--accent);
+}
+
+.modal-content h2 { margin-bottom: 20px; text-align: center; }
+
+.modal-content input {
+ width: 100%;
+ padding: 14px;
+ margin-bottom: 15px;
+ background: #222;
+ border: 1px solid #555;
+ border-radius: 10px;
+ color: white;
+ font-size: 16px;
+}
+
+.error {
+ color: #ff6b6b;
+ min-height: 22px;
+ font-size: 14px;
+ text-align: center;
+}
+
+.buttons {
display: flex;
- gap: 10px;
+ gap: 12px;
margin-top: 20px;
}
-.buttons button{
+.buttons button {
flex: 1;
- padding: 12px;
+ padding: 14px;
border-radius: 10px;
cursor: pointer;
-}
-
-#loginSubmit{
- background: var(--accent);
- color: black;
- border: none;
-}
-
-#closeModal{
- background: transparent;
- border: 1px solid #666;
- color: #aaa;
-}
-
-.how-to-connect{
- margin-bottom: 60px;
-}
-
-.platform-tabs{
- display: flex;
- justify-content: center;
- gap: 12px;
- margin-bottom: 32px;
- flex-wrap: wrap;
-}
-
-.tab-btn{
- background: #1a1a1a;
- border: 1px solid #444;
- color: #aaa;
- padding: 12px 24px;
- border-radius: 50px;
- font-size: 15px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.22s ease;
-}
-
-.tab-btn:hover{
- border-color: #666;
- color: #ddd;
- background: #222;
-}
-
-.tab-btn.active{
- background: var(--accent);
- color: #000;
- border-color: var(--accent);
- box-shadow: 0 0 20px rgba(0, 255, 255, 0.25);
font-weight: 600;
}
-.instructions{
+#addSubmit {
+ background: var(--accent);
+ color: #000;
+ border: none;
+}
+
+#addCancel {
+ background: transparent;
+ border: 1px solid #666;
+ color: #ccc;
+}
+
+/* Табы и инструкции (оставил твои стили, только немного подчистил) */
+.platform-tabs {
+ display: flex;
+ justify-content: center;
+ gap: 12px;
+ margin-bottom: 30px;
+ flex-wrap: wrap;
+}
+
+.tab-btn {
+ background: #1a1a1a;
+ border: 1px solid #444;
+ color: #aaa;
+ padding: 11px 22px;
+ border-radius: 50px;
+ cursor: pointer;
+ transition: all 0.25s;
+}
+
+.tab-btn.active,
+.tab-btn:hover {
+ background: var(--accent);
+ color: #000;
+ border-color: var(--accent);
+}
+
+.instructions {
background: var(--card);
border: 1px solid #333;
border-radius: 16px;
- padding: 28px 32px;
- transition: all 0.3s ease;
+ padding: 26px 30px;
}
-.instructions ol{
+.instructions ol {
counter-reset: step;
list-style: none;
- font-size: 15.5px;
- line-height: 1.65;
}
-.instructions li{
+.instructions li {
position: relative;
- padding-left: 38px;
- margin-bottom: 18px;
+ padding-left: 42px;
+ margin-bottom: 16px;
}
-.instructions li::before{
+.instructions li::before {
content: counter(step);
counter-increment: step;
position: absolute;
left: 0;
- top: 2px;
+ top: 0;
width: 28px;
height: 28px;
background: #222;
@@ -265,37 +298,7 @@ h1{
display: flex;
align-items: center;
justify-content: center;
- font-size: 14px;
- font-weight: 600;
+ font-weight: 700;
}
-.instructions a{
- color: var(--accent);
- text-decoration: none;
- border-bottom: 1px solid rgba(0,255,255,0.3);
- transition: all 0.2s;
-}
-
-.instructions a:hover{
- border-bottom-color: var(--accent);
- color: #00ffff;
-}
-
-.instructions kbd{
- background: #1e1e1e;
- border: 1px solid #444;
- border-radius: 5px;
- padding: 3px 7px;
- font-family: monospace;
- color: #aaa;
-}
-
-.instructions ul{
- margin: 12px 0 12px 24px;
- list-style: disc;
- color: #bbb;
-}
-
-.hidden{
- display: none !important;
-}
\ No newline at end of file
+.hidden { display: none !important; }
\ No newline at end of file
diff --git a/Frontend/Web/control-panel.html b/Frontend/Web/control-panel.html
index 96f23f4..7a97c19 100644
--- a/Frontend/Web/control-panel.html
+++ b/Frontend/Web/control-panel.html
@@ -11,73 +11,87 @@
SpectralVPN
-
+
+
+
Как подключиться
-
-
+
- - Создайте и скопируйте свою конфигурацию в личном кабинете.
- - Скачайте и установите приложение v2rayNG.
- - Откройте приложение.
- - Нажмите + в правом верхнем углу → Импорт из буфера обмена.
- - Готово! Включайте/выключайте соединение большой кнопкой внизу справа.
+ - Создайте конфигурацию ниже и скопируйте ссылку.
+ - Установите v2rayNG.
+ - Нажмите + → Импорт из буфера обмена.
+ - Включайте соединение большой кнопкой.
- - Создайте и скопируйте свою конфигурацию в личном кабинете.
- - Скачайте и установите NekoBox for Windows.
- - Откройте программу.
- - Нажмите Ctrl + V — конфигурация добавится автоматически.
- - Выделите добавленную конфигурацию и нажмите Enter.
- - Вверху включите:
-
- - Системный прокси — для работы в браузерах
- - Режим TUN — для всей системы
-
-
- - Готово! Вы подключены.
+ - Создайте конфигурацию и скопируйте ссылку.
+ - Установите NekoBox.
+ - Ctrl + V — конфиг добавится автоматически.
+ - Включите Системный прокси и/или TUN.
- - Создайте и скопируйте свою конфигурацию в личном кабинете.
- - Скачайте приложение V2RayTun из App Store.
- - Откройте приложение.
- - Нажмите + в правом верхнем углу → Импорт из буфера обмена.
- - Готово! Включайте/выключайте соединение центральной кнопкой.
+ - Создайте конфигурацию.
+ - Установите V2RayTun.
+ - + → Импорт из буфера обмена.
+
Ваши конфигурации
-
+
+
+
+
+
-
+
+
+
+
+
+
+
Новая конфигурация
+
+
+
+
+