196 lines
5.6 KiB
Go
196 lines
5.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"database/sql"
|
|
"html/template"
|
|
"log"
|
|
"net/http"
|
|
"path/filepath"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/orohi/vpn-panel/internal/auth"
|
|
"github.com/orohi/vpn-panel/internal/db"
|
|
"github.com/orohi/vpn-panel/internal/secretbox"
|
|
)
|
|
|
|
type Server struct {
|
|
DB *sql.DB
|
|
Auth *auth.Manager
|
|
Templates *template.Template
|
|
SecretKey []byte
|
|
}
|
|
|
|
type pageData map[string]any
|
|
|
|
func New(database *sql.DB, authMgr *auth.Manager, appSecret, templatesDir string) (*Server, error) {
|
|
pattern := filepath.Join(templatesDir, "*.html")
|
|
tmpl, err := template.New("").Funcs(template.FuncMap{
|
|
"statusClass": func(s string) string {
|
|
switch s {
|
|
case "online":
|
|
return "on"
|
|
case "installing":
|
|
return "warn"
|
|
case "error":
|
|
return "err"
|
|
case "offline":
|
|
return "off"
|
|
default:
|
|
return "off"
|
|
}
|
|
},
|
|
}).ParseGlob(pattern)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Server{
|
|
DB: database,
|
|
Auth: authMgr,
|
|
Templates: tmpl,
|
|
SecretKey: secretbox.DeriveKey(appSecret),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) Routes() http.Handler {
|
|
r := mux.NewRouter()
|
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))
|
|
|
|
r.HandleFunc("/", s.home).Methods(http.MethodGet)
|
|
r.HandleFunc("/login", s.loginGet).Methods(http.MethodGet)
|
|
r.HandleFunc("/login", s.loginPost).Methods(http.MethodPost)
|
|
r.HandleFunc("/logout", s.logout).Methods(http.MethodPost, http.MethodGet)
|
|
|
|
r.HandleFunc("/admin", s.Auth.RequireAuth(s.dashboard)).Methods(http.MethodGet)
|
|
r.HandleFunc("/admin/protocols", s.Auth.RequireAuth(s.protocols)).Methods(http.MethodGet)
|
|
r.HandleFunc("/admin/protocols/{id}/toggle", s.Auth.RequireAuth(s.toggleProtocol)).Methods(http.MethodPost)
|
|
|
|
r.HandleFunc("/admin/nodes", s.Auth.RequireAuth(s.nodesList)).Methods(http.MethodGet)
|
|
r.HandleFunc("/admin/nodes/new", s.Auth.RequireAuth(s.nodesNewGet)).Methods(http.MethodGet)
|
|
r.HandleFunc("/admin/nodes/new", s.Auth.RequireAuth(s.nodesCreate)).Methods(http.MethodPost)
|
|
r.HandleFunc("/admin/nodes/{id}", s.Auth.RequireAuth(s.nodeView)).Methods(http.MethodGet)
|
|
r.HandleFunc("/admin/nodes/{id}/install", s.Auth.RequireAuth(s.nodeInstall)).Methods(http.MethodPost)
|
|
r.HandleFunc("/admin/nodes/{id}/check", s.Auth.RequireAuth(s.nodeCheck)).Methods(http.MethodPost)
|
|
r.HandleFunc("/admin/nodes/{id}/delete", s.Auth.RequireAuth(s.nodeDelete)).Methods(http.MethodPost)
|
|
|
|
r.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte("ok"))
|
|
}).Methods(http.MethodGet)
|
|
|
|
return r
|
|
}
|
|
|
|
func (s *Server) render(w http.ResponseWriter, name string, data pageData) {
|
|
if data == nil {
|
|
data = pageData{}
|
|
}
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
if err := s.Templates.ExecuteTemplate(w, name, data); err != nil {
|
|
log.Printf("template %s: %v", name, err)
|
|
http.Error(w, "template error", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func (s *Server) home(w http.ResponseWriter, r *http.Request) {
|
|
if s.Auth.IsAuthenticated(r) {
|
|
http.Redirect(w, r, "/admin", http.StatusSeeOther)
|
|
return
|
|
}
|
|
s.render(w, "home.html", pageData{
|
|
"Title": "VPN Panel",
|
|
})
|
|
}
|
|
|
|
func (s *Server) loginGet(w http.ResponseWriter, r *http.Request) {
|
|
if s.Auth.IsAuthenticated(r) {
|
|
http.Redirect(w, r, "/admin", http.StatusSeeOther)
|
|
return
|
|
}
|
|
s.render(w, "login.html", pageData{
|
|
"Title": "Вход",
|
|
"Error": "",
|
|
})
|
|
}
|
|
|
|
func (s *Server) loginPost(w http.ResponseWriter, r *http.Request) {
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Error(w, "bad request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
email := r.FormValue("email")
|
|
password := r.FormValue("password")
|
|
|
|
user, err := db.GetUserByEmail(s.DB, email)
|
|
if err != nil || user == nil || !auth.CheckPassword(user.PasswordHash, password) {
|
|
s.render(w, "login.html", pageData{
|
|
"Title": "Вход",
|
|
"Error": "Неверный email или пароль",
|
|
"Email": email,
|
|
})
|
|
return
|
|
}
|
|
|
|
if err := s.Auth.Login(w, r, user.ID.String(), user.Email, user.Name); err != nil {
|
|
http.Error(w, "session error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/admin", http.StatusSeeOther)
|
|
}
|
|
|
|
func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
|
|
_ = s.Auth.Logout(w, r)
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
}
|
|
|
|
func (s *Server) dashboard(w http.ResponseWriter, r *http.Request) {
|
|
stats, err := db.GetStats(s.DB)
|
|
if err != nil {
|
|
http.Error(w, "db error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
protocols, err := db.ListProtocols(s.DB)
|
|
if err != nil {
|
|
http.Error(w, "db error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
nodes, _ := db.ListNodes(s.DB)
|
|
s.render(w, "dashboard.html", pageData{
|
|
"Title": "Админка",
|
|
"UserName": s.Auth.CurrentName(r),
|
|
"Stats": stats,
|
|
"Protocols": protocols,
|
|
"Nodes": nodes,
|
|
"Active": "dashboard",
|
|
})
|
|
}
|
|
|
|
func (s *Server) protocols(w http.ResponseWriter, r *http.Request) {
|
|
protocols, err := db.ListProtocols(s.DB)
|
|
if err != nil {
|
|
http.Error(w, "db error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
s.render(w, "protocols.html", pageData{
|
|
"Title": "Протоколы",
|
|
"UserName": s.Auth.CurrentName(r),
|
|
"Protocols": protocols,
|
|
"Active": "protocols",
|
|
})
|
|
}
|
|
|
|
func (s *Server) toggleProtocol(w http.ResponseWriter, r *http.Request) {
|
|
idStr := mux.Vars(r)["id"]
|
|
id, err := uuid.Parse(idStr)
|
|
if err != nil {
|
|
http.Error(w, "invalid id", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := db.ToggleProtocol(s.DB, id); err != nil {
|
|
http.Error(w, "db error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
http.Redirect(w, r, "/admin/protocols", http.StatusSeeOther)
|
|
}
|