gui.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "log"
  7. "mime"
  8. "net/http"
  9. "path/filepath"
  10. "runtime"
  11. "sync"
  12. "bitbucket.org/tebeka/nrsc"
  13. "github.com/calmh/syncthing/model"
  14. "github.com/codegangsta/martini"
  15. )
  16. func startGUI(addr string, m *model.Model) {
  17. router := martini.NewRouter()
  18. router.Get("/", getRoot)
  19. router.Get("/rest/version", restGetVersion)
  20. router.Get("/rest/model", restGetModel)
  21. router.Get("/rest/connections", restGetConnections)
  22. router.Get("/rest/config", restGetConfig)
  23. router.Get("/rest/need", restGetNeed)
  24. router.Get("/rest/system", restGetSystem)
  25. go func() {
  26. mr := martini.New()
  27. mr.Use(nrscStatic("gui"))
  28. mr.Use(martini.Recovery())
  29. mr.Action(router.Handle)
  30. mr.Map(m)
  31. err := http.ListenAndServe(addr, mr)
  32. if err != nil {
  33. warnln("GUI not possible:", err)
  34. }
  35. }()
  36. }
  37. func getRoot(w http.ResponseWriter, r *http.Request) {
  38. http.Redirect(w, r, "/index.html", 302)
  39. }
  40. func restGetVersion() string {
  41. return Version
  42. }
  43. func restGetModel(m *model.Model, w http.ResponseWriter) {
  44. var res = make(map[string]interface{})
  45. globalFiles, globalDeleted, globalBytes := m.GlobalSize()
  46. res["globalFiles"], res["globalDeleted"], res["globalBytes"] = globalFiles, globalDeleted, globalBytes
  47. localFiles, localDeleted, localBytes := m.LocalSize()
  48. res["localFiles"], res["localDeleted"], res["localBytes"] = localFiles, localDeleted, localBytes
  49. inSyncFiles, inSyncBytes := m.InSyncSize()
  50. res["inSyncFiles"], res["inSyncBytes"] = inSyncFiles, inSyncBytes
  51. files, total := m.NeedFiles()
  52. res["needFiles"], res["needBytes"] = len(files), total
  53. w.Header().Set("Content-Type", "application/json")
  54. json.NewEncoder(w).Encode(res)
  55. }
  56. func restGetConnections(m *model.Model, w http.ResponseWriter) {
  57. var res = m.ConnectionStats()
  58. w.Header().Set("Content-Type", "application/json")
  59. json.NewEncoder(w).Encode(res)
  60. }
  61. func restGetConfig(w http.ResponseWriter) {
  62. var res = make(map[string]interface{})
  63. res["myID"] = myID
  64. res["repository"] = config.OptionMap("repository")
  65. res["nodes"] = config.OptionMap("nodes")
  66. w.Header().Set("Content-Type", "application/json")
  67. json.NewEncoder(w).Encode(res)
  68. }
  69. type guiFile model.File
  70. func (f guiFile) MarshalJSON() ([]byte, error) {
  71. type t struct {
  72. Name string
  73. Size int
  74. }
  75. return json.Marshal(t{
  76. Name: f.Name,
  77. Size: model.File(f).Size(),
  78. })
  79. }
  80. func restGetNeed(m *model.Model, w http.ResponseWriter) {
  81. files, _ := m.NeedFiles()
  82. gfs := make([]guiFile, len(files))
  83. for i, f := range files {
  84. gfs[i] = guiFile(f)
  85. }
  86. w.Header().Set("Content-Type", "application/json")
  87. json.NewEncoder(w).Encode(gfs)
  88. }
  89. var cpuUsagePercent float64
  90. var cpuUsageLock sync.RWMutex
  91. func restGetSystem(w http.ResponseWriter) {
  92. var m runtime.MemStats
  93. runtime.ReadMemStats(&m)
  94. res := make(map[string]interface{})
  95. res["goroutines"] = runtime.NumGoroutine()
  96. res["alloc"] = m.Alloc
  97. res["sys"] = m.Sys
  98. cpuUsageLock.RLock()
  99. res["cpuPercent"] = cpuUsagePercent
  100. cpuUsageLock.RUnlock()
  101. w.Header().Set("Content-Type", "application/json")
  102. json.NewEncoder(w).Encode(res)
  103. }
  104. func nrscStatic(path string) interface{} {
  105. if err := nrsc.Initialize(); err != nil {
  106. panic("Unable to initialize nrsc: " + err.Error())
  107. }
  108. return func(res http.ResponseWriter, req *http.Request, log *log.Logger) {
  109. file := req.URL.Path
  110. // nrsc expects there not to be a leading slash
  111. if file[0] == '/' {
  112. file = file[1:]
  113. }
  114. f := nrsc.Get(file)
  115. if f == nil {
  116. return
  117. }
  118. rdr, err := f.Open()
  119. if err != nil {
  120. http.Error(res, "Internal Server Error", http.StatusInternalServerError)
  121. }
  122. defer rdr.Close()
  123. mtype := mime.TypeByExtension(filepath.Ext(req.URL.Path))
  124. if len(mtype) != 0 {
  125. res.Header().Set("Content-Type", mtype)
  126. }
  127. res.Header().Set("Content-Size", fmt.Sprintf("%d", f.Size()))
  128. res.Header().Set("Last-Modified", f.ModTime().UTC().Format(http.TimeFormat))
  129. io.Copy(res, rdr)
  130. }
  131. }