diff --git a/src/opnsense/www/js/widgets/Metadata/Core.xml b/src/opnsense/www/js/widgets/Metadata/Core.xml
index 831b403fb..0f8607866 100644
--- a/src/opnsense/www/js/widgets/Metadata/Core.xml
+++ b/src/opnsense/www/js/widgets/Metadata/Core.xml
@@ -256,4 +256,16 @@
N/A
+
+ Wireguard.js
+
+ /api/wireguard/service/show
+ /api/wireguard/general/get
+
+
+ Wireguard
+ Wireguard is currently disabled. Click to configure IPsec.
+ There are currently no tunnels.
+
+
\ No newline at end of file
diff --git a/src/opnsense/www/js/widgets/Wireguard.js b/src/opnsense/www/js/widgets/Wireguard.js
new file mode 100644
index 000000000..bbc02c8cb
--- /dev/null
+++ b/src/opnsense/www/js/widgets/Wireguard.js
@@ -0,0 +1,120 @@
+/**
+ * Copyright (C) 2024 Deciso B.V.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import BaseTableWidget from "./BaseTableWidget.js";
+
+export default class Wireguard extends BaseTableWidget {
+ constructor() {
+ super();
+ this.resizeHandles = "e, w";
+ this.currentTunnels = {};
+ this.tickTimeout = 5;
+ }
+
+ getGridOptions() {
+ return {
+ // Automatically triggers vertical scrolling after reaching 650px in height
+ sizeToContent: 650
+ };
+ }
+
+ getMarkup() {
+ let $container = $('
');
+ let $wgTunnelTable = this.createTable('wgTunnelTable', {
+ headerPosition: 'none'
+ });
+
+ $container.append($wgTunnelTable);
+ return $container;
+ }
+
+ async onWidgetTick() {
+ try {
+ const wg = await ajaxGet('/api/wireguard/general/get', {});
+ if (!wg.general || !wg.general.enabled) {
+ this.displayError(`${this.translations.unconfigured}`);
+ return;
+ }
+
+ const response = await ajaxGet('/api/wireguard/service/show', {});
+
+ if (!response || !response.rows || response.rows.length === 0) {
+ this.displayError(`${this.translations.notunnels}`);
+ return;
+ }
+
+ this.processTunnels(response.rows);
+ } catch (error) {
+ this.displayError(`${this.translations.nodata}`);
+ }
+ }
+
+ displayError(message) {
+ $('#wgTunnelTable'). empty().append(
+ $(``)
+ );
+ }
+
+ processTunnels(newTunnels) {
+ let tunnels = newTunnels.map(row => ({
+ ifname: row.ifname ? row.if + ' (' + row.ifname + ') ' : row.if,
+ name: row.name,
+ pubkey: row['public-key'],
+ latest_handhake: row['latest-handshake'],
+ latest_handhake_fmt: row['latest-handshake'] ? moment.unix(row['latest-handshake']).local().format('YYYY-MM-DD HH:mm:ss') : '-'
+ }));
+
+ tunnels.sort((a, b) => a.latest_handhake === b.latest_handhake ? 0 : a.latest_handhake ? -1 : 1);
+
+ let rows = [];
+ // Generate HTML for each tunnel
+ tunnels.forEach(tunnel => {
+ let row = `
+
+
+ ${tunnel.ifname}
+ ↔
+ ${tunnel.name}
+
+
+
+ ${tunnel.pubkey}
+
+
+ ${tunnel.latest_handhake_fmt}
+
+
`;
+ rows.push(row);
+ });
+
+ // Update the HTML table with the sorted rows
+ super.updateTable('wgTunnelTable', rows.map(row => [row]));
+
+ // Activate tooltips for new dynamic elements
+ $('[data-toggle="tooltip"]').tooltip();
+ }
+}