diff --git a/gui/default/index.html b/gui/default/index.html
index 1868052c..bca5b8c7 100644
--- a/gui/default/index.html
+++ b/gui/default/index.html
@@ -715,8 +715,14 @@
- {{addr}}
- {{addr}}
+
+ {{addr}}
+ {{abbreviatedError(addr)}}
+
+
+ {{addr}}
+ {{abbreviatedError(addr)}}
+
|
diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js
index b47f1777..44ea33a7 100755
--- a/gui/default/syncthing/core/syncthingController.js
+++ b/gui/default/syncthing/core/syncthingController.js
@@ -2406,4 +2406,14 @@ angular.module('syncthing.core')
$scope.saveConfig();
}
};
+
+ $scope.abbreviatedError = function (addr) {
+ var status = $scope.system.lastDialStatus[addr];
+ if (!status || !status.error) {
+ return null;
+ }
+ var time = $filter('date')(status.when, "HH:mm:ss")
+ var err = status.error.replace(/.+: /, '');
+ return err + " (" + time + ")";
+ }
});
diff --git a/lib/api/api.go b/lib/api/api.go
index 5146f09a..eb4c415e 100644
--- a/lib/api/api.go
+++ b/lib/api/api.go
@@ -898,7 +898,8 @@ func (s *service) getSystemStatus(w http.ResponseWriter, r *http.Request) {
res["discoveryErrors"] = discoErrors
}
- res["connectionServiceStatus"] = s.connectionsService.Status()
+ res["connectionServiceStatus"] = s.connectionsService.ListenerStatus()
+ res["lastDialStatus"] = s.connectionsService.ConnectionStatus()
// cpuUsage.Rate() is in milliseconds per second, so dividing by ten
// gives us percent
res["cpuPercent"] = s.cpu.Rate() / 10 / float64(runtime.NumCPU())
diff --git a/lib/api/mocked_connections_test.go b/lib/api/mocked_connections_test.go
index 54aee73f..608a1264 100644
--- a/lib/api/mocked_connections_test.go
+++ b/lib/api/mocked_connections_test.go
@@ -6,9 +6,17 @@
package api
+import (
+ "github.com/syncthing/syncthing/lib/connections"
+)
+
type mockedConnections struct{}
-func (m *mockedConnections) Status() map[string]interface{} {
+func (m *mockedConnections) ListenerStatus() map[string]connections.ListenerStatusEntry {
+ return nil
+}
+
+func (m *mockedConnections) ConnectionStatus() map[string]connections.ConnectionStatusEntry {
return nil
}
diff --git a/lib/connections/service.go b/lib/connections/service.go
index f0a49c22..95547f2e 100644
--- a/lib/connections/service.go
+++ b/lib/connections/service.go
@@ -90,10 +90,22 @@ var tlsVersionNames = map[uint16]string{
// dialers. Successful connections are handed to the model.
type Service interface {
suture.Service
- Status() map[string]interface{}
+ ListenerStatus() map[string]ListenerStatusEntry
+ ConnectionStatus() map[string]ConnectionStatusEntry
NATType() string
}
+type ListenerStatusEntry struct {
+ Error *string `json:"error"`
+ LANAddresses []string `json:"lanAddresses"`
+ WANAddresses []string `json:"wanAddresses"`
+}
+
+type ConnectionStatusEntry struct {
+ When time.Time `json:"when"`
+ Error *string `json:"error"`
+}
+
type service struct {
*suture.Supervisor
cfg config.Wrapper
@@ -112,6 +124,9 @@ type service struct {
listeners map[string]genericListener
listenerTokens map[string]suture.ServiceToken
listenerSupervisor *suture.Supervisor
+
+ connectionStatusMut sync.RWMutex
+ connectionStatus map[string]ConnectionStatusEntry // address -> latest error/status
}
func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *tls.Config, discoverer discover.Finder,
@@ -151,6 +166,9 @@ func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *t
FailureBackoff: 600 * time.Second,
PassThroughPanics: true,
}),
+
+ connectionStatusMut: sync.NewRWMutex(),
+ connectionStatus: make(map[string]ConnectionStatusEntry),
}
cfg.Subscribe(service)
@@ -378,18 +396,23 @@ func (s *service) connect() {
uri, err := url.Parse(addr)
if err != nil {
+ s.setConnectionStatus(addr, err)
l.Infof("Parsing dialer address %s: %v", addr, err)
continue
}
if len(deviceCfg.AllowedNetworks) > 0 {
if !IsAllowedNetwork(uri.Host, deviceCfg.AllowedNetworks) {
+ s.setConnectionStatus(addr, errors.New("network disallowed"))
l.Debugln("Network for", uri, "is disallowed")
continue
}
}
dialerFactory, err := getDialerFactory(cfg, uri)
+ if err != nil {
+ s.setConnectionStatus(addr, err)
+ }
switch err {
case nil:
// all good
@@ -424,6 +447,7 @@ func (s *service) connect() {
}
dialTargets = append(dialTargets, dialTarget{
+ addr: addr,
dialer: dialer,
priority: priority,
deviceID: deviceID,
@@ -431,7 +455,7 @@ func (s *service) connect() {
})
}
- conn, ok := dialParallel(deviceCfg.DeviceID, dialTargets)
+ conn, ok := s.dialParallel(deviceCfg.DeviceID, dialTargets)
if ok {
s.conns <- conn
}
@@ -633,19 +657,19 @@ func (s *service) ExternalAddresses() []string {
return util.UniqueStrings(addrs)
}
-func (s *service) Status() map[string]interface{} {
+func (s *service) ListenerStatus() map[string]ListenerStatusEntry {
+ result := make(map[string]ListenerStatusEntry)
s.listenersMut.RLock()
- result := make(map[string]interface{})
for addr, listener := range s.listeners {
- status := make(map[string]interface{})
+ var status ListenerStatusEntry
- err := listener.Error()
- if err != nil {
- status["error"] = err.Error()
+ if err := listener.Error(); err != nil {
+ errStr := err.Error()
+ status.Error = &errStr
}
- status["lanAddresses"] = urlsToStrings(listener.LANAddresses())
- status["wanAddresses"] = urlsToStrings(listener.WANAddresses())
+ status.LANAddresses = urlsToStrings(listener.LANAddresses())
+ status.WANAddresses = urlsToStrings(listener.WANAddresses())
result[addr] = status
}
@@ -653,6 +677,28 @@ func (s *service) Status() map[string]interface{} {
return result
}
+func (s *service) ConnectionStatus() map[string]ConnectionStatusEntry {
+ result := make(map[string]ConnectionStatusEntry)
+ s.connectionStatusMut.RLock()
+ for k, v := range s.connectionStatus {
+ result[k] = v
+ }
+ s.connectionStatusMut.RUnlock()
+ return result
+}
+
+func (s *service) setConnectionStatus(address string, err error) {
+ status := ConnectionStatusEntry{When: time.Now().UTC().Truncate(time.Second)}
+ if err != nil {
+ errStr := err.Error()
+ status.Error = &errStr
+ }
+
+ s.connectionStatusMut.Lock()
+ s.connectionStatus[address] = status
+ s.connectionStatusMut.Unlock()
+}
+
func (s *service) NATType() string {
s.listenersMut.RLock()
defer s.listenersMut.RUnlock()
@@ -769,7 +815,7 @@ func IsAllowedNetwork(host string, allowed []string) bool {
return false
}
-func dialParallel(deviceID protocol.DeviceID, dialTargets []dialTarget) (internalConn, bool) {
+func (s *service) dialParallel(deviceID protocol.DeviceID, dialTargets []dialTarget) (internalConn, bool) {
// Group targets into buckets by priority
dialTargetBuckets := make(map[int][]dialTarget, len(dialTargets))
for _, tgt := range dialTargets {
@@ -793,6 +839,7 @@ func dialParallel(deviceID protocol.DeviceID, dialTargets []dialTarget) (interna
wg.Add(1)
go func(tgt dialTarget) {
conn, err := tgt.Dial()
+ s.setConnectionStatus(tgt.addr, err)
if err == nil {
res <- conn
}
diff --git a/lib/connections/structs.go b/lib/connections/structs.go
index dcd3002e..cdb14c08 100644
--- a/lib/connections/structs.go
+++ b/lib/connections/structs.go
@@ -195,6 +195,7 @@ func (o *onAddressesChangedNotifier) notifyAddressesChanged(l genericListener) {
}
type dialTarget struct {
+ addr string
dialer genericDialer
priority int
uri *url.URL