site.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package sites
  2. import (
  3. "net/http"
  4. "os"
  5. "github.com/0xJacky/Nginx-UI/internal/cert"
  6. "github.com/0xJacky/Nginx-UI/internal/helper"
  7. "github.com/0xJacky/Nginx-UI/internal/nginx"
  8. "github.com/0xJacky/Nginx-UI/internal/site"
  9. "github.com/0xJacky/Nginx-UI/model"
  10. "github.com/0xJacky/Nginx-UI/query"
  11. "github.com/gin-gonic/gin"
  12. "github.com/uozi-tech/cosy"
  13. "github.com/uozi-tech/cosy/logger"
  14. "gorm.io/gorm/clause"
  15. )
  16. func GetSite(c *gin.Context) {
  17. name := helper.UnescapeURL(c.Param("name"))
  18. path := nginx.GetConfPath("sites-available", name)
  19. file, err := os.Stat(path)
  20. if os.IsNotExist(err) {
  21. c.JSON(http.StatusNotFound, gin.H{
  22. "message": "file not found",
  23. })
  24. return
  25. }
  26. s := query.Site
  27. siteModel, err := s.Where(s.Path.Eq(path)).FirstOrCreate()
  28. if err != nil {
  29. cosy.ErrHandler(c, err)
  30. return
  31. }
  32. certModel, err := model.FirstCert(name)
  33. if err != nil {
  34. logger.Warn(err)
  35. }
  36. if siteModel.Advanced {
  37. origContent, err := os.ReadFile(path)
  38. if err != nil {
  39. cosy.ErrHandler(c, err)
  40. return
  41. }
  42. c.JSON(http.StatusOK, site.Site{
  43. ModifiedAt: file.ModTime(),
  44. Site: siteModel,
  45. Name: name,
  46. Config: string(origContent),
  47. AutoCert: certModel.AutoCert == model.AutoCertEnabled,
  48. Filepath: path,
  49. Status: site.GetSiteStatus(name),
  50. })
  51. return
  52. }
  53. nginxConfig, err := nginx.ParseNgxConfig(path)
  54. if err != nil {
  55. cosy.ErrHandler(c, err)
  56. return
  57. }
  58. certInfoMap := make(map[int][]*cert.Info)
  59. for serverIdx, server := range nginxConfig.Servers {
  60. for _, directive := range server.Directives {
  61. if directive.Directive == "ssl_certificate" {
  62. pubKey, err := cert.GetCertInfo(directive.Params)
  63. if err != nil {
  64. logger.Error("Failed to get certificate information", err)
  65. continue
  66. }
  67. certInfoMap[serverIdx] = append(certInfoMap[serverIdx], pubKey)
  68. }
  69. }
  70. }
  71. c.JSON(http.StatusOK, site.Site{
  72. Site: siteModel,
  73. ModifiedAt: file.ModTime(),
  74. Name: name,
  75. Config: nginxConfig.FmtCode(),
  76. Tokenized: nginxConfig,
  77. AutoCert: certModel.AutoCert == model.AutoCertEnabled,
  78. CertInfo: certInfoMap,
  79. Filepath: path,
  80. Status: site.GetSiteStatus(name),
  81. })
  82. }
  83. func SaveSite(c *gin.Context) {
  84. name := helper.UnescapeURL(c.Param("name"))
  85. var json struct {
  86. Content string `json:"content" binding:"required"`
  87. EnvGroupID uint64 `json:"env_group_id"`
  88. SyncNodeIDs []uint64 `json:"sync_node_ids"`
  89. Overwrite bool `json:"overwrite"`
  90. PostAction string `json:"post_action"`
  91. }
  92. if !cosy.BindAndValid(c, &json) {
  93. return
  94. }
  95. err := site.Save(name, json.Content, json.Overwrite, json.EnvGroupID, json.SyncNodeIDs, json.PostAction)
  96. if err != nil {
  97. cosy.ErrHandler(c, err)
  98. return
  99. }
  100. GetSite(c)
  101. }
  102. func RenameSite(c *gin.Context) {
  103. oldName := helper.UnescapeURL(c.Param("name"))
  104. var json struct {
  105. NewName string `json:"new_name"`
  106. }
  107. if !cosy.BindAndValid(c, &json) {
  108. return
  109. }
  110. err := site.Rename(oldName, json.NewName)
  111. if err != nil {
  112. cosy.ErrHandler(c, err)
  113. return
  114. }
  115. c.JSON(http.StatusOK, gin.H{
  116. "message": "ok",
  117. })
  118. }
  119. func EnableSite(c *gin.Context) {
  120. name := helper.UnescapeURL(c.Param("name"))
  121. // Check if the site is in maintenance mode, if yes, disable maintenance mode first
  122. maintenanceConfigPath := nginx.GetConfPath("sites-enabled", name+site.MaintenanceSuffix)
  123. if _, err := os.Stat(maintenanceConfigPath); err == nil {
  124. // Site is in maintenance mode, disable it first
  125. err := site.DisableMaintenance(name)
  126. if err != nil {
  127. cosy.ErrHandler(c, err)
  128. return
  129. }
  130. }
  131. // Then enable the site normally
  132. err := site.Enable(name)
  133. if err != nil {
  134. cosy.ErrHandler(c, err)
  135. return
  136. }
  137. c.JSON(http.StatusOK, gin.H{
  138. "message": "ok",
  139. })
  140. }
  141. func DisableSite(c *gin.Context) {
  142. name := helper.UnescapeURL(c.Param("name"))
  143. // Check if the site is in maintenance mode, if yes, disable maintenance mode first
  144. maintenanceConfigPath := nginx.GetConfPath("sites-enabled", name+site.MaintenanceSuffix)
  145. if _, err := os.Stat(maintenanceConfigPath); err == nil {
  146. // Site is in maintenance mode, disable it first
  147. err := site.DisableMaintenance(name)
  148. if err != nil {
  149. cosy.ErrHandler(c, err)
  150. return
  151. }
  152. }
  153. // Then disable the site normally
  154. err := site.Disable(name)
  155. if err != nil {
  156. cosy.ErrHandler(c, err)
  157. return
  158. }
  159. c.JSON(http.StatusOK, gin.H{
  160. "message": "ok",
  161. })
  162. }
  163. func DeleteSite(c *gin.Context) {
  164. err := site.Delete(helper.UnescapeURL(c.Param("name")))
  165. if err != nil {
  166. cosy.ErrHandler(c, err)
  167. return
  168. }
  169. c.JSON(http.StatusOK, gin.H{
  170. "message": "ok",
  171. })
  172. }
  173. func BatchUpdateSites(c *gin.Context) {
  174. cosy.Core[model.Site](c).SetValidRules(gin.H{
  175. "env_group_id": "required",
  176. }).SetItemKey("path").
  177. BeforeExecuteHook(func(ctx *cosy.Ctx[model.Site]) {
  178. effectedPath := make([]string, len(ctx.BatchEffectedIDs))
  179. var sites []*model.Site
  180. for i, name := range ctx.BatchEffectedIDs {
  181. path := nginx.GetConfPath("sites-available", name)
  182. effectedPath[i] = path
  183. sites = append(sites, &model.Site{
  184. Path: path,
  185. })
  186. }
  187. s := query.Site
  188. err := s.Clauses(clause.OnConflict{
  189. DoNothing: true,
  190. }).Create(sites...)
  191. if err != nil {
  192. ctx.AbortWithError(err)
  193. return
  194. }
  195. ctx.BatchEffectedIDs = effectedPath
  196. }).BatchModify()
  197. }
  198. func EnableMaintenanceSite(c *gin.Context) {
  199. name := helper.UnescapeURL(c.Param("name"))
  200. // If site is already enabled, disable the normal site first
  201. enabledConfigPath := nginx.GetConfPath("sites-enabled", name)
  202. if _, err := os.Stat(enabledConfigPath); err == nil {
  203. // Site is already enabled, disable normal site first
  204. err := site.Disable(name)
  205. if err != nil {
  206. cosy.ErrHandler(c, err)
  207. return
  208. }
  209. }
  210. // Then enable maintenance mode
  211. err := site.EnableMaintenance(name)
  212. if err != nil {
  213. cosy.ErrHandler(c, err)
  214. return
  215. }
  216. c.JSON(http.StatusOK, gin.H{
  217. "message": "ok",
  218. })
  219. }