212 lines
4.9 KiB
Go
Raw Normal View History

2014-03-02 23:58:14 +01:00
package main
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"runtime"
"sync"
"time"
"github.com/calmh/syncthing/scanner"
2014-03-02 23:58:14 +01:00
"github.com/codegangsta/martini"
2014-04-08 15:56:12 +02:00
"github.com/codegangsta/martini-contrib/auth"
2014-03-02 23:58:14 +01:00
)
type guiError struct {
Time time.Time
Error string
}
var (
configInSync = true
guiErrors = []guiError{}
guiErrorsMut sync.Mutex
)
2014-04-08 15:56:12 +02:00
func startGUI(cfg GUIConfiguration, m *Model) {
2014-03-02 23:58:14 +01:00
router := martini.NewRouter()
router.Get("/", getRoot)
router.Get("/rest/version", restGetVersion)
router.Get("/rest/model/:repo", restGetModel)
2014-03-02 23:58:14 +01:00
router.Get("/rest/connections", restGetConnections)
router.Get("/rest/config", restGetConfig)
router.Get("/rest/config/sync", restGetConfigInSync)
router.Get("/rest/need/:repo", restGetNeed)
2014-03-02 23:58:14 +01:00
router.Get("/rest/system", restGetSystem)
router.Get("/rest/errors", restGetErrors)
router.Post("/rest/config", restPostConfig)
router.Post("/rest/restart", restPostRestart)
router.Post("/rest/reset", restPostReset)
2014-03-02 23:58:14 +01:00
router.Post("/rest/error", restPostError)
router.Post("/rest/error/clear", restClearErrors)
2014-03-02 23:58:14 +01:00
go func() {
mr := martini.New()
2014-04-08 15:56:12 +02:00
if len(cfg.User) > 0 && len(cfg.Password) > 0 {
mr.Use(auth.Basic(cfg.User, cfg.Password))
}
2014-03-02 23:58:14 +01:00
mr.Use(embeddedStatic())
mr.Use(martini.Recovery())
mr.Use(restMiddleware)
2014-03-02 23:58:14 +01:00
mr.Action(router.Handle)
mr.Map(m)
2014-04-08 15:56:12 +02:00
err := http.ListenAndServe(cfg.Address, mr)
2014-03-02 23:58:14 +01:00
if err != nil {
warnln("GUI not possible:", err)
}
}()
}
func getRoot(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/index.html", 302)
}
func restMiddleware(w http.ResponseWriter, r *http.Request) {
if len(r.URL.Path) >= 6 && r.URL.Path[:6] == "/rest/" {
w.Header().Set("Cache-Control", "no-cache")
}
}
2014-03-02 23:58:14 +01:00
func restGetVersion() string {
return Version
}
func restGetModel(m *Model, w http.ResponseWriter, params martini.Params) {
var repo = params["repo"]
2014-03-02 23:58:14 +01:00
var res = make(map[string]interface{})
globalFiles, globalDeleted, globalBytes := m.GlobalSize(repo)
2014-03-02 23:58:14 +01:00
res["globalFiles"], res["globalDeleted"], res["globalBytes"] = globalFiles, globalDeleted, globalBytes
localFiles, localDeleted, localBytes := m.LocalSize(repo)
2014-03-02 23:58:14 +01:00
res["localFiles"], res["localDeleted"], res["localBytes"] = localFiles, localDeleted, localBytes
needFiles, needBytes := m.NeedSize(repo)
res["needFiles"], res["needBytes"] = needFiles, needBytes
2014-03-02 23:58:14 +01:00
res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes
2014-03-02 23:58:14 +01:00
res["state"] = m.State(repo)
2014-03-02 23:58:14 +01:00
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}
func restGetConnections(m *Model, w http.ResponseWriter) {
var res = m.ConnectionStats()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}
func restGetConfig(w http.ResponseWriter) {
json.NewEncoder(w).Encode(cfg)
}
func restPostConfig(req *http.Request) {
err := json.NewDecoder(req.Body).Decode(&cfg)
if err != nil {
log.Println(err)
} else {
saveConfig()
configInSync = false
}
}
func restGetConfigInSync(w http.ResponseWriter) {
json.NewEncoder(w).Encode(map[string]bool{"configInSync": configInSync})
}
func restPostRestart(req *http.Request) {
go restart()
}
func restPostReset(req *http.Request) {
resetRepositories()
go restart()
2014-03-02 23:58:14 +01:00
}
type guiFile scanner.File
2014-03-02 23:58:14 +01:00
func (f guiFile) MarshalJSON() ([]byte, error) {
type t struct {
Name string
Size int64
Modified int64
Flags uint32
2014-03-02 23:58:14 +01:00
}
return json.Marshal(t{
Name: f.Name,
Size: scanner.File(f).Size,
Modified: f.Modified,
Flags: f.Flags,
2014-03-02 23:58:14 +01:00
})
}
func restGetNeed(m *Model, w http.ResponseWriter, params martini.Params) {
repo := params["repo"]
files := m.NeedFilesRepo(repo)
2014-03-02 23:58:14 +01:00
gfs := make([]guiFile, len(files))
for i, f := range files {
gfs[i] = guiFile(f)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(gfs)
}
2014-04-14 12:02:40 +02:00
var cpuUsagePercent [10]float64 // The last ten seconds
2014-03-02 23:58:14 +01:00
var cpuUsageLock sync.RWMutex
func restGetSystem(w http.ResponseWriter) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
res := make(map[string]interface{})
res["myID"] = myID
res["goroutines"] = runtime.NumGoroutine()
res["alloc"] = m.Alloc
res["sys"] = m.Sys
if discoverer != nil {
res["extAnnounceOK"] = discoverer.ExtAnnounceOK()
}
2014-03-02 23:58:14 +01:00
cpuUsageLock.RLock()
2014-04-14 12:02:40 +02:00
var cpusum float64
for _, p := range cpuUsagePercent {
cpusum += p
}
2014-03-02 23:58:14 +01:00
cpuUsageLock.RUnlock()
2014-04-14 12:02:40 +02:00
res["cpuPercent"] = cpusum / 10
2014-03-02 23:58:14 +01:00
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(res)
}
func restGetErrors(w http.ResponseWriter) {
guiErrorsMut.Lock()
json.NewEncoder(w).Encode(guiErrors)
guiErrorsMut.Unlock()
}
func restPostError(req *http.Request) {
bs, _ := ioutil.ReadAll(req.Body)
req.Body.Close()
showGuiError(string(bs))
}
func restClearErrors() {
guiErrorsMut.Lock()
guiErrors = nil
guiErrorsMut.Unlock()
}
2014-03-02 23:58:14 +01:00
func showGuiError(err string) {
guiErrorsMut.Lock()
guiErrors = append(guiErrors, guiError{time.Now(), err})
if len(guiErrors) > 5 {
guiErrors = guiErrors[len(guiErrors)-5:]
}
guiErrorsMut.Unlock()
}