diff --git a/internal/devices/devices.go b/internal/devices/devices.go index f58e4c3..6de532c 100644 --- a/internal/devices/devices.go +++ b/internal/devices/devices.go @@ -142,6 +142,10 @@ func (d *DeviceManager) DeleteDevice(user string, name string) error { return nil } +func (d *DeviceManager) GetByPublicKey(publicKey string) (*storage.Device, error) { + return d.storage.GetByPublicKey(publicKey) +} + var nextIPLock = sync.Mutex{} func (d *DeviceManager) nextClientAddress() (string, error) { diff --git a/internal/devices/metadata.go b/internal/devices/metadata.go index 35b3301..b525eeb 100644 --- a/internal/devices/metadata.go +++ b/internal/devices/metadata.go @@ -16,29 +16,28 @@ func metadataLoop(d *DeviceManager) { func syncMetrics(d *DeviceManager) { logrus.Debug("metadata sync executing") - devices, err := d.ListAllDevices() - if err != nil { - logrus.Warn(errors.Wrap(err, "failed to list devices - metrics cannot be recorded")) - return - } + peers, err := d.wg.ListPeers() if err != nil { logrus.Warn(errors.Wrap(err, "failed to list peers - metrics cannot be recorded")) return } + for _, peer := range peers { - for _, device := range devices { - if peer.PublicKey.String() == device.PublicKey { + // if the peer is connected we can update their metrics + // importantly, we'll ignore peers that we know about + // but aren't connected at the moment. + // they may actually be connected to another replica. + if peer.Endpoint != nil { + if device, err := d.GetByPublicKey(peer.PublicKey.String()); err == nil { + device.Endpoint = peer.Endpoint.IP.String() device.ReceiveBytes = peer.ReceiveBytes device.TransmitBytes = peer.TransmitBytes if !peer.LastHandshakeTime.IsZero() { device.LastHandshakeTime = &peer.LastHandshakeTime } - if peer.Endpoint != nil { - device.Endpoint = peer.Endpoint.IP.String() - } if err := d.SaveDevice(device); err != nil { - logrus.Debug(errors.Wrap(err, "failed to update device metadata")) + logrus.Error(errors.Wrap(err, "failed to save device during metadata sync")) } } } diff --git a/internal/storage/contracts.go b/internal/storage/contracts.go index c9603c1..bef9566 100644 --- a/internal/storage/contracts.go +++ b/internal/storage/contracts.go @@ -14,6 +14,7 @@ type Storage interface { Save(device *Device) error List(owner string) ([]*Device, error) Get(owner string, name string) (*Device, error) + GetByPublicKey(publicKey string) (*Device, error) Delete(device *Device) error Close() error Open() error @@ -33,7 +34,7 @@ type Device struct { OwnerEmail string `json:"owner_email"` OwnerProvider string `json:"owner_provider"` Name string `json:"name" gorm:"type:varchar(100);unique_index:key;primary_key"` - PublicKey string `json:"public_key"` + PublicKey string `json:"public_key" gorm:"unique_index"` Address string `json:"address"` CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` diff --git a/internal/storage/inmemory.go b/internal/storage/inmemory.go index a0beea5..2416b02 100644 --- a/internal/storage/inmemory.go +++ b/internal/storage/inmemory.go @@ -57,6 +57,19 @@ func (s *InMemoryStorage) Get(owner string, name string) (*Device, error) { return device, nil } +func (s *InMemoryStorage) GetByPublicKey(publicKey string) (*Device, error) { + devices, err := s.List("") + if err != nil { + return nil, err + } + for _, device := range devices { + if device.PublicKey == publicKey { + return device, nil + } + } + return nil, errors.New("device doesn't exist") +} + func (s *InMemoryStorage) Delete(device *Device) error { delete(s.db, key(device)) s.emitDelete(device) diff --git a/internal/storage/sql.go b/internal/storage/sql.go index f7971f5..d34710e 100644 --- a/internal/storage/sql.go +++ b/internal/storage/sql.go @@ -170,6 +170,14 @@ func (s *SQLStorage) Get(owner string, name string) (*Device, error) { return device, nil } +func (s *SQLStorage) GetByPublicKey(publicKey string) (*Device, error) { + device := &Device{} + if err := s.db.Where("public_key = ?", publicKey).First(&device).Error; err != nil { + return nil, errors.Wrapf(err, "failed to read device") + } + return device, nil +} + func (s *SQLStorage) Delete(device *Device) error { if err := s.db.Delete(&device).Error; err != nil { return errors.Wrap(err, "failed to delete device file") diff --git a/website/src/components/DeviceListItem.tsx b/website/src/components/DeviceListItem.tsx index d209963..d051dc1 100644 --- a/website/src/components/DeviceListItem.tsx +++ b/website/src/components/DeviceListItem.tsx @@ -73,16 +73,14 @@ export class DeviceListItem extends React.Component { )} {AppState.info?.metadataEnabled && !device.connected && ( - <> - - Disconnected - - - Last Seen - {lastSeen(device.lastHandshakeTime)} - - + + Disconnected + )} + + Last Seen + {lastSeen(device.lastHandshakeTime)} + Public key diff --git a/website/src/components/Devices.tsx b/website/src/components/Devices.tsx index 1163929..7d89d67 100644 --- a/website/src/components/Devices.tsx +++ b/website/src/components/Devices.tsx @@ -10,7 +10,7 @@ import { AddDevice } from './AddDevice'; @observer export class Devices extends React.Component { @observable - devices = autorefresh(5, async () => { + devices = autorefresh(30, async () => { return (await grpc.devices.listDevices({})).items; }); diff --git a/website/src/components/GetConnected.tsx b/website/src/components/GetConnected.tsx index 190b7a0..c974948 100644 --- a/website/src/components/GetConnected.tsx +++ b/website/src/components/GetConnected.tsx @@ -67,11 +67,11 @@ export class GetConnected extends React.Component { - diff --git a/website/src/pages/admin/AllDevices.tsx b/website/src/pages/admin/AllDevices.tsx index 1b3f9c5..29b8cd0 100644 --- a/website/src/pages/admin/AllDevices.tsx +++ b/website/src/pages/admin/AllDevices.tsx @@ -6,11 +6,13 @@ import TableContainer from '@material-ui/core/TableContainer'; import TableHead from '@material-ui/core/TableHead'; import TableRow from '@material-ui/core/TableRow'; import Button from '@material-ui/core/Button'; +import Typography from '@material-ui/core/Typography'; import { observer } from 'mobx-react'; import { grpc } from '../../Api'; import { lastSeen, lazy } from '../../Util'; import { Device } from '../../sdk/devices_pb'; import { confirm } from '../../components/Present'; +import { AppState } from '../../AppState'; @observer export class AllDevices extends React.Component { @@ -42,38 +44,52 @@ export class AllDevices extends React.Component { const showProviderCol = rows.length >= 2 && rows.some((r) => r.ownerProvider !== rows[0].ownerProvider); return ( - - - - - Owner - {showProviderCol && Auth Provider} - Device - Connected - Last Seen - Actions - - - - {rows.map((row, i) => ( - - - {row.ownerName || row.ownerEmail || row.owner} - - {showProviderCol && {row.ownerProvider}} - {row.name} - {row.connected ? 'yes' : 'no'} - {lastSeen(row.lastHandshakeTime)} - - - +
+ + Devices + + +
+ + + Owner + {showProviderCol && Auth Provider} + Device + Connected + Last Seen + Actions - ))} - -
-
+ + + {rows.map((row, i) => ( + + + {row.ownerName || row.ownerEmail || row.owner} + + {showProviderCol && {row.ownerProvider}} + {row.name} + {row.connected ? 'yes' : 'no'} + {lastSeen(row.lastHandshakeTime)} + + + + + ))} + + + + + Server Info + + +
+          {JSON.stringify(AppState.info, null, 2)}
+
+          
+
+ ); } }