diff --git a/frontend/src/helpers/models.js b/frontend/src/helpers/models.js
index 6e1e52b..5c0d1d7 100644
--- a/frontend/src/helpers/models.js
+++ b/frontend/src/helpers/models.js
@@ -53,6 +53,7 @@ export function freshPeer() {
Identifier: "",
DisplayName: "",
UserIdentifier: "",
+ UserDisplayName: "",
InterfaceIdentifier: "",
Disabled: false,
ExpiresAt: null,
diff --git a/frontend/src/views/InterfaceView.vue b/frontend/src/views/InterfaceView.vue
index b28cf5f..ecd253e 100644
--- a/frontend/src/views/InterfaceView.vue
+++ b/frontend/src/views/InterfaceView.vue
@@ -400,7 +400,7 @@ onMounted(async () => {
{{peer.DisplayName}}{{ $filters.truncate(peer.Identifier, 10)}} |
- {{peer.UserIdentifier}} |
+ {{peer.UserIdentifier}} |
{{ ip }}
|
diff --git a/internal/app/api/v0/model/models_peer.go b/internal/app/api/v0/model/models_peer.go
index b0e2f7e..c7843c5 100644
--- a/internal/app/api/v0/model/models_peer.go
+++ b/internal/app/api/v0/model/models_peer.go
@@ -43,6 +43,7 @@ type Peer struct {
Identifier string `json:"Identifier" example:"super_nice_peer"` // peer unique identifier
DisplayName string `json:"DisplayName"` // a nice display name/ description for the peer
UserIdentifier string `json:"UserIdentifier"` // the owner
+ UserDisplayName string `json:"UserDisplayName"` // the owner display name
InterfaceIdentifier string `json:"InterfaceIdentifier"` // the interface id
Disabled bool `json:"Disabled"` // flag that specifies if the peer is enabled (up) or not (down)
DisabledReason string `json:"DisabledReason"` // the reason why the peer has been disabled
@@ -80,7 +81,7 @@ type Peer struct {
}
func NewPeer(src *domain.Peer) *Peer {
- return &Peer{
+ p := &Peer{
Identifier: string(src.Identifier),
DisplayName: src.DisplayName,
UserIdentifier: string(src.UserIdentifier),
@@ -111,6 +112,12 @@ func NewPeer(src *domain.Peer) *Peer {
PostDown: ConfigOptionFromDomain(src.Interface.PostDown),
Filename: src.GetConfigFileName(),
}
+
+ if src.User != nil {
+ p.UserDisplayName = src.User.DisplayName()
+ }
+
+ return p
}
func NewPeers(src []domain.Peer) []Peer {
diff --git a/internal/domain/peer.go b/internal/domain/peer.go
index c47f63e..2ea3254 100644
--- a/internal/domain/peer.go
+++ b/internal/domain/peer.go
@@ -1,12 +1,14 @@
package domain
import (
+ "errors"
"fmt"
"net"
"strings"
"time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+ "gorm.io/gorm"
"github.com/h44z/wg-portal/internal"
"github.com/h44z/wg-portal/internal/config"
@@ -44,6 +46,7 @@ type Peer struct {
DisplayName string // a nice display name/ description for the peer
Identifier PeerIdentifier `gorm:"primaryKey;column:identifier"` // peer unique identifier
UserIdentifier UserIdentifier `gorm:"index;column:user_identifier"` // the owner
+ User *User `gorm:"-"` // the owner user object; loaded automatically after fetch
InterfaceIdentifier InterfaceIdentifier `gorm:"index;column:interface_identifier"` // the interface id
Disabled *time.Time `gorm:"column:disabled"` // if this field is set, the peer is disabled
DisabledReason string // the reason why the peer has been disabled
@@ -348,3 +351,26 @@ type PeerCreationRequest struct {
UserIdentifiers []string
Prefix string
}
+
+// AfterFind is a GORM hook that automatically loads the associated User object
+// based on the UserIdentifier field. If the identifier is empty or no user is
+// found, the User field is set to nil.
+func (p *Peer) AfterFind(tx *gorm.DB) error {
+ if p == nil {
+ return nil
+ }
+ if p.UserIdentifier == "" {
+ p.User = nil
+ return nil
+ }
+ var u User
+ if err := tx.Where("identifier = ?", p.UserIdentifier).First(&u).Error; err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ p.User = nil
+ return nil
+ }
+ return err
+ }
+ p.User = &u
+ return nil
+}
diff --git a/internal/domain/user.go b/internal/domain/user.go
index 84b5345..ee5f371 100644
--- a/internal/domain/user.go
+++ b/internal/domain/user.go
@@ -185,6 +185,27 @@ func (u *User) CopyCalculatedAttributes(src *User) {
u.LinkedPeerCount = src.LinkedPeerCount
}
+// DisplayName returns the display name of the user.
+// The display name is the first and last name, or the email address of the user.
+// If none of these fields are set, the user identifier is returned.
+func (u *User) DisplayName() string {
+ var displayName string
+ switch {
+ case u.Firstname != "" && u.Lastname != "":
+ displayName = fmt.Sprintf("%s %s", u.Firstname, u.Lastname)
+ case u.Firstname != "":
+ displayName = u.Firstname
+ case u.Lastname != "":
+ displayName = u.Lastname
+ case u.Email != "":
+ displayName = u.Email
+ default:
+ displayName = string(u.Identifier)
+ }
+
+ return displayName
+}
+
// region webauthn
func (u *User) WebAuthnID() []byte {
@@ -209,19 +230,7 @@ func (u *User) WebAuthnName() string {
}
func (u *User) WebAuthnDisplayName() string {
- var userName string
- switch {
- case u.Firstname != "" && u.Lastname != "":
- userName = fmt.Sprintf("%s %s", u.Firstname, u.Lastname)
- case u.Firstname != "":
- userName = u.Firstname
- case u.Lastname != "":
- userName = u.Lastname
- default:
- userName = string(u.Identifier)
- }
-
- return userName
+ return u.DisplayName()
}
func (u *User) WebAuthnCredentials() []webauthn.Credential {