add user's display-name to peer view (#525) (#534)
Some checks failed
Docker / Build and Push (push) Has been cancelled
Docker / release (push) Has been cancelled
github-pages / deploy (push) Has been cancelled

This commit is contained in:
h44z
2025-09-21 13:02:12 +02:00
committed by GitHub
parent 80693400be
commit 61bf349813
5 changed files with 58 additions and 15 deletions

View File

@@ -53,6 +53,7 @@ export function freshPeer() {
Identifier: "", Identifier: "",
DisplayName: "", DisplayName: "",
UserIdentifier: "", UserIdentifier: "",
UserDisplayName: "",
InterfaceIdentifier: "", InterfaceIdentifier: "",
Disabled: false, Disabled: false,
ExpiresAt: null, ExpiresAt: null,

View File

@@ -400,7 +400,7 @@ onMounted(async () => {
<span v-if="!peer.Disabled && peer.ExpiresAt" class="text-warning" :title="$t('interfaces.peer-expiring') + ' ' + peer.ExpiresAt"><i class="fas fa-hourglass-end expiring-peer"></i></span> <span v-if="!peer.Disabled && peer.ExpiresAt" class="text-warning" :title="$t('interfaces.peer-expiring') + ' ' + peer.ExpiresAt"><i class="fas fa-hourglass-end expiring-peer"></i></span>
</td> </td>
<td><span v-if="peer.DisplayName" :title="peer.Identifier">{{peer.DisplayName}}</span><span v-else :title="peer.Identifier">{{ $filters.truncate(peer.Identifier, 10)}}</span></td> <td><span v-if="peer.DisplayName" :title="peer.Identifier">{{peer.DisplayName}}</span><span v-else :title="peer.Identifier">{{ $filters.truncate(peer.Identifier, 10)}}</span></td>
<td>{{peer.UserIdentifier}}</td> <td><span :title="peer.UserDisplayName">{{peer.UserIdentifier}}</span></td>
<td> <td>
<span v-for="ip in peer.Addresses" :key="ip" class="badge bg-light me-1">{{ ip }}</span> <span v-for="ip in peer.Addresses" :key="ip" class="badge bg-light me-1">{{ ip }}</span>
</td> </td>

View File

@@ -43,6 +43,7 @@ type Peer struct {
Identifier string `json:"Identifier" example:"super_nice_peer"` // peer unique identifier Identifier string `json:"Identifier" example:"super_nice_peer"` // peer unique identifier
DisplayName string `json:"DisplayName"` // a nice display name/ description for the peer DisplayName string `json:"DisplayName"` // a nice display name/ description for the peer
UserIdentifier string `json:"UserIdentifier"` // the owner UserIdentifier string `json:"UserIdentifier"` // the owner
UserDisplayName string `json:"UserDisplayName"` // the owner display name
InterfaceIdentifier string `json:"InterfaceIdentifier"` // the interface id InterfaceIdentifier string `json:"InterfaceIdentifier"` // the interface id
Disabled bool `json:"Disabled"` // flag that specifies if the peer is enabled (up) or not (down) 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 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 { func NewPeer(src *domain.Peer) *Peer {
return &Peer{ p := &Peer{
Identifier: string(src.Identifier), Identifier: string(src.Identifier),
DisplayName: src.DisplayName, DisplayName: src.DisplayName,
UserIdentifier: string(src.UserIdentifier), UserIdentifier: string(src.UserIdentifier),
@@ -111,6 +112,12 @@ func NewPeer(src *domain.Peer) *Peer {
PostDown: ConfigOptionFromDomain(src.Interface.PostDown), PostDown: ConfigOptionFromDomain(src.Interface.PostDown),
Filename: src.GetConfigFileName(), Filename: src.GetConfigFileName(),
} }
if src.User != nil {
p.UserDisplayName = src.User.DisplayName()
}
return p
} }
func NewPeers(src []domain.Peer) []Peer { func NewPeers(src []domain.Peer) []Peer {

View File

@@ -1,12 +1,14 @@
package domain package domain
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"strings" "strings"
"time" "time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"gorm.io/gorm"
"github.com/h44z/wg-portal/internal" "github.com/h44z/wg-portal/internal"
"github.com/h44z/wg-portal/internal/config" "github.com/h44z/wg-portal/internal/config"
@@ -44,6 +46,7 @@ type Peer struct {
DisplayName string // a nice display name/ description for the peer DisplayName string // a nice display name/ description for the peer
Identifier PeerIdentifier `gorm:"primaryKey;column:identifier"` // peer unique identifier Identifier PeerIdentifier `gorm:"primaryKey;column:identifier"` // peer unique identifier
UserIdentifier UserIdentifier `gorm:"index;column:user_identifier"` // the owner 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 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 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 DisabledReason string // the reason why the peer has been disabled
@@ -348,3 +351,26 @@ type PeerCreationRequest struct {
UserIdentifiers []string UserIdentifiers []string
Prefix 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
}

View File

@@ -185,6 +185,27 @@ func (u *User) CopyCalculatedAttributes(src *User) {
u.LinkedPeerCount = src.LinkedPeerCount 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 // region webauthn
func (u *User) WebAuthnID() []byte { func (u *User) WebAuthnID() []byte {
@@ -209,19 +230,7 @@ func (u *User) WebAuthnName() string {
} }
func (u *User) WebAuthnDisplayName() string { func (u *User) WebAuthnDisplayName() string {
var userName string return u.DisplayName()
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
} }
func (u *User) WebAuthnCredentials() []webauthn.Credential { func (u *User) WebAuthnCredentials() []webauthn.Credential {