initial: ZeroTier-like P2P mesh VPN server with multi-tenant Web UI
This commit is contained in:
62
internal/handler/admin.go
Normal file
62
internal/handler/admin.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"zeromesh/internal/database"
|
||||
"zeromesh/internal/model"
|
||||
"zeromesh/internal/service"
|
||||
)
|
||||
|
||||
type AdminHandler struct {
|
||||
authService *service.AuthService
|
||||
db *database.DB
|
||||
nodeService *service.NodeService
|
||||
netService *service.NetworkService
|
||||
}
|
||||
|
||||
func NewAdminHandler(authService *service.AuthService, db *database.DB, nodeService *service.NodeService, netService *service.NetworkService) *AdminHandler {
|
||||
return &AdminHandler{
|
||||
authService: authService,
|
||||
db: db,
|
||||
nodeService: nodeService,
|
||||
netService: netService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AdminHandler) Dashboard(c *gin.Context) {
|
||||
uid := userID(c)
|
||||
|
||||
var authorizedMembers int64
|
||||
if isAdmin(c) {
|
||||
h.db.Model(&model.NetworkMember{}).Where("authorized = ?", true).Count(&authorizedMembers)
|
||||
} else {
|
||||
networks, _ := h.netService.ListNetworks(uid)
|
||||
var ids []uint32
|
||||
for _, n := range networks {
|
||||
ids = append(ids, n.NetworkID)
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
h.db.Model(&model.NetworkMember{}).Where("authorized = ? AND network_id IN ?", true, ids).Count(&authorizedMembers)
|
||||
}
|
||||
}
|
||||
|
||||
nodes, _ := h.nodeService.ListNode(uid)
|
||||
var online int64
|
||||
for _, n := range nodes {
|
||||
if n.Online {
|
||||
online++
|
||||
}
|
||||
}
|
||||
|
||||
networks, _ := h.netService.ListNetworks(uid)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"authorized_members": authorizedMembers,
|
||||
"nodes_total": len(nodes),
|
||||
"nodes_online": online,
|
||||
"networks_count": len(networks),
|
||||
})
|
||||
}
|
||||
87
internal/handler/auth.go
Normal file
87
internal/handler/auth.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"zeromesh/internal/service"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
func NewAuthHandler(authService *service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{authService: authService}
|
||||
}
|
||||
|
||||
type loginReq struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type registerReq struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req loginReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
token, user, err := h.authService.Login(req.Username, req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"token": token,
|
||||
"user": user,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req registerReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
user, token, err := h.authService.Register(req.Username, req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"token": token,
|
||||
"user": user,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) InitAdmin(c *gin.Context) {
|
||||
var req registerReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
user, token, err := h.authService.InitAdmin(req.Username, req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"token": token,
|
||||
"user": user,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) CheckAdmin(c *gin.Context) {
|
||||
exists, err := h.authService.CheckAdmin()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"admin_exists": exists})
|
||||
}
|
||||
23
internal/handler/context.go
Normal file
23
internal/handler/context.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package handler
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func userID(c *gin.Context) uint {
|
||||
id, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
return 0
|
||||
}
|
||||
uid, ok := id.(uint)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return uid
|
||||
}
|
||||
|
||||
func isAdmin(c *gin.Context) bool {
|
||||
role, exists := c.Get("role")
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
return role.(string) == "admin"
|
||||
}
|
||||
75
internal/handler/log.go
Normal file
75
internal/handler/log.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"zeromesh/internal/service"
|
||||
)
|
||||
|
||||
type LogHandler struct {
|
||||
logService *service.LogService
|
||||
}
|
||||
|
||||
func NewLogHandler(logService *service.LogService) *LogHandler {
|
||||
return &LogHandler{logService: logService}
|
||||
}
|
||||
|
||||
func (h *LogHandler) ListApiLogs(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
ip := c.Query("ip")
|
||||
path := c.Query("path")
|
||||
method := c.Query("method")
|
||||
statusCode, _ := strconv.Atoi(c.Query("status_code"))
|
||||
|
||||
logs, total, err := h.logService.ListApiLogs(page, pageSize, ip, path, method, statusCode)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": logs, "total": total, "page": page, "page_size": pageSize})
|
||||
}
|
||||
|
||||
func (h *LogHandler) ListErrorLogs(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
ip := c.Query("ip")
|
||||
path := c.Query("path")
|
||||
method := c.Query("method")
|
||||
statusCode, _ := strconv.Atoi(c.Query("status_code"))
|
||||
|
||||
logs, total, err := h.logService.ListErrorLogs(page, pageSize, ip, path, method, statusCode)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": logs, "total": total, "page": page, "page_size": pageSize})
|
||||
}
|
||||
|
||||
func (h *LogHandler) ListApiLogGrouped(c *gin.Context) {
|
||||
groups, err := h.logService.ListApiLogGrouped()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": groups})
|
||||
}
|
||||
|
||||
func (h *LogHandler) ClearApiLogs(c *gin.Context) {
|
||||
if err := h.logService.ClearApiLogs(); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "ok"})
|
||||
}
|
||||
|
||||
func (h *LogHandler) ClearErrorLogs(c *gin.Context) {
|
||||
if err := h.logService.ClearErrorLogs(); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "ok"})
|
||||
}
|
||||
143
internal/handler/network.go
Normal file
143
internal/handler/network.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"zeromesh/internal/service"
|
||||
)
|
||||
|
||||
type NetworkHandler struct {
|
||||
networkService *service.NetworkService
|
||||
}
|
||||
|
||||
func NewNetworkHandler(networkService *service.NetworkService) *NetworkHandler {
|
||||
return &NetworkHandler{networkService: networkService}
|
||||
}
|
||||
|
||||
type createNetworkReq struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
IPRange string `json:"ip_range"`
|
||||
}
|
||||
|
||||
func (h *NetworkHandler) Create(c *gin.Context) {
|
||||
var req createNetworkReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
uid := userID(c)
|
||||
network, err := h.networkService.CreateNetwork(uid, req.Name, req.IPRange)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"network": network})
|
||||
}
|
||||
|
||||
func (h *NetworkHandler) List(c *gin.Context) {
|
||||
uid := userID(c)
|
||||
if !isAdmin(c) {
|
||||
networks, err := h.networkService.ListNetworks(uid)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"networks": networks})
|
||||
return
|
||||
}
|
||||
networks, err := h.networkService.ListNetworks(0)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"networks": networks})
|
||||
}
|
||||
|
||||
func (h *NetworkHandler) Get(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid network id"})
|
||||
return
|
||||
}
|
||||
uid := userID(c)
|
||||
network, err := h.networkService.GetNetwork(uint32(id), uid)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "network not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"network": network})
|
||||
}
|
||||
|
||||
func (h *NetworkHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid network id"})
|
||||
return
|
||||
}
|
||||
uid := userID(c)
|
||||
if err := h.networkService.DeleteNetwork(uint32(id), uid); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
|
||||
}
|
||||
|
||||
func (h *NetworkHandler) Members(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid network id"})
|
||||
return
|
||||
}
|
||||
uid := userID(c)
|
||||
members, err := h.networkService.ListMembers(uint32(id), uid)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"members": members})
|
||||
}
|
||||
|
||||
type authorizeReq struct {
|
||||
NodeID string `json:"node_id" binding:"required"`
|
||||
}
|
||||
|
||||
func (h *NetworkHandler) Authorize(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid network id"})
|
||||
return
|
||||
}
|
||||
var req authorizeReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
uid := userID(c)
|
||||
if err := h.networkService.AuthorizeMember(uint32(id), req.NodeID, uid); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "member authorized"})
|
||||
}
|
||||
|
||||
func (h *NetworkHandler) Deauthorize(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid network id"})
|
||||
return
|
||||
}
|
||||
var req authorizeReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
uid := userID(c)
|
||||
if err := h.networkService.DeauthorizeMember(uint32(id), req.NodeID, uid); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "member deauthorized"})
|
||||
}
|
||||
81
internal/handler/node.go
Normal file
81
internal/handler/node.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"zeromesh/internal/service"
|
||||
)
|
||||
|
||||
type NodeHandler struct {
|
||||
nodeService *service.NodeService
|
||||
}
|
||||
|
||||
func NewNodeHandler(nodeService *service.NodeService) *NodeHandler {
|
||||
return &NodeHandler{nodeService: nodeService}
|
||||
}
|
||||
|
||||
type registerNodeReq struct {
|
||||
NodeID string `json:"node_id" binding:"required"`
|
||||
PublicKey string `json:"public_key" binding:"required"`
|
||||
Name string `json:"name"`
|
||||
IPAddress string `json:"ip_address"`
|
||||
Port int `json:"port"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (h *NodeHandler) Register(c *gin.Context) {
|
||||
var req registerNodeReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
uid := userID(c)
|
||||
node, err := h.nodeService.Register(req.NodeID, req.PublicKey, req.Name, req.IPAddress, req.Port, req.Version, uid)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"node": node})
|
||||
}
|
||||
|
||||
func (h *NodeHandler) List(c *gin.Context) {
|
||||
uid := userID(c)
|
||||
if !isAdmin(c) {
|
||||
nodes, err := h.nodeService.ListNode(uid)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"nodes": nodes})
|
||||
return
|
||||
}
|
||||
nodes, err := h.nodeService.ListNode(0)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"nodes": nodes})
|
||||
}
|
||||
|
||||
func (h *NodeHandler) ListOnline(c *gin.Context) {
|
||||
uid := userID(c)
|
||||
nodes, err := h.nodeService.ListOnline(uid)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"nodes": nodes})
|
||||
}
|
||||
|
||||
func (h *NodeHandler) Heartbeat(c *gin.Context) {
|
||||
nodeID := c.Param("node_id")
|
||||
if err := h.nodeService.SetOnline(nodeID, true); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||
}
|
||||
Reference in New Issue
Block a user