mirror of https://github.com/wg-easy/wg-easy
Browse Source
* check metrics password * rewrite prometheus and json metric endpoints * move metrics to general metrics is not per interface * change metrics settings in admin panel * add i18n keyspull/1657/head
committed by
GitHub
30 changed files with 391 additions and 346 deletions
@ -0,0 +1,26 @@ |
|||
<template> |
|||
<Label :for="id" class="font-semibold md:align-middle md:leading-10"> |
|||
{{ label }} |
|||
</Label> |
|||
<input |
|||
:id="id" |
|||
v-model.trim="data" |
|||
:name="id" |
|||
type="text" |
|||
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400" |
|||
/> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
defineProps<{ id: string; label: string }>(); |
|||
|
|||
const data = defineModel<string | null>({ |
|||
set(value) { |
|||
const temp = value?.trim() ?? null; |
|||
if (temp === '') { |
|||
return null; |
|||
} |
|||
return temp; |
|||
}, |
|||
}); |
|||
</script> |
@ -1,3 +0,0 @@ |
|||
<template><div></div></template> |
|||
|
|||
<script lang="ts" setup></script> |
@ -1,6 +1,4 @@ |
|||
export default definePermissionEventHandler(actions.ADMIN, async () => { |
|||
const sessionConfig = await Database.general.getSessionConfig(); |
|||
return { |
|||
sessionTimeout: sessionConfig.sessionTimeout, |
|||
}; |
|||
const generalConfig = await Database.general.getConfig(); |
|||
return generalConfig; |
|||
}); |
|||
|
@ -1,21 +0,0 @@ |
|||
import { sql } from 'drizzle-orm'; |
|||
import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; |
|||
|
|||
import { wgInterface } from '../../schema'; |
|||
|
|||
export const prometheus = sqliteTable('prometheus_table', { |
|||
id: text() |
|||
.primaryKey() |
|||
.references(() => wgInterface.name, { |
|||
onDelete: 'cascade', |
|||
onUpdate: 'cascade', |
|||
}), |
|||
password: text().notNull(), |
|||
createdAt: text('created_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`), |
|||
updatedAt: text('updated_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`) |
|||
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), |
|||
}); |
@ -1,31 +0,0 @@ |
|||
import type { DBType } from '#db/sqlite'; |
|||
import { eq, sql } from 'drizzle-orm'; |
|||
import { prometheus } from './schema'; |
|||
|
|||
function createPreparedStatement(db: DBType) { |
|||
return { |
|||
get: db.query.prometheus |
|||
.findFirst({ where: eq(prometheus.id, sql.placeholder('interface')) }) |
|||
.prepare(), |
|||
}; |
|||
} |
|||
|
|||
export class PrometheusService { |
|||
#statements: ReturnType<typeof createPreparedStatement>; |
|||
|
|||
constructor(db: DBType) { |
|||
this.#statements = createPreparedStatement(db); |
|||
} |
|||
|
|||
get(infName: string) { |
|||
return this.#statements.get.execute({ interface: infName }); |
|||
} |
|||
} |
|||
|
|||
export class MetricsService { |
|||
prometheus: PrometheusService; |
|||
|
|||
constructor(db: DBType) { |
|||
this.prometheus = new PrometheusService(db); |
|||
} |
|||
} |
@ -1,4 +0,0 @@ |
|||
import type { InferSelectModel } from 'drizzle-orm'; |
|||
import type { prometheus } from './schema'; |
|||
|
|||
export type PrometheusType = InferSelectModel<typeof prometheus>; |
@ -1,14 +0,0 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
// TODO: check password
|
|||
|
|||
const prometheus = await Database.metrics.prometheus.get('wg0'); |
|||
if (!prometheus) { |
|||
throw createError({ |
|||
statusCode: 400, |
|||
message: 'Prometheus metrics are not enabled', |
|||
}); |
|||
} |
|||
|
|||
setHeader(event, 'Content-Type', 'text/plain'); |
|||
return getPrometheusResponse(); |
|||
}); |
@ -1,13 +1,35 @@ |
|||
export default defineEventHandler(async () => { |
|||
// TODO: check password
|
|||
|
|||
const prometheus = await Database.metrics.prometheus.get('wg0'); |
|||
if (!prometheus) { |
|||
throw createError({ |
|||
statusCode: 400, |
|||
message: 'Prometheus metrics are not enabled', |
|||
}); |
|||
} |
|||
|
|||
export default defineMetricsHandler('json', async () => { |
|||
return getMetricsJSON(); |
|||
}); |
|||
|
|||
async function getMetricsJSON() { |
|||
const clients = await WireGuard.getClients(); |
|||
let wireguardPeerCount = 0; |
|||
let wireguardEnabledPeersCount = 0; |
|||
let wireguardConnectedPeersCount = 0; |
|||
for (const client of clients) { |
|||
wireguardPeerCount++; |
|||
if (client.enabled === true) { |
|||
wireguardEnabledPeersCount++; |
|||
} |
|||
if (isPeerConnected(client)) { |
|||
wireguardConnectedPeersCount++; |
|||
} |
|||
} |
|||
return { |
|||
wireguard_configured_peers: wireguardPeerCount, |
|||
wireguard_enabled_peers: wireguardEnabledPeersCount, |
|||
wireguard_connected_peers: wireguardConnectedPeersCount, |
|||
clients: clients.map((client) => ({ |
|||
name: client.name, |
|||
enabled: client.enabled, |
|||
ipv4Address: client.ipv4Address, |
|||
ipv6Address: client.ipv6Address, |
|||
publicKey: client.publicKey, |
|||
endpoint: client.endpoint, |
|||
latestHandshakeAt: client.latestHandshakeAt, |
|||
transferRx: client.transferRx, |
|||
transferTx: client.transferTx, |
|||
})), |
|||
}; |
|||
} |
|||
|
@ -0,0 +1,67 @@ |
|||
export default defineMetricsHandler('prometheus', async ({ event }) => { |
|||
setHeader(event, 'Content-Type', 'text/plain'); |
|||
return getPrometheusResponse(); |
|||
}); |
|||
|
|||
async function getPrometheusResponse() { |
|||
const clients = await WireGuard.getClients(); |
|||
let wireguardPeerCount = 0; |
|||
let wireguardEnabledPeersCount = 0; |
|||
let wireguardConnectedPeersCount = 0; |
|||
const wireguardSentBytes = []; |
|||
const wireguardReceivedBytes = []; |
|||
const wireguardLatestHandshakeSeconds = []; |
|||
for (const client of clients) { |
|||
wireguardPeerCount++; |
|||
if (client.enabled === true) { |
|||
wireguardEnabledPeersCount++; |
|||
} |
|||
|
|||
if (isPeerConnected(client)) { |
|||
wireguardConnectedPeersCount++; |
|||
} |
|||
|
|||
const id = `interface="wg0",enabled="${client.enabled}",ipv4Address="${client.ipv4Address}",ipv6Address="${client.ipv6Address}",name="${client.name}"`; |
|||
|
|||
wireguardSentBytes.push( |
|||
`wireguard_sent_bytes{${id}} ${client.transferTx ?? 0}` |
|||
); |
|||
wireguardReceivedBytes.push( |
|||
`wireguard_received_bytes{${id}} ${client.transferRx ?? 0}` |
|||
); |
|||
// TODO: if latestHandshakeAt is null this would result in client showing as online?
|
|||
wireguardLatestHandshakeSeconds.push( |
|||
`wireguard_latest_handshake_seconds{${id}} ${client.latestHandshakeAt ? (Date.now() - client.latestHandshakeAt.getTime()) / 1000 : 0}` |
|||
); |
|||
} |
|||
|
|||
const returnText = [ |
|||
'# HELP wg-easy and wireguard metrics', |
|||
'', |
|||
'# HELP wireguard_configured_peers', |
|||
'# TYPE wireguard_configured_peers gauge', |
|||
`wireguard_configured_peers{interface="wg0"} ${wireguardPeerCount}`, |
|||
'', |
|||
'# HELP wireguard_enabled_peers', |
|||
'# TYPE wireguard_enabled_peers gauge', |
|||
`wireguard_enabled_peers{interface="wg0"} ${wireguardEnabledPeersCount}`, |
|||
'', |
|||
'# HELP wireguard_connected_peers', |
|||
'# TYPE wireguard_connected_peers gauge', |
|||
`wireguard_connected_peers{interface="wg0"} ${wireguardConnectedPeersCount}`, |
|||
'', |
|||
'# HELP wireguard_sent_bytes Bytes sent to the peer', |
|||
'# TYPE wireguard_sent_bytes counter', |
|||
`${wireguardSentBytes.join('\n')}`, |
|||
'', |
|||
'# HELP wireguard_received_bytes Bytes received from the peer', |
|||
'# TYPE wireguard_received_bytes counter', |
|||
`${wireguardReceivedBytes.join('\n')}`, |
|||
'', |
|||
'# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake', |
|||
'# TYPE wireguard_latest_handshake_seconds gauge', |
|||
`${wireguardLatestHandshakeSeconds.join('\n')}`, |
|||
]; |
|||
|
|||
return returnText.join('\n'); |
|||
} |
@ -1,74 +0,0 @@ |
|||
// TODO: rewrite
|
|||
|
|||
export async function getPrometheusResponse() { |
|||
const clients = await WireGuard.getClients(); |
|||
let wireguardPeerCount = 0; |
|||
let wireguardEnabledPeersCount = 0; |
|||
let wireguardConnectedPeersCount = 0; |
|||
let wireguardSentBytes = ''; |
|||
let wireguardReceivedBytes = ''; |
|||
let wireguardLatestHandshakeSeconds = ''; |
|||
for (const client of clients) { |
|||
wireguardPeerCount++; |
|||
if (client.enabled === true) { |
|||
wireguardEnabledPeersCount++; |
|||
} |
|||
if (client.endpoint !== null) { |
|||
wireguardConnectedPeersCount++; |
|||
} |
|||
wireguardSentBytes += `wireguard_sent_bytes{interface="wg0",enabled="${client.enabled}",ipv4Address="${client.ipv4Address}",ipv6Address="${client.ipv6Address}",name="${client.name}"} ${Number(client.transferTx)}\n`; |
|||
wireguardReceivedBytes += `wireguard_received_bytes{interface="wg0",enabled="${client.enabled}",ipv4Address="${client.ipv4Address}",ipv6Address="${client.ipv6Address}",name="${client.name}"} ${Number(client.transferRx)}\n`; |
|||
wireguardLatestHandshakeSeconds += `wireguard_latest_handshake_seconds{interface="wg0",enabled="${client.enabled}",ipv4Address="${client.ipv4Address}",ipv6Address="${client.ipv6Address}",name="${client.name}"} ${client.latestHandshakeAt ? (new Date().getTime() - new Date(client.latestHandshakeAt).getTime()) / 1000 : 0}\n`; |
|||
} |
|||
|
|||
let returnText = '# HELP wg-easy and wireguard metrics\n'; |
|||
|
|||
returnText += '\n# HELP wireguard_configured_peers\n'; |
|||
returnText += '# TYPE wireguard_configured_peers gauge\n'; |
|||
returnText += `wireguard_configured_peers{interface="wg0"} ${wireguardPeerCount}\n`; |
|||
|
|||
returnText += '\n# HELP wireguard_enabled_peers\n'; |
|||
returnText += '# TYPE wireguard_enabled_peers gauge\n'; |
|||
returnText += `wireguard_enabled_peers{interface="wg0"} ${wireguardEnabledPeersCount}\n`; |
|||
|
|||
returnText += '\n# HELP wireguard_connected_peers\n'; |
|||
returnText += '# TYPE wireguard_connected_peers gauge\n'; |
|||
returnText += `wireguard_connected_peers{interface="wg0"} ${wireguardConnectedPeersCount}\n`; |
|||
|
|||
returnText += '\n# HELP wireguard_sent_bytes Bytes sent to the peer\n'; |
|||
returnText += '# TYPE wireguard_sent_bytes counter\n'; |
|||
returnText += `${wireguardSentBytes}`; |
|||
|
|||
returnText += |
|||
'\n# HELP wireguard_received_bytes Bytes received from the peer\n'; |
|||
returnText += '# TYPE wireguard_received_bytes counter\n'; |
|||
returnText += `${wireguardReceivedBytes}`; |
|||
|
|||
returnText += |
|||
'\n# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake\n'; |
|||
returnText += '# TYPE wireguard_latest_handshake_seconds gauge\n'; |
|||
returnText += `${wireguardLatestHandshakeSeconds}`; |
|||
|
|||
return returnText; |
|||
} |
|||
|
|||
export async function getMetricsJSON() { |
|||
const clients = await WireGuard.getClients(); |
|||
let wireguardPeerCount = 0; |
|||
let wireguardEnabledPeersCount = 0; |
|||
let wireguardConnectedPeersCount = 0; |
|||
for (const client of clients) { |
|||
wireguardPeerCount++; |
|||
if (client.enabled === true) { |
|||
wireguardEnabledPeersCount++; |
|||
} |
|||
if (client.endpoint !== null) { |
|||
wireguardConnectedPeersCount++; |
|||
} |
|||
} |
|||
return { |
|||
wireguard_configured_peers: wireguardPeerCount, |
|||
wireguard_enabled_peers: wireguardEnabledPeersCount, |
|||
wireguard_connected_peers: wireguardConnectedPeersCount, |
|||
}; |
|||
} |
@ -0,0 +1,10 @@ |
|||
export function isPeerConnected(client: { latestHandshakeAt: Date | null }) { |
|||
if (!client.latestHandshakeAt) { |
|||
return false; |
|||
} |
|||
|
|||
const lastHandshakeMs = Date.now() - client.latestHandshakeAt.getTime(); |
|||
|
|||
// connected if last handshake was less than 10 minutes ago
|
|||
return lastHandshakeMs < 1000 * 60 * 10; |
|||
} |
Loading…
Reference in new issue