diff --git a/gui.go b/gui.go index 200f96c1..7c4c7059 100644 --- a/gui.go +++ b/gui.go @@ -8,6 +8,8 @@ import ( "mime" "net/http" "path/filepath" + "runtime" + "sync" "bitbucket.org/tebeka/nrsc" "github.com/calmh/syncthing/model" @@ -22,6 +24,7 @@ func startGUI(addr string, m *model.Model) { router.Get("/rest/connections", restGetConnections) router.Get("/rest/config", restGetConfig) router.Get("/rest/need", restGetNeed) + router.Get("/rest/system", restGetSystem) go func() { mr := martini.New() @@ -34,6 +37,7 @@ func startGUI(addr string, m *model.Model) { warnln("GUI not possible:", err) } }() + } func getRoot(w http.ResponseWriter, r *http.Request) { @@ -100,6 +104,25 @@ func restGetNeed(m *model.Model, w http.ResponseWriter) { json.NewEncoder(w).Encode(gfs) } +var cpuUsagePercent float64 +var cpuUsageLock sync.RWMutex + +func restGetSystem(w http.ResponseWriter) { + var m runtime.MemStats + runtime.ReadMemStats(&m) + + res := make(map[string]interface{}) + res["goroutines"] = runtime.NumGoroutine() + res["alloc"] = m.Alloc + res["sys"] = m.Sys + cpuUsageLock.RLock() + res["cpuPercent"] = cpuUsagePercent + cpuUsageLock.RUnlock() + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(res) +} + func nrscStatic(path string) interface{} { if err := nrsc.Initialize(); err != nil { panic("Unable to initialize nrsc: " + err.Error()) diff --git a/gui/app.js b/gui/app.js index 37d3ed1d..dbac161d 100644 --- a/gui/app.js +++ b/gui/app.js @@ -26,6 +26,9 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) { }); $scope.refresh = function () { + $http.get("/rest/system").success(function (data) { + $scope.system = data; + }); $http.get("/rest/model").success(function (data) { $scope.model = data; modelGetSucceeded(); @@ -71,16 +74,19 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) { setInterval($scope.refresh, 10000); }); -function decimals(num) { - if (num > 100) { - return 0; - } - if (num > 10) { - return 1; - } - return 2; +function decimals(val, num) { + if (val === 0) { return 0; } + var digits = Math.floor(Math.log(Math.abs(val))/Math.log(10)); + var decimals = Math.max(0, num - digits); + return decimals; } +syncthing.filter('natural', function() { + return function(input, valid) { + return input.toFixed(decimals(input, valid)); + } +}); + syncthing.filter('binary', function() { return function(input) { if (input === undefined) { @@ -88,15 +94,15 @@ syncthing.filter('binary', function() { } if (input > 1024 * 1024 * 1024) { input /= 1024 * 1024 * 1024; - return input.toFixed(decimals(input)) + ' Gi'; + return input.toFixed(decimals(input, 2)) + ' Gi'; } if (input > 1024 * 1024) { input /= 1024 * 1024; - return input.toFixed(decimals(input)) + ' Mi'; + return input.toFixed(decimals(input, 2)) + ' Mi'; } if (input > 1024) { input /= 1024; - return input.toFixed(decimals(input)) + ' Ki'; + return input.toFixed(decimals(input, 2)) + ' Ki'; } return Math.round(input) + ' '; } @@ -109,15 +115,15 @@ syncthing.filter('metric', function() { } if (input > 1000 * 1000 * 1000) { input /= 1000 * 1000 * 1000; - return input.toFixed(decimals(input)) + ' G'; + return input.toFixed(decimals(input, 2)) + ' G'; } if (input > 1000 * 1000) { input /= 1000 * 1000; - return input.toFixed(decimals(input)) + ' M'; + return input.toFixed(decimals(input, 2)) + ' M'; } if (input > 1000) { input /= 1000; - return input.toFixed(decimals(input)) + ' k'; + return input.toFixed(decimals(input, 2)) + ' k'; } return Math.round(input) + ' '; } diff --git a/gui/index.html b/gui/index.html index 48a39a9b..41dee585 100644 --- a/gui/index.html +++ b/gui/index.html @@ -47,27 +47,42 @@ html, body {
-

Synchronization

-
-
- {{100 * model.inSyncBytes / model.globalBytes | number:0}}% +
+

Synchronization

+
+
+
+ {{100 * model.inSyncBytes / model.globalBytes | alwaysNumber | number:0}}% +
+
+

Need {{model.needFiles | alwaysNumber}} files, {{model.needBytes | binary}}B

-

Need {{model.needFiles | alwaysNumber}} files, {{model.needBytes | binary}}B

-

Repository Status

+
+

Repository

+
+

Cluster contains {{model.globalFiles | alwaysNumber}} files, {{model.globalBytes | binary}}B + (+{{model.globalDeleted | alwaysNumber}} delete records)

-

Cluster contains {{model.globalFiles | alwaysNumber}} files, {{model.globalBytes | binary}}B - (+{{model.globalDeleted | alwaysNumber}} delete records)

+

Local repository has {{model.localFiles | alwaysNumber}} files, {{model.localBytes | binary}}B + (+{{model.localDeleted | alwaysNumber}} delete records)

+
+
-

Local repository has {{model.localFiles | alwaysNumber}} files, {{model.localBytes | binary}}B - (+{{model.localDeleted | alwaysNumber}} delete records)

+
+

System

+
+

{{system.sys | binary}}B RAM allocated, {{system.alloc | binary}}B in use

+

{{system.cpuPercent | alwaysNumber | natural:1}}% CPU, {{system.goroutines | alwaysNumber}} goroutines

+
+

Files to Synchronize

@@ -80,32 +95,34 @@ html, body {
-

Cluster Status

- - - - - - - - - -
{{node | short}} - - - {{connections[node].Address}} - - - - {{address}} - - - {{connections[node].inbps | metric}}b/s - - - {{connections[node].outbps | metric}}b/s - -
+
+

Cluster

+ + + + + + + + + +
{{node | short}} + + + {{connections[node].Address}} + + + + {{address}} + + + {{connections[node].inbps | metric}}b/s + + + {{connections[node].outbps | metric}}b/s + +
+
@@ -123,7 +140,7 @@ html, body {