Инициализация проекта

Всем привет :)
This commit is contained in:
2025-10-16 16:27:36 +07:00
commit 0e93af1d8c
23 changed files with 4058 additions and 0 deletions

548
internal/server/handlers.go Normal file
View File

@@ -0,0 +1,548 @@
package server
import (
"encoding/json"
"fmt"
"html/template"
"net/http"
"strconv"
"strings"
"wg-panel/internal/database"
"wg-panel/internal/wireguard"
)
var (
DB *database.Database
Config *database.Config
)
// authMiddleware проверяет авторизацию
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Проверяем cookie
cookie, err := r.Cookie("auth")
if err != nil || cookie.Value != "authenticated" {
if r.URL.Path == "/" {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next(w, r)
}
}
// handleLogin обрабатывает страницу входа
func HandleLogin(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
tmplData, err := TemplatesFS.ReadFile("templates/login.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl, err := template.New("login").Parse(string(tmplData))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl.Execute(w, nil)
return
}
if r.Method == "POST" {
username := r.FormValue("username")
password := r.FormValue("password")
if username == Config.Username && password == Config.Password {
http.SetCookie(w, &http.Cookie{
Name: "auth",
Value: "authenticated",
Path: "/",
MaxAge: 86400, // 24 часа
HttpOnly: true,
})
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/login?error=1", http.StatusSeeOther)
}
}
// handleLogout обрабатывает выход
func HandleLogout(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: "auth",
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
})
http.Redirect(w, r, "/login", http.StatusSeeOther)
}
// handleIndex обрабатывает главную страницу
func HandleIndex(w http.ResponseWriter, r *http.Request) {
tmplData, err := TemplatesFS.ReadFile("templates/index.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl, err := template.New("index").Parse(string(tmplData))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl.Execute(w, nil)
}
// === ОБРАБОТЧИКИ СЕРВЕРОВ ===
// HandleServers возвращает список серверов
func HandleServers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if DB.Servers == nil {
json.NewEncoder(w).Encode([]database.Server{})
} else {
json.NewEncoder(w).Encode(DB.Servers)
}
}
// HandleCreateServer создает новый WireGuard сервер
func HandleCreateServer(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
name := r.FormValue("name")
address := r.FormValue("address")
portStr := r.FormValue("port")
dns := r.FormValue("dns")
if name == "" || address == "" || portStr == "" {
http.Error(w, "Missing required fields", http.StatusBadRequest)
return
}
// Добавляем /24 если не указано
if !strings.Contains(address, "/") {
address = address + "/24"
}
port, err := strconv.Atoi(portStr)
if err != nil {
http.Error(w, "Invalid port", http.StatusBadRequest)
return
}
// Валидируем конфигурацию
if err := database.ValidateServerConfig(DB, address, port); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
server, err := wireguard.CreateServer(DB, name, address, port, dns)
if err != nil {
http.Error(w, "Failed to create server: "+err.Error(), http.StatusInternalServerError)
return
}
DB.Servers = append(DB.Servers, *server)
database.SaveDatabase(DB)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(server)
}
// HandleUpdateServer обновляет настройки сервера
func HandleUpdateServer(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
id := r.FormValue("id")
name := r.FormValue("name")
portStr := r.FormValue("port")
dns := r.FormValue("dns")
for i, server := range DB.Servers {
if server.ID == id {
if name != "" {
DB.Servers[i].Name = name
}
if portStr != "" {
port, err := strconv.Atoi(portStr)
if err == nil {
DB.Servers[i].ListenPort = port
}
}
if dns != "" {
DB.Servers[i].DNS = dns
}
// Обновляем конфиг файл
wireguard.UpdateServerConfig(&DB.Servers[i], DB)
database.SaveDatabase(DB)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(DB.Servers[i])
return
}
}
http.Error(w, "Server not found", http.StatusNotFound)
}
// HandleDeleteServer удаляет сервер
func HandleDeleteServer(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
id := r.FormValue("id")
for i, server := range DB.Servers {
if server.ID == id {
// Удаляем сервер
if err := wireguard.DeleteServer(&server); err != nil {
http.Error(w, "Failed to delete server", http.StatusInternalServerError)
return
}
// Удаляем всех клиентов этого сервера
var newClients []database.Client
for _, client := range DB.Clients {
if client.ServerID != id {
newClients = append(newClients, client)
}
}
DB.Clients = newClients
// Удаляем сервер из базы
DB.Servers = append(DB.Servers[:i], DB.Servers[i+1:]...)
database.SaveDatabase(DB)
w.WriteHeader(http.StatusOK)
return
}
}
http.Error(w, "Server not found", http.StatusNotFound)
}
// HandleToggleServer включает/выключает сервер
func HandleToggleServer(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
id := r.FormValue("id")
for i, server := range DB.Servers {
if server.ID == id {
if err := wireguard.ToggleServer(&DB.Servers[i]); err != nil {
http.Error(w, "Failed to toggle server: "+err.Error(), http.StatusInternalServerError)
return
}
database.SaveDatabase(DB)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(DB.Servers[i])
return
}
}
http.Error(w, "Server not found", http.StatusNotFound)
}
// === ОБРАБОТЧИКИ КЛИЕНТОВ ===
// HandleClients возвращает список клиентов
func HandleClients(w http.ResponseWriter, r *http.Request) {
serverID := r.URL.Query().Get("server_id")
var clients []database.Client
for _, client := range DB.Clients {
if serverID == "" || client.ServerID == serverID {
clients = append(clients, client)
}
}
w.Header().Set("Content-Type", "application/json")
if clients == nil {
json.NewEncoder(w).Encode([]database.Client{})
} else {
json.NewEncoder(w).Encode(clients)
}
}
// HandleCreateClient создает новый конфиг клиента
func HandleCreateClient(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
serverID := r.FormValue("server_id")
name := r.FormValue("name")
comment := r.FormValue("comment")
if serverID == "" || name == "" {
http.Error(w, "Missing required fields", http.StatusBadRequest)
return
}
client, err := wireguard.CreateClient(DB, serverID, name, comment)
if err != nil {
http.Error(w, "Failed to create client: "+err.Error(), http.StatusInternalServerError)
return
}
DB.Clients = append(DB.Clients, *client)
database.SaveDatabase(DB)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(client)
}
// HandleDeleteClient удаляет клиента
func HandleDeleteClient(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
id := r.FormValue("id")
for i, client := range DB.Clients {
if client.ID == id {
// Удаляем клиента
if err := wireguard.DeleteClient(DB, &client); err != nil {
http.Error(w, "Failed to delete client", http.StatusInternalServerError)
return
}
// Удаляем из базы
DB.Clients = append(DB.Clients[:i], DB.Clients[i+1:]...)
database.SaveDatabase(DB)
w.WriteHeader(http.StatusOK)
return
}
}
http.Error(w, "Client not found", http.StatusNotFound)
}
// HandleToggleClient включает/выключает клиента
func HandleToggleClient(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
id := r.FormValue("id")
for i, client := range DB.Clients {
if client.ID == id {
if err := wireguard.ToggleClient(DB, &DB.Clients[i]); err != nil {
http.Error(w, "Failed to toggle client", http.StatusInternalServerError)
return
}
database.SaveDatabase(DB)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(DB.Clients[i])
return
}
}
http.Error(w, "Client not found", http.StatusNotFound)
}
// HandleUpdateClient обновляет имя и комментарий клиента
func HandleUpdateClient(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
id := r.FormValue("id")
name := r.FormValue("name")
comment := r.FormValue("comment")
for i, client := range DB.Clients {
if client.ID == id {
if name != "" {
DB.Clients[i].Name = name
}
DB.Clients[i].Comment = comment
database.SaveDatabase(DB)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(DB.Clients[i])
return
}
}
http.Error(w, "Client not found", http.StatusNotFound)
}
// HandleAddPortForward добавляет проброс порта
func HandleAddPortForward(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
clientID := r.FormValue("client_id")
portStr := r.FormValue("port")
protocol := r.FormValue("protocol")
description := r.FormValue("description")
port, err := strconv.Atoi(portStr)
if err != nil {
http.Error(w, "Invalid port", http.StatusBadRequest)
return
}
if protocol != "tcp" && protocol != "udp" && protocol != "both" {
http.Error(w, "Protocol must be tcp, udp or both", http.StatusBadRequest)
return
}
for i, client := range DB.Clients {
if client.ID == clientID {
if err := wireguard.AddPortForward(DB, &DB.Clients[i], port, protocol, description); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
database.SaveDatabase(DB)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(DB.Clients[i])
return
}
}
http.Error(w, "Client not found", http.StatusNotFound)
}
// HandleRemovePortForward удаляет проброс порта
func HandleRemovePortForward(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
clientID := r.FormValue("client_id")
portStr := r.FormValue("port")
protocol := r.FormValue("protocol")
port, err := strconv.Atoi(portStr)
if err != nil {
http.Error(w, "Invalid port", http.StatusBadRequest)
return
}
for i, client := range DB.Clients {
if client.ID == clientID {
if err := wireguard.RemovePortForward(&DB.Clients[i], port, protocol); err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
database.SaveDatabase(DB)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(DB.Clients[i])
return
}
}
http.Error(w, "Client not found", http.StatusNotFound)
}
// HandleDownloadConfig скачивает конфиг клиента
func HandleDownloadConfig(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
for _, client := range DB.Clients {
if client.ID == id {
// Находим сервер
var server *database.Server
for i := range DB.Servers {
if DB.Servers[i].ID == client.ServerID {
server = &DB.Servers[i]
break
}
}
if server == nil {
http.Error(w, "Server not found", http.StatusNotFound)
return
}
config := wireguard.GenerateClientConfig(client, server)
// Создаем безопасное имя файла (без пробелов и спецсимволов)
safeName := database.SanitizeFilename(client.Name)
w.Header().Set("Content-Type", "application/x-wireguard-profile")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.conf", safeName))
w.Write([]byte(config))
return
}
}
http.Error(w, "Client not found", http.StatusNotFound)
}
// HandleQRCode генерирует QR код для конфига
func HandleQRCode(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
for _, client := range DB.Clients {
if client.ID == id {
// Находим сервер
var server *database.Server
for i := range DB.Servers {
if DB.Servers[i].ID == client.ServerID {
server = &DB.Servers[i]
break
}
}
if server == nil {
http.Error(w, "Server not found", http.StatusNotFound)
return
}
config := wireguard.GenerateClientConfig(client, server)
// Генерируем QR код
png, err := wireguard.GenerateQRCode(config)
if err != nil {
http.Error(w, "Failed to generate QR code", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "image/png")
w.Write(png)
return
}
}
http.Error(w, "Client not found", http.StatusNotFound)
}
// HandleStats возвращает статистику
func HandleStats(w http.ResponseWriter, r *http.Request) {
wireguard.UpdateStats(DB)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(DB.Clients)
}

34
internal/server/routes.go Normal file
View File

@@ -0,0 +1,34 @@
package server
import (
"net/http"
)
// SetupRoutes настраивает маршруты HTTP сервера
func SetupRoutes() {
// Главная страница
http.HandleFunc("/", authMiddleware(HandleIndex))
// API для серверов
http.HandleFunc("/api/servers", authMiddleware(HandleServers))
http.HandleFunc("/api/server/create", authMiddleware(HandleCreateServer))
http.HandleFunc("/api/server/update", authMiddleware(HandleUpdateServer))
http.HandleFunc("/api/server/delete", authMiddleware(HandleDeleteServer))
http.HandleFunc("/api/server/toggle", authMiddleware(HandleToggleServer))
// API для клиентов
http.HandleFunc("/api/clients", authMiddleware(HandleClients))
http.HandleFunc("/api/client/create", authMiddleware(HandleCreateClient))
http.HandleFunc("/api/client/delete", authMiddleware(HandleDeleteClient))
http.HandleFunc("/api/client/toggle", authMiddleware(HandleToggleClient))
http.HandleFunc("/api/client/update", authMiddleware(HandleUpdateClient))
http.HandleFunc("/api/client/download", authMiddleware(HandleDownloadConfig))
http.HandleFunc("/api/client/qr", authMiddleware(HandleQRCode))
http.HandleFunc("/api/client/portforward/add", authMiddleware(HandleAddPortForward))
http.HandleFunc("/api/client/portforward/remove", authMiddleware(HandleRemovePortForward))
http.HandleFunc("/api/stats", authMiddleware(HandleStats))
// Авторизация
http.HandleFunc("/login", HandleLogin)
http.HandleFunc("/logout", HandleLogout)
}

View File

@@ -0,0 +1,6 @@
package server
import "embed"
//go:embed templates/*
var TemplatesFS embed.FS

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,130 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WireGuard Panel - Вход</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-container {
background: white;
padding: 40px;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
width: 100%;
max-width: 400px;
}
.logo {
text-align: center;
margin-bottom: 30px;
}
.logo h1 {
font-size: 28px;
color: #333;
margin-bottom: 5px;
}
.logo p {
color: #666;
font-size: 14px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
font-size: 14px;
}
.form-group input {
width: 100%;
padding: 12px 15px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 14px;
transition: all 0.3s;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
}
.btn {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s;
}
.btn:hover {
transform: translateY(-2px);
}
.error {
background: #fee;
border: 1px solid #fcc;
color: #c33;
padding: 12px;
border-radius: 10px;
margin-bottom: 20px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="login-container">
<div class="logo">
<h1>WireGuard Panel</h1>
<p>Панель управления VPN</p>
</div>
<script>
const params = new URLSearchParams(window.location.search);
if (params.get('error')) {
document.write('<div class="error">Неверный логин или пароль</div>');
}
</script>
<form method="POST" action="/login">
<div class="form-group">
<label for="username">Логин</label>
<input type="text" id="username" name="username" required autofocus>
</div>
<div class="form-group">
<label for="password">Пароль</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="btn">Войти</button>
</form>
</div>
</body>
</html>