From 91443a777f2853c4749f372d61d687698354aad6 Mon Sep 17 00:00:00 2001 From: Lev Date: Tue, 9 Dec 2025 22:17:47 +0300 Subject: [PATCH] control-panel --- Frontend/JS/control-panel.js | 208 ++++++++++++++++++++++++++++ Frontend/Styles/control-panel.css | 197 +++++++++++++++++++++++++- Frontend/control-panel.html | 36 ++++- configs/nginx/spectralvpn_api.nginx | 2 +- 4 files changed, 435 insertions(+), 8 deletions(-) create mode 100644 Frontend/JS/control-panel.js diff --git a/Frontend/JS/control-panel.js b/Frontend/JS/control-panel.js new file mode 100644 index 0000000..41d4344 --- /dev/null +++ b/Frontend/JS/control-panel.js @@ -0,0 +1,208 @@ +const API_BASE = "https://spectralvpn.ru:8500"; + +const getCookie = (name) => { + const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); + return match ? decodeURIComponent(match[2]) : null; +}; + +const deleteCookie = (name) => { + document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; SameSite=Strict`; +}; + +const sha256 = async (str) => { + const buf = new TextEncoder().encode(str); + const hash = await crypto.subtle.digest("SHA-256", buf); + return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, "0")).join(""); +}; + +const showNotification = (text, color = "cyan") => { + const notification = document.createElement("div"); + notification.textContent = text; + notification.style.cssText = ` + position: fixed; top: 20px; right: 20px; padding: 15px 25px; border-radius: 12px; + z-index: 9999; font-weight: 600; background: ${color === "cyan" ? "#00ffff22" : "#ff444422"}; + color: ${color}; border: 1px solid ${color === "cyan" ? "cyan" : "#f66"}; + `; + document.body.appendChild(notification); + setTimeout(() => notification.remove(), 3000); +}; + +let currentEmail = null; +let currentHash = null; + +const modal = document.getElementById("loginModal"); +const showModal = () => modal.classList.add("active"); +const hideModal = () => modal.classList.remove("active"); + +const showLoginError = (msg) => { + document.getElementById("loginError").textContent = msg; +}; + +const renderUrls = (urls) => { + const container = document.getElementById("urlsList"); + container.innerHTML = ""; + + if (urls.length === 0) { + container.innerHTML = "

У вас пока нет конфигураций

"; + return; + } + + urls.forEach(name => { + const card = document.createElement("div"); + card.className = "url-card"; + + card.innerHTML = ` +
+
${name}
+
+
+ + +
+ `; + + card.querySelector(".btn-copy").onclick = async () => { + try { + const url = `${API_BASE}/get_url?email=${encodeURIComponent(currentEmail)}&password=${encodeURIComponent(currentHash)}&urls_name=${encodeURIComponent(name)}`; + const res = await fetch(url); + const data = await res.json(); + + if (res.ok && data.url) { + await navigator.clipboard.writeText(data.url); + showNotification(`Конфиг "${name}" скопирован!`); + } else { + showNotification("Ошибка получения конфига", "red"); + } + } catch (e) { + showNotification("Нет связи с сервером", "red"); + } + }; + + card.querySelector(".btn-delete").onclick = async () => { + if (!confirm(`Удалить конфиг "${name}"?`)) return; + + try { + const url = `${API_BASE}/del_url?email=${encodeURIComponent(currentEmail)}&password=${encodeURIComponent(currentHash)}&urls_name=${encodeURIComponent(name)}`; + const res = await fetch(url, { method: "DELETE" }); + + if (res.ok) { + card.remove(); + showNotification(`Конфиг "${name}" удалён`, "red"); + } else { + showNotification("Не удалось удалить", "red"); + } + } catch (e) { + showNotification("Ошибка сети", "red"); + } + }; + + container.appendChild(card); + }); +}; + +const tryAutoLogin = async () => { + currentEmail = getCookie("user_email"); + currentHash = getCookie("user_hash"); + + if (!currentEmail || !currentHash) { + showModal(); + return; + } + + try { + const res = await fetch(`${API_BASE}/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email: currentEmail, password: currentHash }) + }); + + if (res.ok) { + const data = await res.json(); + document.getElementById("userEmail").textContent = currentEmail; + document.getElementById("mainContent").classList.remove("hidden"); + renderUrls(data.urls || []); + } else { + deleteCookie("user_email"); + deleteCookie("user_hash"); + showModal(); + } + } catch (e) { + showModal(); + } +}; + +document.getElementById("loginSubmit").onclick = async () => { + const email = document.getElementById("loginEmail").value.trim(); + const pass = document.getElementById("loginPassword").value; + + if (!email || !pass) { + showLoginError("Заполните все поля"); + return; + } + + const hash = await sha256(pass); + + try { + const res = await fetch(`${API_BASE}/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, password: hash }) + }); + + const data = await res.json(); + + if (res.ok) { + currentEmail = email; + currentHash = hash; + document.cookie = `user_email=${encodeURIComponent(email)}; path=/; max-age=2592000; Secure; SameSite=Strict`; + document.cookie = `user_hash=${hash}; path=/; max-age=2592000; Secure; SameSite=Strict`; + + document.getElementById("userEmail").textContent = email; + hideModal(); + document.getElementById("mainContent").classList.remove("hidden"); + renderUrls(data.urls || []); + } else { + showLoginError("Неверный email или пароль"); + } + } catch (e) { + showLoginError("Ошибка подключения"); + } +}; + +document.getElementById("addUrlBtn").onclick = async () => { + const name = prompt("Введите название конфигурации:"); + if (!name?.trim()) return; + + try { + const res = await fetch(`${API_BASE}/add_url`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: currentEmail, + password: currentHash, + urls_name: name.trim() + }) + }); + + const data = await res.json(); + + if (res.ok) { + renderUrls(data.urls); + showNotification(`Конфиг "${name}" создан! Скопируйте через кнопку.`); + } else { + showNotification("Ошибка: " + (data.detail || "неизвестно"), "red"); + } + } catch (e) { + showNotification("Нет связи с сервером", "red"); + } +}; + +document.getElementById("logoutBtn").onclick = () => { + deleteCookie("user_email"); + deleteCookie("user_hash"); + location.reload(); +}; + +document.getElementById("closeModal").onclick = hideModal; + +tryAutoLogin(); \ No newline at end of file diff --git a/Frontend/Styles/control-panel.css b/Frontend/Styles/control-panel.css index afafd28..3fa3e3c 100644 --- a/Frontend/Styles/control-panel.css +++ b/Frontend/Styles/control-panel.css @@ -1,4 +1,193 @@ -*{ - margin: 0; - padding: 0; -} \ No newline at end of file +:root{ + --bg: #000; + --card: #111; + --text: #e0e0e0; + --accent: cyan; + --border: #333; +} + +*{margin: 0; padding: 0; box-sizing: border-box;} +body{background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; min-height: 100vh;} + +.container{max-width: 800px; margin: 0 auto; padding: 20px;} + +header{ + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 0; + border-bottom: 1px solid #333; + margin-bottom: 40px; +} + +header h2 {font-size: 24pt;} +header h2 b {color: var(--accent);} + +.user-info{ + display: flex; + align-items: center; + gap: 15px; + font-size: 14pt; +} + +#logoutBtn{ + background: transparent; + border: 1px solid var(--accent); + color: var(--accent); + padding: 8px 16px; + border-radius: 8px; + cursor: pointer; + transition: 0.3s; +} + +#logoutBtn:hover{ + background: var(--accent); + color: #000; +} + +h1{ + font-size: 28pt; + margin-bottom: 30px; + text-align: center; +} + +.urls-list{ + display: grid; + gap: 15px; + margin-bottom: 30px; +} + +.url-card{ + background: var(--card); + border: 1px solid #333; + border-radius: 12px; + padding: 20px; + display: flex; + justify-content: space-between; + align-items: center; + transition: 0.3s; +} + +.url-card:hover{ + border-color: var(--accent); + box-shadow: 0 0 15px rgba(0, 255, 255, 0.2); +} + +.url-name{ + font-weight: 600; + color: var(--accent); +} + +.url-actions{ + display: flex; + gap: 10px; +} + +.btn-copy, .btn-delete{ + padding: 8px 16px; + border-radius: 8px; + cursor: pointer; + font-weight: 600; + transition: 0.3s; +} + +.btn-copy{ + background: transparent; + border: 1px solid var(--accent); + color: var(--accent); +} + +.btn-copy:hover{ + background: var(--accent); + color: #000; +} + +.btn-delete{ + background: #330000; + border: 1px solid #800; + color: #f88; +} + +.btn-delete:hover{ + background: #800; + color: white; +} + +.add-btn{ + width: 100%; + padding: 16px; + background: transparent; + border: 2px dashed var(--accent); + color: var(--accent); + font-size: 16pt; + border-radius: 12px; + cursor: pointer; + transition:hover{ + background: rgba(0, 255, 255, 0.1); + } +} + +.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; +} + +.modal.active{display: flex;} + +.modal-content{ + background: #111; + padding: 30px; + border-radius: 16px; + width: 90%; + max-width: 400px; + border: 1px solid var(--accent); + text-align: center; +} + +.modal-content input{ + width: 100%; + padding: 14px; + margin: 10px 0; + background: #222; + border: 1px solid #444; + border-radius: 10px; + color: white; +} + +.modal-content .error{ + color: #ff6b6b; + margin: 10px 0; + min-height: 20px; +} + +.buttons{ + display: flex; + gap: 10px; + margin-top: 20px; +} + +.buttons button{ + flex: 1; + padding: 12px; + border-radius: 10px; + cursor: pointer; +} + +#loginSubmit{ + background: var(--accent); + color: black; + border: none; +} + +#closeModal{ + background: transparent; + border: 1px solid #666; + color: #aaa; +} + +.hidden { display: none; } \ No newline at end of file diff --git a/Frontend/control-panel.html b/Frontend/control-panel.html index 1b40ba6..d05c238 100644 --- a/Frontend/control-panel.html +++ b/Frontend/control-panel.html @@ -1,12 +1,42 @@ - + - Document + Личный кабинет — SpectralVPN + - +
+
+

SpectralVPN

+ +
+ +
+

Ваши конфигурации

+
+ +
+ + +
+ + \ No newline at end of file diff --git a/configs/nginx/spectralvpn_api.nginx b/configs/nginx/spectralvpn_api.nginx index bd5ae31..e2126fd 100644 --- a/configs/nginx/spectralvpn_api.nginx +++ b/configs/nginx/spectralvpn_api.nginx @@ -9,7 +9,7 @@ server { location / { if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' 'https://spectralvpn.ru'; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + 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; return 204;