mirror of
https://github.com/h44z/wg-portal.git
synced 2025-08-10 07:22:24 +00:00
stats display, expiry date field
This commit is contained in:
parent
8e0c59bb52
commit
2235c2a0d7
@ -172,7 +172,7 @@ function handleChangeAllowedIPs(tags) {
|
||||
}
|
||||
})
|
||||
if(validInput) {
|
||||
formData.value.AllowedIPs = tags
|
||||
formData.value.AllowedIPs.Value = tags
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,9 +340,17 @@ async function del() {
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend class="mt-4">State</legend>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" v-model="formData.Disabled">
|
||||
<label class="form-check-label" >Disabled</label>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" v-model="formData.Disabled">
|
||||
<label class="form-check-label" >Disabled</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label class="form-label">{{ $t('modals.peeredit.expiresat') }}</label>
|
||||
<input type="date" pattern="\d{4}-\d{2}-\d{2}" class="form-control" min="2023-01-01" v-model="formData.ExpiresAt">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</template>
|
||||
|
@ -4,7 +4,7 @@ import {peerStore} from "@/stores/peers";
|
||||
import {interfaceStore} from "@/stores/interfaces";
|
||||
import {computed, ref, watch} from "vue";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import { freshInterface, freshPeer } from '@/helpers/models';
|
||||
import {freshInterface, freshPeer, freshStats} from '@/helpers/models';
|
||||
import Prism from "vue-prism-component";
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
|
||||
@ -36,6 +36,16 @@ const selectedPeer = computed(() => {
|
||||
return p
|
||||
})
|
||||
|
||||
const selectedStats = computed(() => {
|
||||
let s = peers.Statistics(props.peerId)
|
||||
|
||||
if (!s) {
|
||||
s = freshStats() // dummy peer to avoid 'undefined' exceptions
|
||||
}
|
||||
|
||||
return s
|
||||
})
|
||||
|
||||
const selectedInterface = computed(() => {
|
||||
let i = interfaces.GetSelected;
|
||||
|
||||
@ -105,14 +115,14 @@ function email() {
|
||||
<template>
|
||||
<Modal :title="title" :visible="visible" @close="close">
|
||||
<template #default>
|
||||
<div class="accordion">
|
||||
<div class="accordion" id="peerInformation">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDetails" aria-expanded="true" aria-controls="collapseDetails">
|
||||
Peer Information
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample" style="">
|
||||
<div id="collapseDetails" class="accordion-collapse collapse show" aria-labelledby="headingDetails" data-bs-parent="#peerInformation" style="">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
@ -122,9 +132,8 @@ function email() {
|
||||
<li>IP Addresses: <span v-for="ip in selectedPeer.Addresses" :key="ip" class="badge rounded-pill bg-light">{{ ip }}</span></li>
|
||||
<li>Linked User: {{ selectedPeer.UserIdentifier }}</li>
|
||||
<li>Notes: {{ selectedPeer.Notes }}</li>
|
||||
<li>Expires At: {{ selectedPeer.ExpiresAt }}</li>
|
||||
</ul>
|
||||
<h4>Traffic</h4>
|
||||
<p><i class="fas fa-long-arrow-alt-down"></i> 1.5 MB / <i class="fas fa-long-arrow-alt-up"></i> 3.9 MB</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<img class="config-qr-img" :src="peers.ConfigQrUrl(props.peerId)" loading="lazy" alt="Configuration QR Code">
|
||||
@ -133,19 +142,42 @@ function email() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingStatus">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseStatus" aria-expanded="false" aria-controls="collapseStatus">
|
||||
Current Status
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseStatus" class="accordion-collapse collapse" aria-labelledby="headingStatus" data-bs-parent="#peerInformation" style="">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>Traffic</h4>
|
||||
<p><i class="fas fa-long-arrow-alt-down"></i> {{ selectedStats.BytesReceived }} Bytes / <i class="fas fa-long-arrow-alt-up"></i> {{ selectedStats.BytesTransmitted }} Bytes</p>
|
||||
<h4>Connection Stats</h4>
|
||||
<ul>
|
||||
<li>Pingable: {{ selectedStats.IsPingable }}</li>
|
||||
<li>Last Handshake: {{ selectedStats.LastHandshake }}</li>
|
||||
<li>Connected Since: {{ selectedStats.LastSessionStart }}</li>
|
||||
<li>Endpoint: {{ selectedStats.EndpointAddress }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedInterface.Mode==='server'" class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingTwo">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
||||
<h2 class="accordion-header" id="headingConfig">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseConfig" aria-expanded="false" aria-controls="collapseConfig">
|
||||
Peer Configuration
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#accordionExample" style="">
|
||||
<div id="collapseConfig" class="accordion-collapse collapse" aria-labelledby="headingConfig" data-bs-parent="#peerInformation" style="">
|
||||
<div class="accordion-body">
|
||||
<Prism language="ini" :code="configString"></Prism>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
|
@ -117,4 +117,17 @@ export function freshPeer() {
|
||||
Overridable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function freshStats() {
|
||||
return {
|
||||
IsConnected: false,
|
||||
IsPingable: false,
|
||||
LastHandshake: null,
|
||||
LastPing: null,
|
||||
LastSessionStart: null,
|
||||
BytesTransmitted: 0,
|
||||
BytesReceived: 0,
|
||||
EndpointAddress: ""
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
||||
import {apiWrapper} from "../helpers/fetch-wrapper";
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
import {interfaceStore} from "./interfaces";
|
||||
import { freshPeer } from '@/helpers/models';
|
||||
import {freshPeer, freshStats} from '@/helpers/models';
|
||||
import { base64_url_encode } from '@/helpers/encoding';
|
||||
|
||||
const baseUrl = `/peer`
|
||||
@ -11,6 +11,8 @@ export const peerStore = defineStore({
|
||||
id: 'peers',
|
||||
state: () => ({
|
||||
peers: [],
|
||||
stats: {},
|
||||
statsEnabled: false,
|
||||
peer: freshPeer(),
|
||||
prepared: freshPeer(),
|
||||
configuration: "",
|
||||
@ -24,6 +26,7 @@ export const peerStore = defineStore({
|
||||
Find: (state) => {
|
||||
return (id) => state.peers.find((p) => p.Identifier === id)
|
||||
},
|
||||
|
||||
Count: (state) => state.peers.length,
|
||||
Prepared: (state) => {console.log("STATE:", state.prepared); return state.prepared},
|
||||
FilteredCount: (state) => state.Filtered.length,
|
||||
@ -46,6 +49,11 @@ export const peerStore = defineStore({
|
||||
hasNextPage: (state) => state.pageOffset < (state.FilteredCount - state.pageSize),
|
||||
hasPrevPage: (state) => state.pageOffset > 0,
|
||||
currentPage: (state) => (state.pageOffset / state.pageSize)+1,
|
||||
Statistics: (state) => {
|
||||
return (id) => state.statsEnabled && (id in state.stats) ? state.stats[id] : freshStats()
|
||||
},
|
||||
hasStatistics: (state) => state.statsEnabled,
|
||||
|
||||
},
|
||||
actions: {
|
||||
afterPageSizeChange() {
|
||||
@ -90,6 +98,14 @@ export const peerStore = defineStore({
|
||||
setPeerConfig(config) {
|
||||
this.configuration = config;
|
||||
},
|
||||
setStats(statsResponse) {
|
||||
if (!statsResponse) {
|
||||
this.stats = {}
|
||||
this.statsEnabled = false
|
||||
}
|
||||
this.stats = statsResponse.Stats
|
||||
this.statsEnabled = statsResponse.Enabled
|
||||
},
|
||||
async PreparePeer(interfaceId) {
|
||||
return apiWrapper.get(`${baseUrl}/iface/${base64_url_encode(interfaceId)}/prepare`)
|
||||
.then(this.setPreparedPeer)
|
||||
@ -143,6 +159,27 @@ export const peerStore = defineStore({
|
||||
})
|
||||
})
|
||||
},
|
||||
async LoadStats(interfaceId) {
|
||||
// if no interfaceId is given, use the currently selected interface
|
||||
if (!interfaceId) {
|
||||
interfaceId = interfaceStore().GetSelected.Identifier
|
||||
if (!interfaceId) {
|
||||
return // no interface, nothing to load
|
||||
}
|
||||
}
|
||||
this.fetching = true
|
||||
|
||||
return apiWrapper.get(`${baseUrl}/iface/${base64_url_encode(interfaceId)}/stats`)
|
||||
.then(this.setStats)
|
||||
.catch(error => {
|
||||
this.setStats(undefined)
|
||||
console.log("Failed to load peer stats: ", error)
|
||||
notify({
|
||||
title: "Backend Connection Failure",
|
||||
text: "Failed to load peer stats!",
|
||||
})
|
||||
})
|
||||
},
|
||||
async DeletePeer(id) {
|
||||
this.fetching = true
|
||||
return apiWrapper.delete(`${baseUrl}/${base64_url_encode(id)}`)
|
||||
|
@ -46,7 +46,8 @@ async function download() {
|
||||
|
||||
onMounted(async () => {
|
||||
await interfaces.LoadInterfaces()
|
||||
await peers.LoadPeers()
|
||||
await peers.LoadPeers(undefined) // use default interface
|
||||
await peers.LoadStats(undefined) // use default interface
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -294,7 +295,7 @@ onMounted(async () => {
|
||||
<th scope="col">{{ $t('interfaces.tableHeadings[1]') }}</th>
|
||||
<th scope="col">{{ $t('interfaces.tableHeadings[2]') }}</th>
|
||||
<th v-if="interfaces.GetSelected.Mode==='client'" scope="col">{{ $t('interfaces.tableHeadings[3]') }}</th>
|
||||
<th scope="col">{{ $t('interfaces.tableHeadings[4]') }}</th>
|
||||
<th v-if="peers.hasStatistics" scope="col">{{ $t('interfaces.tableHeadings[4]') }}</th>
|
||||
<th scope="col"></th><!-- Actions -->
|
||||
</tr>
|
||||
</thead>
|
||||
@ -309,7 +310,14 @@ onMounted(async () => {
|
||||
<span v-for="ip in peer.Addresses" :key="ip" class="badge bg-light me-1">{{ ip }}</span>
|
||||
</td>
|
||||
<td v-if="interfaces.GetSelected.Mode==='client'">{{peer.Endpoint.Value}}</td>
|
||||
<td>{{peer.LastConnected}}</td>
|
||||
<td v-if="peers.hasStatistics">
|
||||
<div v-if="peers.Statistics(peer.Identifier).IsConnected">
|
||||
<span class="badge rounded-pill bg-success"><i class="fa-solid fa-link"></i></span> <span :title="peers.Statistics(peer.Identifier).LastHandshake">Connected</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span class="badge rounded-pill bg-light"><i class="fa-solid fa-link-slash"></i></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="#" title="Show peer" @click.prevent="viewedPeerId=peer.Identifier"><i class="fas fa-eye me-2"></i></a>
|
||||
<a href="#" title="Edit peer" @click.prevent="editPeerId=peer.Identifier"><i class="fas fa-cog"></i></a>
|
||||
|
@ -230,6 +230,21 @@ func (r *SqlRepo) GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceI
|
||||
return in, peers, nil
|
||||
}
|
||||
|
||||
func (r *SqlRepo) GetPeersStats(ctx context.Context, ids ...domain.PeerIdentifier) ([]domain.PeerStatus, error) {
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var stats []domain.PeerStatus
|
||||
|
||||
err := r.db.WithContext(ctx).Where("identifier IN ?", ids).Find(&stats).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (r *SqlRepo) GetAllInterfaces(ctx context.Context) ([]domain.Interface, error) {
|
||||
var interfaces []domain.Interface
|
||||
|
||||
|
@ -22,6 +22,7 @@ func (e peerEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti
|
||||
apiGroup := g.Group("/peer", e.authenticator.LoggedIn())
|
||||
|
||||
apiGroup.GET("/iface/:iface/all", e.handleAllGet())
|
||||
apiGroup.GET("/iface/:iface/stats", e.handleStatsGet())
|
||||
apiGroup.GET("/iface/:iface/prepare", e.handlePrepareGet())
|
||||
apiGroup.POST("/iface/:iface/new", e.handleCreatePost())
|
||||
apiGroup.POST("/iface/:iface/multiplenew", e.handleCreateMultiplePost())
|
||||
@ -404,3 +405,34 @@ func (e peerEndpoint) handleEmailPost() gin.HandlerFunc {
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// handleStatsGet returns a gorm handler function.
|
||||
//
|
||||
// @ID peers_handleStatsGet
|
||||
// @Tags Peer
|
||||
// @Summary Get peer stats for the given interface.
|
||||
// @Produce json
|
||||
// @Param iface path string true "The interface identifier"
|
||||
// @Success 200 {object} model.PeerStats
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /peer/iface/{iface}/stats [get]
|
||||
func (e peerEndpoint) handleStatsGet() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := domain.SetUserInfoFromGin(c)
|
||||
|
||||
interfaceId := Base64UrlDecode(c.Param("iface"))
|
||||
if interfaceId == "" {
|
||||
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing iface parameter"})
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := e.app.GetPeerStats(ctx, domain.InterfaceIdentifier(interfaceId))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, model.NewPeerStats(true, stats))
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,38 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const ExpiryDateTimeLayout = "\"2006-01-02\""
|
||||
|
||||
type ExpiryDate struct {
|
||||
*time.Time
|
||||
}
|
||||
|
||||
// UnmarshalJSON will unmarshal using 2006-01-02 layout
|
||||
func (d *ExpiryDate) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 0 || string(b) == "null" {
|
||||
return nil
|
||||
}
|
||||
parsed, err := time.Parse(ExpiryDateTimeLayout, string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !parsed.IsZero() {
|
||||
d.Time = &parsed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON will marshal using 2006-01-02 layout
|
||||
func (d *ExpiryDate) MarshalJSON() ([]byte, error) {
|
||||
if d == nil || d.Time == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
|
||||
s := d.Format(ExpiryDateTimeLayout)
|
||||
return []byte(s), nil
|
||||
}
|
||||
|
||||
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
|
||||
@ -13,7 +45,7 @@ type Peer struct {
|
||||
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
|
||||
ExpiresAt *time.Time `json:"ExpiresAt"` // expiry dates for peers
|
||||
ExpiresAt ExpiryDate `json:"ExpiresAt,omitempty"` // expiry dates for peers
|
||||
Notes string `json:"Notes"` // a note field for peers
|
||||
|
||||
Endpoint StringConfigOption `json:"Endpoint"` // the endpoint address
|
||||
@ -50,7 +82,7 @@ func NewPeer(src *domain.Peer) *Peer {
|
||||
InterfaceIdentifier: string(src.InterfaceIdentifier),
|
||||
Disabled: src.IsDisabled(),
|
||||
DisabledReason: src.DisabledReason,
|
||||
ExpiresAt: src.ExpiresAt,
|
||||
ExpiresAt: ExpiryDate{src.ExpiresAt},
|
||||
Notes: src.Notes,
|
||||
Endpoint: StringConfigOptionFromDomain(src.Endpoint),
|
||||
EndpointPublicKey: StringConfigOptionFromDomain(src.EndpointPublicKey),
|
||||
@ -103,7 +135,7 @@ func NewDomainPeer(src *Peer) *domain.Peer {
|
||||
InterfaceIdentifier: domain.InterfaceIdentifier(src.InterfaceIdentifier),
|
||||
Disabled: nil, // set below
|
||||
DisabledReason: src.DisabledReason,
|
||||
ExpiresAt: src.ExpiresAt,
|
||||
ExpiresAt: src.ExpiresAt.Time,
|
||||
Notes: src.Notes,
|
||||
Interface: domain.PeerInterfaceConfig{
|
||||
KeyPair: domain.KeyPair{
|
||||
@ -148,3 +180,45 @@ type PeerMailRequest struct {
|
||||
Identifiers []string `json:"Identifiers"`
|
||||
LinkOnly bool `json:"LinkOnly"`
|
||||
}
|
||||
|
||||
type PeerStats struct {
|
||||
Enabled bool `json:"Enabled" example:"true"` // peer stats tracking enabled
|
||||
|
||||
Stats map[string]PeerStatData `json:"Stats"` // stats, map key = Peer identifier
|
||||
}
|
||||
|
||||
func NewPeerStats(enabled bool, src []domain.PeerStatus) *PeerStats {
|
||||
stats := make(map[string]PeerStatData, len(src))
|
||||
|
||||
for _, srcStat := range src {
|
||||
stats[string(srcStat.PeerId)] = PeerStatData{
|
||||
IsConnected: srcStat.IsConnected(),
|
||||
IsPingable: srcStat.IsPingable,
|
||||
LastPing: srcStat.LastPing,
|
||||
BytesReceived: srcStat.BytesReceived,
|
||||
BytesTransmitted: srcStat.BytesTransmitted,
|
||||
LastHandshake: srcStat.LastHandshake,
|
||||
EndpointAddress: srcStat.Endpoint,
|
||||
LastSessionStart: srcStat.LastSessionStart,
|
||||
}
|
||||
}
|
||||
|
||||
return &PeerStats{
|
||||
Enabled: enabled,
|
||||
Stats: stats,
|
||||
}
|
||||
}
|
||||
|
||||
type PeerStatData struct {
|
||||
IsConnected bool `json:"IsConnected"`
|
||||
|
||||
IsPingable bool `json:"IsPingable"`
|
||||
LastPing *time.Time `json:"LastPing"`
|
||||
|
||||
BytesReceived uint64 `json:"BytesReceived"`
|
||||
BytesTransmitted uint64 `json:"BytesTransmitted"`
|
||||
|
||||
LastHandshake *time.Time `json:"LastHandshake"`
|
||||
EndpointAddress string `json:"EndpointAddress"`
|
||||
LastSessionStart *time.Time `json:"LastSessionStart"`
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ type WireGuardManager interface {
|
||||
RestoreInterfaceState(ctx context.Context, updateDbOnError bool, filter ...domain.InterfaceIdentifier) error
|
||||
CreateDefaultPeer(ctx context.Context, user *domain.User) error
|
||||
GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error)
|
||||
GetPeerStats(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.PeerStatus, error)
|
||||
GetAllInterfaces(ctx context.Context) ([]domain.Interface, error)
|
||||
GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error)
|
||||
PrepareInterface(ctx context.Context) (*domain.Interface, error)
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
type InterfaceAndPeerDatabaseRepo interface {
|
||||
GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, error)
|
||||
GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error)
|
||||
GetPeersStats(ctx context.Context, ids ...domain.PeerIdentifier) ([]domain.PeerStatus, error)
|
||||
GetAllInterfaces(ctx context.Context) ([]domain.Interface, error)
|
||||
FindInterfaces(ctx context.Context, search string) ([]domain.Interface, error)
|
||||
GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIdentifier][]domain.Cidr, error)
|
||||
|
@ -137,8 +137,8 @@ func (c *StatisticsCollector) collectPeerData(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func getSessionStartTime(oldStats domain.PeerStatus, newReceived, newTransmitted uint64, lastHandshake *time.Time) *time.Time {
|
||||
if lastHandshake == nil {
|
||||
func getSessionStartTime(oldStats domain.PeerStatus, newReceived, newTransmitted uint64, latestHandshake *time.Time) *time.Time {
|
||||
if latestHandshake == nil {
|
||||
return nil // currently not connected
|
||||
}
|
||||
|
||||
@ -146,16 +146,19 @@ func getSessionStartTime(oldStats domain.PeerStatus, newReceived, newTransmitted
|
||||
switch {
|
||||
// old session was never initiated
|
||||
case oldStats.BytesReceived == 0 && oldStats.BytesTransmitted == 0 && (newReceived > 0 || newTransmitted > 0):
|
||||
return lastHandshake
|
||||
return latestHandshake
|
||||
// session never received bytes -> first receive
|
||||
case oldStats.BytesReceived == 0 && newReceived > 0 && (oldStats.LastHandshake == nil || oldStats.LastHandshake.Before(oldestHandshakeTime)):
|
||||
return lastHandshake
|
||||
return latestHandshake
|
||||
// session never transmitted bytes -> first transmit
|
||||
case oldStats.BytesTransmitted == 0 && newTransmitted > 0 && (oldStats.LastSessionStart == nil || oldStats.LastHandshake.Before(oldestHandshakeTime)):
|
||||
return lastHandshake
|
||||
return latestHandshake
|
||||
// session restarted as newer send or transmit counts are lower
|
||||
case (newReceived != 0 && newReceived < oldStats.BytesReceived) || (newTransmitted != 0 && newTransmitted < oldStats.BytesTransmitted):
|
||||
return lastHandshake
|
||||
return latestHandshake
|
||||
// session initiated (but some bytes were already transmitted
|
||||
case oldStats.LastSessionStart == nil && (newReceived > oldStats.BytesReceived || newTransmitted > oldStats.BytesTransmitted):
|
||||
return latestHandshake
|
||||
default:
|
||||
return oldStats.LastSessionStart
|
||||
}
|
||||
|
@ -53,6 +53,16 @@ func Test_getSessionStartTime(t *testing.T) {
|
||||
},
|
||||
want: &now,
|
||||
},
|
||||
{
|
||||
name: "freshly connected (no prev session but bytes)",
|
||||
args: args{
|
||||
oldStats: domain.PeerStatus{LastSessionStart: nil, BytesReceived: 10, BytesTransmitted: 20},
|
||||
newReceived: 100,
|
||||
newTransmitted: 100,
|
||||
lastHandshake: &now,
|
||||
},
|
||||
want: &now,
|
||||
},
|
||||
{
|
||||
name: "still connected",
|
||||
args: args{
|
||||
|
@ -269,7 +269,7 @@ func (m Manager) RestoreInterfaceState(ctx context.Context, updateDbOnError bool
|
||||
|
||||
func (m Manager) CreateDefaultPeer(ctx context.Context, user *domain.User) error {
|
||||
// TODO: implement
|
||||
return nil
|
||||
return fmt.Errorf("IMPLEMENT ME")
|
||||
}
|
||||
|
||||
func (m Manager) GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error) {
|
||||
@ -885,3 +885,17 @@ func (m Manager) validatePeerDeletion(ctx context.Context, del *domain.Peer) err
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Manager) GetPeerStats(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.PeerStatus, error) {
|
||||
_, peers, err := m.db.GetInterfaceAndPeers(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch peers for interface %s: %w", id, err)
|
||||
}
|
||||
|
||||
peerIds := make([]domain.PeerIdentifier, len(peers))
|
||||
for i, peer := range peers {
|
||||
peerIds[i] = peer.Identifier
|
||||
}
|
||||
|
||||
return m.db.GetPeersStats(ctx, peerIds...)
|
||||
}
|
||||
|
@ -132,10 +132,10 @@ type PhysicalPeer struct {
|
||||
}
|
||||
|
||||
func (p PhysicalPeer) GetPresharedKey() *wgtypes.Key {
|
||||
if p.PrivateKey == "" {
|
||||
if p.PresharedKey == "" {
|
||||
return nil
|
||||
}
|
||||
key, err := wgtypes.ParseKey(p.PrivateKey)
|
||||
key, err := wgtypes.ParseKey(string(p.PresharedKey))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -17,6 +17,17 @@ type PeerStatus struct {
|
||||
LastSessionStart *time.Time `gorm:"column:last_session_start"`
|
||||
}
|
||||
|
||||
func (s PeerStatus) IsConnected() bool {
|
||||
oldestHandshakeTime := time.Now().Add(-2 * time.Minute) // if a handshake is older than 2 minutes, the peer is no longer connected
|
||||
|
||||
handshakeValid := false
|
||||
if s.LastHandshake != nil {
|
||||
handshakeValid = !s.LastHandshake.Before(oldestHandshakeTime)
|
||||
}
|
||||
|
||||
return s.IsPingable || handshakeValid
|
||||
}
|
||||
|
||||
type InterfaceStatus struct {
|
||||
InterfaceId InterfaceIdentifier `gorm:"primaryKey;column:identifier"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||
|
Loading…
x
Reference in New Issue
Block a user