mirror of
https://github.com/swissmakers/wireguard-manager.git
synced 2025-07-20 14:20:57 +02:00
Change client connection-status query to API-callable
This commit is contained in:
parent
1c370dcf54
commit
0eaccfbc85
@ -842,101 +842,123 @@ func GlobalSettings(db store.IStore) echo.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// Status handler displays WireGuard status information.
|
||||
// Status renders the HTML page for VPN status.
|
||||
func Status(db store.IStore) echo.HandlerFunc {
|
||||
// code that renders the effective "status.html" with the default variables
|
||||
return func(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "status.html", map[string]interface{}{
|
||||
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||
"devices": nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// APIStatus returns the current WireGuard status as JSON.
|
||||
// This handler is intended to be polled via AJAX to update the VPN status table dynamically.
|
||||
func APIStatus(db store.IStore) echo.HandlerFunc {
|
||||
// Define the view model structures.
|
||||
type PeerVM struct {
|
||||
Name string
|
||||
Email string
|
||||
PublicKey string
|
||||
ReceivedBytes int64
|
||||
TransmitBytes int64
|
||||
LastHandshakeTime time.Time
|
||||
LastHandshakeRel time.Duration
|
||||
Connected bool
|
||||
AllocatedIP string
|
||||
Endpoint string
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
PublicKey string `json:"public_key"`
|
||||
ReceivedBytes int64 `json:"received_bytes"`
|
||||
TransmitBytes int64 `json:"transmit_bytes"`
|
||||
LastHandshakeTime time.Time `json:"last_handshake_time"`
|
||||
LastHandshakeRel time.Duration `json:"last_handshake_rel"`
|
||||
Connected bool `json:"connected"`
|
||||
AllocatedIP string `json:"allocated_ip"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
}
|
||||
type DeviceVM struct {
|
||||
Name string
|
||||
Peers []PeerVM
|
||||
Name string `json:"name"`
|
||||
Peers []PeerVM `json:"peers"`
|
||||
}
|
||||
|
||||
return func(c echo.Context) error {
|
||||
// Create a new WireGuard client.
|
||||
wgClient, err := wgctrl.New()
|
||||
if err != nil {
|
||||
return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{
|
||||
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||
"error": err.Error(),
|
||||
"devices": nil,
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Retrieve the list of WireGuard clients.
|
||||
devices, err := wgClient.Devices()
|
||||
if err != nil {
|
||||
return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{
|
||||
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||
"error": err.Error(),
|
||||
"devices": nil,
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
devicesVm := make([]DeviceVM, 0, len(devices))
|
||||
// Prepare the device view model.
|
||||
var devicesVM []DeviceVM
|
||||
if len(devices) > 0 {
|
||||
m := make(map[string]*model.Client)
|
||||
// Create a map of clients keyed by public key.
|
||||
clients, err := db.GetClients(false)
|
||||
if err != nil {
|
||||
return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{
|
||||
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||
"error": err.Error(),
|
||||
"devices": nil,
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
clientMap := make(map[string]*model.Client)
|
||||
for i := range clients {
|
||||
if clients[i].Client != nil {
|
||||
m[clients[i].Client.PublicKey] = clients[i].Client
|
||||
clientMap[clients[i].Client.PublicKey] = clients[i].Client
|
||||
}
|
||||
}
|
||||
|
||||
// Helper map for sorting based on connection status.
|
||||
conv := map[bool]int{true: 1, false: 0}
|
||||
for i := range devices {
|
||||
devVm := DeviceVM{Name: devices[i].Name}
|
||||
for j := range devices[i].Peers {
|
||||
|
||||
for _, dev := range devices {
|
||||
devVm := DeviceVM{
|
||||
Name: dev.Name,
|
||||
}
|
||||
// Process each peer on the device.
|
||||
for _, peer := range dev.Peers {
|
||||
var allocatedIPs string
|
||||
for _, ip := range devices[i].Peers[j].AllowedIPs {
|
||||
if len(allocatedIPs) > 0 {
|
||||
// Concatenate all allowed IPs (as strings) separated by line breaks.
|
||||
for _, ip := range peer.AllowedIPs {
|
||||
if allocatedIPs != "" {
|
||||
allocatedIPs += "</br>"
|
||||
}
|
||||
allocatedIPs += ip.String()
|
||||
}
|
||||
|
||||
pVm := PeerVM{
|
||||
PublicKey: devices[i].Peers[j].PublicKey.String(),
|
||||
ReceivedBytes: devices[i].Peers[j].ReceiveBytes,
|
||||
TransmitBytes: devices[i].Peers[j].TransmitBytes,
|
||||
LastHandshakeTime: devices[i].Peers[j].LastHandshakeTime,
|
||||
LastHandshakeRel: time.Since(devices[i].Peers[j].LastHandshakeTime),
|
||||
PublicKey: peer.PublicKey.String(),
|
||||
ReceivedBytes: peer.ReceiveBytes,
|
||||
TransmitBytes: peer.TransmitBytes,
|
||||
LastHandshakeTime: peer.LastHandshakeTime,
|
||||
LastHandshakeRel: time.Since(peer.LastHandshakeTime),
|
||||
AllocatedIP: allocatedIPs,
|
||||
}
|
||||
// Mark as connected if handshake was less than 3 minutes ago.
|
||||
// Mark as connected if the last handshake was less than 3 minutes ago.
|
||||
pVm.Connected = pVm.LastHandshakeRel.Minutes() < 3.0
|
||||
|
||||
// If the user is an admin, add the endpoint information.
|
||||
if isAdmin(c) {
|
||||
pVm.Endpoint = devices[i].Peers[j].Endpoint.String()
|
||||
pVm.Endpoint = peer.Endpoint.String()
|
||||
}
|
||||
if _client, ok := m[pVm.PublicKey]; ok {
|
||||
pVm.Name = _client.Name
|
||||
pVm.Email = _client.Email
|
||||
// If we have additional client info, use it.
|
||||
if client, ok := clientMap[pVm.PublicKey]; ok {
|
||||
pVm.Name = client.Name
|
||||
pVm.Email = client.Email
|
||||
}
|
||||
devVm.Peers = append(devVm.Peers, pVm)
|
||||
}
|
||||
sort.SliceStable(devVm.Peers, func(i, j int) bool { return devVm.Peers[i].Name < devVm.Peers[j].Name })
|
||||
sort.SliceStable(devVm.Peers, func(i, j int) bool { return conv[devVm.Peers[i].Connected] > conv[devVm.Peers[j].Connected] })
|
||||
devicesVm = append(devicesVm, devVm)
|
||||
|
||||
// Sort peers alphabetically and by connection status.
|
||||
sort.SliceStable(devVm.Peers, func(i, j int) bool {
|
||||
return devVm.Peers[i].Name < devVm.Peers[j].Name
|
||||
})
|
||||
sort.SliceStable(devVm.Peers, func(i, j int) bool {
|
||||
return conv[devVm.Peers[i].Connected] > conv[devVm.Peers[j].Connected]
|
||||
})
|
||||
devicesVM = append(devicesVM, devVm)
|
||||
}
|
||||
}
|
||||
|
||||
return c.Render(http.StatusOK, "status.html", map[string]interface{}{
|
||||
"baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)},
|
||||
"devices": devicesVm,
|
||||
"error": "",
|
||||
// Return the final client-devices status as JSON.
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"devices": devicesVM,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
1
main.go
1
main.go
@ -270,6 +270,7 @@ func main() {
|
||||
app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession)
|
||||
app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession)
|
||||
app.GET(util.BasePath+"/api/machine-ips", handler.MachineIPAddresses(), handler.ValidSession)
|
||||
app.GET(util.BasePath+"/api/connection-status", handler.APIStatus(db), handler.ValidSession)
|
||||
app.GET(util.BasePath+"/api/subnet-ranges", handler.GetOrderedSubnetRanges(), handler.ValidSession)
|
||||
app.GET(util.BasePath+"/api/suggest-client-ips", handler.SuggestIPAllocation(db), handler.ValidSession)
|
||||
app.POST(util.BasePath+"/api/apply-wg-config", handler.ApplyServerConfig(db, tmplDir),
|
||||
|
@ -15,17 +15,68 @@ VPN Connected Users (Peers)
|
||||
|
||||
{{define "page_content"}}
|
||||
<script>
|
||||
function bytesToHumanReadable(temporal) {
|
||||
const units = [" ", " K", " M", " G", " T", " P", " E", " Z", " Y"]
|
||||
let pow = 0
|
||||
|
||||
while (temporal > 1024) {
|
||||
temporal /= 1024
|
||||
pow ++
|
||||
if (pow == units.length-1) break
|
||||
// Converts a number of bytes to a human-readable string.
|
||||
function bytesToHumanReadable(bytes) {
|
||||
const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
let i = 0;
|
||||
while (bytes >= 1024 && i < units.length - 1) {
|
||||
bytes /= 1024;
|
||||
i++;
|
||||
}
|
||||
return bytes.toFixed(2) + " " + units[i];
|
||||
}
|
||||
|
||||
return parseFloat(temporal.toFixed(2)) + units[pow]+"B"
|
||||
// Fetch and update the VPN status table.
|
||||
function updateStatusTable() {
|
||||
$.ajax({
|
||||
url: "{{.basePath}}/api/connection-status", // Make sure your APIStatus endpoint is registered.
|
||||
method: "GET",
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
console.log("Received API status data:", data);
|
||||
// Use a fallback if devices is undefined.
|
||||
var devices = (data && data.devices && Array.isArray(data.devices)) ? data.devices : [];
|
||||
let html = "";
|
||||
devices.forEach(function(dev) {
|
||||
html += '<div class="table-responsive">';
|
||||
html += '<table class="table table-sm table-bordered table-striped">';
|
||||
html += '<caption>List of connected peers for device ' + dev.name + '</caption>';
|
||||
html += '<thead class="thead-dark"><tr>';
|
||||
html += '<th scope="col">#</th>';
|
||||
html += '<th scope="col">Name</th>';
|
||||
html += '<th scope="col">Email</th>';
|
||||
html += '<th scope="col">Allocated IPs</th>';
|
||||
html += '<th scope="col">Endpoint</th>';
|
||||
html += '<th scope="col">Public Key</th>';
|
||||
html += '<th scope="col">Received</th>';
|
||||
html += '<th scope="col">Transmitted</th>';
|
||||
html += '<th scope="col">Connected</th>';
|
||||
html += '<th scope="col">Last Handshake</th>';
|
||||
html += '</tr></thead>';
|
||||
html += '<tbody>';
|
||||
// Use the lowercase property names as provided by the API.
|
||||
dev.peers.forEach(function(peer, idx) {
|
||||
html += '<tr' + (peer.connected ? ' class="table-success"' : '') + '>';
|
||||
html += '<th scope="row">' + idx + '</th>';
|
||||
html += '<td>' + peer.name + '</td>';
|
||||
html += '<td>' + peer.email + '</td>';
|
||||
html += '<td>' + peer.allocated_ip + '</td>';
|
||||
html += '<td>' + peer.endpoint + '</td>';
|
||||
html += '<td>' + peer.public_key + '</td>';
|
||||
html += '<td title="' + peer.received_bytes + ' Bytes">' + bytesToHumanReadable(peer.received_bytes) + '</td>';
|
||||
html += '<td title="' + peer.transmit_bytes + ' Bytes">' + bytesToHumanReadable(peer.transmit_bytes) + '</td>';
|
||||
html += '<td>' + (peer.connected ? '✓' : '') + '</td>';
|
||||
html += '<td>' + new Date(peer.last_handshake_time).toLocaleString() + '</td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
});
|
||||
$("#status-table-container").html(html);
|
||||
},
|
||||
error: function(err) {
|
||||
console.error("Error updating status:", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<section class="content">
|
||||
@ -33,43 +84,7 @@ VPN Connected Users (Peers)
|
||||
{{ if .error }}
|
||||
<div class="alert alert-warning" role="alert">{{.error}}</div>
|
||||
{{ end }}
|
||||
{{ range $dev := .devices }}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered table-striped">
|
||||
<caption>List of connected peers for device with name {{ $dev.Name }} </caption>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Allocated IPs</th>
|
||||
<th scope="col">Endpoint</th>
|
||||
<th scope="col">Public Key</th>
|
||||
<th scope="col">Received</th>
|
||||
<th scope="col">Transmitted</th>
|
||||
<th scope="col">Connected (Approximation)</th>
|
||||
<th scope="col">Last Handshake</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $idx, $peer := $dev.Peers }}
|
||||
<tr {{ if $peer.Connected }} class="table-success" {{ end }}>
|
||||
<th scope="row">{{ $idx }}</th>
|
||||
<td>{{ $peer.Name }}</td>
|
||||
<td>{{ $peer.Email }}</td>
|
||||
<td>{{ $peer.AllocatedIP }}</td>
|
||||
<td>{{ $peer.Endpoint }}</td>
|
||||
<td>{{ $peer.PublicKey }}</td>
|
||||
<td title="{{ $peer.ReceivedBytes }} Bytes"><script>document.write(bytesToHumanReadable({{ $peer.ReceivedBytes }}))</script></td>
|
||||
<td title="{{ $peer.TransmitBytes }} Bytes"><script>document.write(bytesToHumanReadable({{ $peer.TransmitBytes }}))</script></td>
|
||||
<td>{{ if $peer.Connected }}✓{{end}}</td>
|
||||
<td>{{ $peer.LastHandshakeTime.Format "2006-01-02 15:04:05 MST" }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div id="status-table-container">
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user