* lib/api, lib/connections, gui: Show connection error for disconnected devices (fixes #3345) This adds functionality in the connetions service to track the last error per address. That is in turn exposed in the /rest/system/status API method, as that is also where we already show the listener status from the connection service. The GUI uses this info where it lists addresses, showing errors (if any) in red underneath each address. I also slightly refactored the existing status method on the connection service to have a better name and return typed information. * ok * review * formatting * review
This commit is contained in:
committed by
Audrius Butkevicius
parent
1da9317a09
commit
2c866277a2
@@ -715,8 +715,14 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td ng-if="!connections[deviceCfg.deviceID].connected" class="text-right">
|
<td ng-if="!connections[deviceCfg.deviceID].connected" class="text-right">
|
||||||
<span ng-repeat="addr in deviceCfg.addresses"><span tooltip data-original-title="{{'Configured' | translate}}">{{addr}}</span><br></span>
|
<span ng-repeat="addr in deviceCfg.addresses">
|
||||||
<span ng-repeat="addr in discoveryCache[deviceCfg.deviceID].addresses"><span tooltip data-original-title="{{'Discovered' | translate}}">{{addr}}</span><br></span>
|
<span tooltip data-original-title="{{'Configured' | translate}}">{{addr}}</span><br>
|
||||||
|
<small ng-if="system.lastDialStatus[addr].error" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
|
||||||
|
</span>
|
||||||
|
<span ng-repeat="addr in discoveryCache[deviceCfg.deviceID].addresses">
|
||||||
|
<span tooltip data-original-title="{{'Discovered' | translate}}">{{addr}}</span><br>
|
||||||
|
<small ng-if="system.lastDialStatus[addr].error" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-if="connections[deviceCfg.deviceID].connected && connections[deviceCfg.deviceID].type.indexOf('Relay') > -1" tooltip data-original-title="Connections via relays might be rate limited by the relay">
|
<tr ng-if="connections[deviceCfg.deviceID].connected && connections[deviceCfg.deviceID].type.indexOf('Relay') > -1" tooltip data-original-title="Connections via relays might be rate limited by the relay">
|
||||||
|
|||||||
@@ -2406,4 +2406,14 @@ angular.module('syncthing.core')
|
|||||||
$scope.saveConfig();
|
$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 + ")";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -898,7 +898,8 @@ func (s *service) getSystemStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
res["discoveryErrors"] = discoErrors
|
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
|
// cpuUsage.Rate() is in milliseconds per second, so dividing by ten
|
||||||
// gives us percent
|
// gives us percent
|
||||||
res["cpuPercent"] = s.cpu.Rate() / 10 / float64(runtime.NumCPU())
|
res["cpuPercent"] = s.cpu.Rate() / 10 / float64(runtime.NumCPU())
|
||||||
|
|||||||
@@ -6,9 +6,17 @@
|
|||||||
|
|
||||||
package api
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/syncthing/syncthing/lib/connections"
|
||||||
|
)
|
||||||
|
|
||||||
type mockedConnections struct{}
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,10 +90,22 @@ var tlsVersionNames = map[uint16]string{
|
|||||||
// dialers. Successful connections are handed to the model.
|
// dialers. Successful connections are handed to the model.
|
||||||
type Service interface {
|
type Service interface {
|
||||||
suture.Service
|
suture.Service
|
||||||
Status() map[string]interface{}
|
ListenerStatus() map[string]ListenerStatusEntry
|
||||||
|
ConnectionStatus() map[string]ConnectionStatusEntry
|
||||||
NATType() string
|
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 {
|
type service struct {
|
||||||
*suture.Supervisor
|
*suture.Supervisor
|
||||||
cfg config.Wrapper
|
cfg config.Wrapper
|
||||||
@@ -112,6 +124,9 @@ type service struct {
|
|||||||
listeners map[string]genericListener
|
listeners map[string]genericListener
|
||||||
listenerTokens map[string]suture.ServiceToken
|
listenerTokens map[string]suture.ServiceToken
|
||||||
listenerSupervisor *suture.Supervisor
|
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,
|
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,
|
FailureBackoff: 600 * time.Second,
|
||||||
PassThroughPanics: true,
|
PassThroughPanics: true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
connectionStatusMut: sync.NewRWMutex(),
|
||||||
|
connectionStatus: make(map[string]ConnectionStatusEntry),
|
||||||
}
|
}
|
||||||
cfg.Subscribe(service)
|
cfg.Subscribe(service)
|
||||||
|
|
||||||
@@ -378,18 +396,23 @@ func (s *service) connect() {
|
|||||||
|
|
||||||
uri, err := url.Parse(addr)
|
uri, err := url.Parse(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.setConnectionStatus(addr, err)
|
||||||
l.Infof("Parsing dialer address %s: %v", addr, err)
|
l.Infof("Parsing dialer address %s: %v", addr, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(deviceCfg.AllowedNetworks) > 0 {
|
if len(deviceCfg.AllowedNetworks) > 0 {
|
||||||
if !IsAllowedNetwork(uri.Host, deviceCfg.AllowedNetworks) {
|
if !IsAllowedNetwork(uri.Host, deviceCfg.AllowedNetworks) {
|
||||||
|
s.setConnectionStatus(addr, errors.New("network disallowed"))
|
||||||
l.Debugln("Network for", uri, "is disallowed")
|
l.Debugln("Network for", uri, "is disallowed")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialerFactory, err := getDialerFactory(cfg, uri)
|
dialerFactory, err := getDialerFactory(cfg, uri)
|
||||||
|
if err != nil {
|
||||||
|
s.setConnectionStatus(addr, err)
|
||||||
|
}
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
// all good
|
// all good
|
||||||
@@ -424,6 +447,7 @@ func (s *service) connect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dialTargets = append(dialTargets, dialTarget{
|
dialTargets = append(dialTargets, dialTarget{
|
||||||
|
addr: addr,
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
priority: priority,
|
priority: priority,
|
||||||
deviceID: deviceID,
|
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 {
|
if ok {
|
||||||
s.conns <- conn
|
s.conns <- conn
|
||||||
}
|
}
|
||||||
@@ -633,19 +657,19 @@ func (s *service) ExternalAddresses() []string {
|
|||||||
return util.UniqueStrings(addrs)
|
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()
|
s.listenersMut.RLock()
|
||||||
result := make(map[string]interface{})
|
|
||||||
for addr, listener := range s.listeners {
|
for addr, listener := range s.listeners {
|
||||||
status := make(map[string]interface{})
|
var status ListenerStatusEntry
|
||||||
|
|
||||||
err := listener.Error()
|
if err := listener.Error(); err != nil {
|
||||||
if err != nil {
|
errStr := err.Error()
|
||||||
status["error"] = err.Error()
|
status.Error = &errStr
|
||||||
}
|
}
|
||||||
|
|
||||||
status["lanAddresses"] = urlsToStrings(listener.LANAddresses())
|
status.LANAddresses = urlsToStrings(listener.LANAddresses())
|
||||||
status["wanAddresses"] = urlsToStrings(listener.WANAddresses())
|
status.WANAddresses = urlsToStrings(listener.WANAddresses())
|
||||||
|
|
||||||
result[addr] = status
|
result[addr] = status
|
||||||
}
|
}
|
||||||
@@ -653,6 +677,28 @@ func (s *service) Status() map[string]interface{} {
|
|||||||
return result
|
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 {
|
func (s *service) NATType() string {
|
||||||
s.listenersMut.RLock()
|
s.listenersMut.RLock()
|
||||||
defer s.listenersMut.RUnlock()
|
defer s.listenersMut.RUnlock()
|
||||||
@@ -769,7 +815,7 @@ func IsAllowedNetwork(host string, allowed []string) bool {
|
|||||||
return false
|
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
|
// Group targets into buckets by priority
|
||||||
dialTargetBuckets := make(map[int][]dialTarget, len(dialTargets))
|
dialTargetBuckets := make(map[int][]dialTarget, len(dialTargets))
|
||||||
for _, tgt := range dialTargets {
|
for _, tgt := range dialTargets {
|
||||||
@@ -793,6 +839,7 @@ func dialParallel(deviceID protocol.DeviceID, dialTargets []dialTarget) (interna
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(tgt dialTarget) {
|
go func(tgt dialTarget) {
|
||||||
conn, err := tgt.Dial()
|
conn, err := tgt.Dial()
|
||||||
|
s.setConnectionStatus(tgt.addr, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
res <- conn
|
res <- conn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ func (o *onAddressesChangedNotifier) notifyAddressesChanged(l genericListener) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type dialTarget struct {
|
type dialTarget struct {
|
||||||
|
addr string
|
||||||
dialer genericDialer
|
dialer genericDialer
|
||||||
priority int
|
priority int
|
||||||
uri *url.URL
|
uri *url.URL
|
||||||
|
|||||||
Reference in New Issue
Block a user