server.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. package web
  2. import (
  3. "fmt"
  4. "dhcp-dns-manager/internal/config"
  5. "dhcp-dns-manager/internal/db"
  6. "dhcp-dns-manager/internal/dhcp"
  7. "dhcp-dns-manager/internal/dns"
  8. "github.com/gin-gonic/gin"
  9. "net/http"
  10. "sync"
  11. "time"
  12. )
  13. type Server struct {
  14. config *config.WebConfig
  15. db *db.DB
  16. dhcpServer *dhcp.Server
  17. dnsServer *dns.Server
  18. router *gin.Engine
  19. configManager *ConfigManager
  20. }
  21. type User struct {
  22. ID uint `gorm:"primaryKey"`
  23. Username string `gorm:"uniqueIndex"`
  24. Password string
  25. IsAdmin bool
  26. }
  27. func NewServer(cfg *config.WebConfig, database *db.DB, d *dhcp.Server, n *dns.Server, cm *ConfigManager) *Server {
  28. gin.SetMode(gin.ReleaseMode)
  29. s := &Server{
  30. config: cfg,
  31. db: database,
  32. dhcpServer: d,
  33. dnsServer: n,
  34. router: gin.New(),
  35. configManager: cm,
  36. }
  37. // Wire up config reloader so DHCP server picks up web UI config changes
  38. d.SetConfigReloader(func() *config.DHCPConfig {
  39. cfg := cm.GetConfig()
  40. dhcpCfg := new(config.DHCPConfig)
  41. *dhcpCfg = cfg.DHCP // copy the value
  42. return dhcpCfg
  43. })
  44. s.setupRoutes()
  45. return s
  46. }
  47. func (s *Server) setupRoutes() {
  48. // Custom recovery middleware that returns JSON
  49. s.router.Use(gin.CustomRecovery(func(c *gin.Context, err any) {
  50. c.JSON(http.StatusInternalServerError, gin.H{
  51. "error": fmt.Sprintf("Internal server error: %v", err),
  52. })
  53. c.Abort()
  54. }))
  55. s.router.Use(gin.Logger())
  56. // Inject ConfigManager into context
  57. s.router.Use(func(c *gin.Context) {
  58. c.Set("configManager", s.configManager)
  59. c.Next()
  60. })
  61. // Static files
  62. s.router.Static("/static", "./web/static")
  63. // Public routes
  64. s.router.GET("/", s.handleIndex)
  65. s.router.POST("/api/login", s.handleLogin)
  66. s.router.GET("/api/health", func(c *gin.Context) {
  67. c.JSON(http.StatusOK, gin.H{"status": "ok", "message": "Server is running"})
  68. })
  69. // Protected routes
  70. protected := s.router.Group("/api")
  71. protected.Use(s.authMiddleware())
  72. {
  73. // Dashboard
  74. protected.GET("/dashboard", s.handleDashboard)
  75. // DHCP
  76. protected.GET("/dhcp/config", s.handleGetDHCPConfig)
  77. protected.PUT("/dhcp/config", s.handleUpdateDHCPConfig)
  78. protected.GET("/dhcp/leases", s.handleGetLeases)
  79. protected.GET("/dhcp/bindings", s.handleGetBindings)
  80. protected.POST("/dhcp/bindings", s.handleCreateBinding)
  81. protected.DELETE("/dhcp/bindings/:id", s.handleDeleteBinding)
  82. // DNS
  83. protected.GET("/dns/config", s.handleGetDNSConfig)
  84. protected.PUT("/dns/config", s.handleUpdateDNSConfig)
  85. protected.GET("/dns/records", s.handleGetRecords)
  86. protected.POST("/dns/records", s.handleCreateRecord)
  87. protected.DELETE("/dns/records/:id", s.handleDeleteRecord)
  88. protected.GET("/dns/logs", s.handleGetLogs)
  89. protected.GET("/dns/zones", s.handleGetZones)
  90. protected.POST("/dns/zones", s.handleCreateZone)
  91. protected.DELETE("/dns/zones/:id", s.handleDeleteZone)
  92. // Config
  93. protected.GET("/config", s.handleGetFullConfig)
  94. protected.PUT("/config", s.handleUpdateConfig)
  95. protected.GET("/config/export", s.handleExportConfig)
  96. protected.POST("/config/import", s.handleImportConfig)
  97. // Service
  98. protected.POST("/service/restart", s.handleRestartService)
  99. }
  100. }
  101. // sessionStore in-memory session store
  102. var sessionStore = sync.Map{}
  103. func (s *Server) authMiddleware() gin.HandlerFunc {
  104. return func(c *gin.Context) {
  105. sessionID := c.GetHeader("X-Session-ID")
  106. if sessionID == "" {
  107. c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
  108. c.Abort()
  109. return
  110. }
  111. // Validate session exists in store
  112. if _, ok := sessionStore.Load(sessionID); !ok {
  113. c.JSON(http.StatusUnauthorized, gin.H{"error": "Session expired"})
  114. c.Abort()
  115. return
  116. }
  117. c.Next()
  118. }
  119. }
  120. func (s *Server) Start() error {
  121. addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port)
  122. return s.router.Run(addr)
  123. }
  124. // Handlers
  125. func (s *Server) handleIndex(c *gin.Context) {
  126. c.File("./web/templates/index.html")
  127. }
  128. func (s *Server) handleLogin(c *gin.Context) {
  129. var req struct {
  130. Username string `json:"username"`
  131. Password string `json:"password"`
  132. }
  133. if err := c.ShouldBindJSON(&req); err != nil {
  134. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  135. return
  136. }
  137. // TODO: Validate against database
  138. // For now, simple demo auth
  139. if req.Username == "admin" && req.Password == "admin" {
  140. sessionID := fmt.Sprintf("session-%d-%s", time.Now().UnixNano(), req.Username)
  141. sessionStore.Store(sessionID, true)
  142. c.JSON(http.StatusOK, gin.H{
  143. "session_id": sessionID,
  144. "is_admin": true,
  145. })
  146. return
  147. }
  148. c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
  149. }
  150. func (s *Server) handleDashboard(c *gin.Context) {
  151. leases := s.dhcpServer.GetLeases()
  152. bindings, _ := s.dhcpServer.GetStaticBindings()
  153. records, _ := s.dnsServer.GetDNSRecords()
  154. c.JSON(http.StatusOK, gin.H{
  155. "active_leases": len(leases),
  156. "static_bindings": len(bindings),
  157. "dns_records": len(records),
  158. "leases": leases,
  159. "bindings": bindings,
  160. "records": records,
  161. })
  162. }
  163. func (s *Server) handleGetLeases(c *gin.Context) {
  164. leases := s.dhcpServer.GetLeases()
  165. c.JSON(http.StatusOK, gin.H{"leases": leases})
  166. }
  167. func (s *Server) handleGetBindings(c *gin.Context) {
  168. bindings, err := s.dhcpServer.GetStaticBindings()
  169. if err != nil {
  170. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  171. return
  172. }
  173. c.JSON(http.StatusOK, gin.H{"bindings": bindings})
  174. }
  175. func (s *Server) handleCreateBinding(c *gin.Context) {
  176. var req struct {
  177. MAC string `json:"mac"`
  178. IP string `json:"ip"`
  179. Hostname string `json:"hostname"`
  180. Description string `json:"description"`
  181. }
  182. if err := c.ShouldBindJSON(&req); err != nil {
  183. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  184. return
  185. }
  186. if err := s.dhcpServer.CreateStaticBinding(req.MAC, req.IP, req.Hostname, req.Description); err != nil {
  187. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  188. return
  189. }
  190. c.JSON(http.StatusOK, gin.H{"message": "Binding created"})
  191. }
  192. func (s *Server) handleDeleteBinding(c *gin.Context) {
  193. _ = c.Param("id")
  194. // TODO: Convert to uint and delete
  195. c.JSON(http.StatusOK, gin.H{"message": "Binding deleted"})
  196. }
  197. func (s *Server) handleGetRecords(c *gin.Context) {
  198. records, err := s.dnsServer.GetDNSRecords()
  199. if err != nil {
  200. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  201. return
  202. }
  203. c.JSON(http.StatusOK, gin.H{"records": records})
  204. }
  205. func (s *Server) handleCreateRecord(c *gin.Context) {
  206. var req struct {
  207. Name string `json:"name"`
  208. Type string `json:"type"`
  209. Value string `json:"value"`
  210. TTL int `json:"ttl"`
  211. }
  212. if err := c.ShouldBindJSON(&req); err != nil {
  213. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  214. return
  215. }
  216. if err := s.dnsServer.CreateDNSRecord(req.Name, req.Type, req.Value, req.TTL); err != nil {
  217. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  218. return
  219. }
  220. c.JSON(http.StatusOK, gin.H{"message": "Record created"})
  221. }
  222. func (s *Server) handleDeleteRecord(c *gin.Context) {
  223. _ = c.Param("id")
  224. // TODO: Convert to uint and delete
  225. c.JSON(http.StatusOK, gin.H{"message": "Record deleted"})
  226. }
  227. func (s *Server) handleGetLogs(c *gin.Context) {
  228. logs, err := s.dnsServer.GetQueryLogs(100)
  229. if err != nil {
  230. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  231. return
  232. }
  233. c.JSON(http.StatusOK, gin.H{"logs": logs})
  234. }
  235. func (s *Server) handleGetZones(c *gin.Context) {
  236. zones, err := s.dnsServer.GetDNSZones()
  237. if err != nil {
  238. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  239. return
  240. }
  241. c.JSON(http.StatusOK, gin.H{"zones": zones})
  242. }
  243. func (s *Server) handleCreateZone(c *gin.Context) {
  244. var req struct {
  245. Name string `json:"name"`
  246. Type string `json:"type"`
  247. }
  248. if err := c.ShouldBindJSON(&req); err != nil {
  249. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  250. return
  251. }
  252. if err := s.dnsServer.CreateDNSZone(req.Name, req.Type); err != nil {
  253. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  254. return
  255. }
  256. c.JSON(http.StatusOK, gin.H{"message": "Zone created"})
  257. }
  258. func (s *Server) handleDeleteZone(c *gin.Context) {
  259. _ = c.Param("id")
  260. // TODO: Convert to uint and delete
  261. c.JSON(http.StatusOK, gin.H{"message": "Zone deleted"})
  262. }
  263. func (s *Server) handleGetConfig(c *gin.Context) {
  264. // Return current config (without sensitive data)
  265. c.JSON(http.StatusOK, gin.H{"config": "placeholder"})
  266. }
  267. func (s *Server) handleUpdateConfig(c *gin.Context) {
  268. // Update config
  269. c.JSON(http.StatusOK, gin.H{"message": "Config updated"})
  270. }