4 Commits

Author SHA1 Message Date
iTakinn b33bef3d71 pol 2025-10-12 19:21:41 -03:00
iTakinn d5a38398f7 removed generate qrcodes that i struggled settin up 2025-10-12 18:58:08 -03:00
iTakinn 662b1e58ee misfig suspend/unsuspend 2025-10-12 18:37:36 -03:00
iTakinn aa337f9b29 EN-US 2025-10-12 18:05:12 -03:00
5 changed files with 542 additions and 340 deletions
View File
+13 -10
View File
@@ -12,36 +12,34 @@ WHMCS module for integration with [WGDashboard](https://github.com/donaldzou/WGD
## 🚀 Installation ## 🚀 Installation
1. Upload the module files to the WHMCS directory: 1. Upload the module files to the WHMCS directory:
``` ```
/modules/servers/wgdashboard/ /modules/servers/wgdashboard/
``` ```
2. File structure: 2. File structure:
``` ```
modules/server/wgdashboard/ modules/server/wgdashboard/
├── wgdashboard.php ├── wgdashboard.php
└── clientarea.tpl └── clientarea.tpl
``` ```
3. In the WHMCS admin panel, navigate to: 3. In the WHMCS admin panel, navigate to:
**Setup → Products/Services → Servers → Add New Server** **Setup → Products/Services → Servers → Add New Server**
4. Configure the server: 4. Configure the server:
- **Hostname**: IP or domain of WGDashboard (e.g., `10.0.238.222` or `vpn.example.com`) - **Hostname**: IP or domain of WGDashboard (e.g., `10.0.238.222` or `vpn.example.com`)
- **Module**: WGDashboard - **Module**: WGDashboard
- **Port**: API port (e.g., `10086`) - **Port**: API port (e.g., `10086`)
- **Password**: WGDashboard API Key - **Password**: WGDashboard API Key
5. Click **Test Connection** to validate the configuration. 5. Click **Test Connection** to validate the configuration.
## ⚙️ Product Configuration ## ⚙️ Product Configuration
1. Create or edit a product in **Setup → Products/Services** 1. Create or edit a product in **Setup → Products/Services**
2. In the **Module Settings** tab: 2. In the **Module Settings** tab:
- **Module Name**: WGDashboard - **Module Name**: WGDashboard
- **Server Group**: Select the configured server - **Server Group**: Select the configured server
3. **Configuration Options**: 3. **Configuration Options**:
- **Allowed IPs**: Allowed IPs for the peer (default: `0.0.0.0/0`) - **Allowed IPs**: Allowed IPs for the peer (default: `0.0.0.0/0`)
@@ -59,6 +57,7 @@ WHMCS module for integration with [WGDashboard](https://github.com/donaldzou/WGD
``` ```
Available placeholders: Available placeholders:
- `{{client_email}}` - Client's email - `{{client_email}}` - Client's email
- `{{domain}}` - Service domain - `{{domain}}` - Service domain
- `{{serviceid}}` - Service ID in WHMCS - `{{serviceid}}` - Service ID in WHMCS
@@ -82,7 +81,6 @@ Client Area:
- 📄 **Formatted Configuration**: Ready-to-use WireGuard configuration file - 📄 **Formatted Configuration**: Ready-to-use WireGuard configuration file
- 📋 **Download**: Button to download `.conf` file - 📋 **Download**: Button to download `.conf` file
### Admin Panel ### Admin Panel
In the client's service tab, the administrator views: In the client's service tab, the administrator views:
@@ -95,7 +93,6 @@ In the client's service tab, the administrator views:
- **Data Received**: Volume of data received From peer to server - **Data Received**: Volume of data received From peer to server
- **Data Sent**: Volume of data sent From server to peer - **Data Sent**: Volume of data sent From server to peer
## 🌐 WGDashboard Configuration ## 🌐 WGDashboard Configuration
To use this module, ensure the WGDashboard API is enabled: To use this module, ensure the WGDashboard API is enabled:
@@ -110,16 +107,19 @@ To use this module, ensure the WGDashboard API is enabled:
### Connection Test Fails ### Connection Test Fails
**Error 401/403**: Verify API Key **Error 401/403**: Verify API Key
``` ```
Solution: Confirm that the API Key is correct in the server's Password field Solution: Confirm that the API Key is correct in the server's Password field
``` ```
**Error 404**: Endpoint not found **Error 404**: Endpoint not found
``` ```
Solution: Check if your WGDashboard version supports the API Solution: Check if your WGDashboard version supports the API
``` ```
**Error 500**: WGDashboard internal error **Error 500**: WGDashboard internal error
``` ```
Solution: Check WGDashboard logs on the server Solution: Check WGDashboard logs on the server
``` ```
@@ -134,6 +134,7 @@ Solution: Check WGDashboard logs on the server
### QR Code Doesn't Appear ### QR Code Doesn't Appear
Check: Check:
- If JavaScript is enabled in the browser - If JavaScript is enabled in the browser
- Browser console for errors (F12) - Browser console for errors (F12)
- If QRCode.js library is loading correctly - If QRCode.js library is loading correctly
@@ -144,14 +145,15 @@ The module uses WHMCS's **Server Port** field to define the API port.
Do not include the port in the hostname, only in the dedicated field. Do not include the port in the hostname, only in the dedicated field.
✅ **Correct**: ✅ **Correct**:
- Hostname: `10.0.0.55` - Hostname: `10.0.0.55`
- Port: `10086` - Port: `10086`
❌ **Incorrect**: ❌ **Incorrect**:
- Hostname: `10.0.0.55:10086` - Hostname: `10.0.0.55:10086`
- Port: (empty) - Port: (empty)
## 🔄 Compatibility ## 🔄 Compatibility
- **WHMCS**: 7.0+ - **WHMCS**: 7.0+
@@ -182,6 +184,7 @@ Contributions are welcome! To contribute:
## 📋 Changelog ## 📋 Changelog
### v1.0.0 ### v1.0.0
- ✨ Initial release - ✨ Initial release
- ✅ Peer creation, suspension, reactivation, and termination - ✅ Peer creation, suspension, reactivation, and termination
- 📱 Client area with QR code and formatted configuration - 📱 Client area with QR code and formatted configuration
@@ -189,4 +192,4 @@ Contributions are welcome! To contribute:
- 🔐 Local WireGuard key generation - 🔐 Local WireGuard key generation
- 📊 Admin panel with peer statistics - 📊 Admin panel with peer statistics
--- ---
+235 -323
View File
@@ -1,292 +1,152 @@
{if $error} {if $error}
<div class="alert alert-danger"> <div class="alert alert-danger">
<strong>Erro:</strong> {$error} <strong>Error:</strong> {$error}
</div> </div>
{else} {else}
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
{if $formatted_config} {if $formatted_config}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<i class="fa fa-qrcode"></i> QR Code
</h3>
</div>
<div class="panel-body text-center">
<div id="qrcode" style="display: inline-block; margin-bottom: 10px;"></div>
<p class="text-muted">Scan this QR code with your WireGuard client</p>
<div class="text-center" style="margin-top: 15px;">
<button class="btn btn-info btn-sm" onclick="showInstructions()">
<i class="fa fa-info-circle"></i> Installation Instructions
</button>
</div>
</div>
</div>
{else}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<i class="fa fa-qrcode"></i> QR Code
</h3>
</div>
<div class="panel-body text-center">
<p class="text-muted">Configuration not available</p>
</div>
</div>
{/if}
</div>
<div class="col-md-6">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title"> <h3 class="panel-title">
<i class="fa fa-qrcode"></i> Código QR <i class="fa fa-cog"></i> WireGuard Configuration
</h3> </h3>
</div> </div>
<div class="panel-body text-center"> <div class="panel-body">
<div id="qrcode" style="display: inline-block; margin-bottom: 10px;"></div> {if $formatted_config}
<p class="text-muted">Escaneie este código QR com seu cliente WireGuard</p> <div class="form-group">
<div class="text-center" style="margin-top: 15px;"> <label>Configuration File:</label>
<button class="btn btn-info btn-sm" onclick="showInstructions()"> <pre class="wireguard-config" style="background: #f8f9fa;text-align:left; border: 1px solid #dee2e6; border-radius: 4px; padding: 15px; font-family: 'Courier New', monospace; font-size: 13px; line-height: 1.4; color: #333; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word; max-height: 350px; overflow-y: auto;">{$formatted_config|escape}</pre>
<i class="fa fa-info-circle"></i> Instruções de Instalação </div>
</button> <div class="text-center">
</div> <button class="btn btn-primary" onclick="downloadFormattedConfig()">
<i class="fa fa-download"></i> Download Configuration
</button>
<button class="btn btn-default" onclick="copyFormattedConfig()">
<i class="fa fa-copy"></i> Copy to Clipboard
</button>
</div>
{else}
<p class="text-muted">Configuration not available</p>
{/if}
</div> </div>
</div> </div>
{else} </div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<i class="fa fa-qrcode"></i> Código QR
</h3>
</div>
<div class="panel-body text-center">
<p class="text-muted">Configuração não disponível</p>
</div>
</div>
{/if}
</div> </div>
{/if}
<div class="col-md-6"> {* Instructions Modal *}
<div class="panel panel-default"> <div class="modal fade" id="instructionsModal" tabindex="-1" role="dialog">
<div class="panel-heading"> <div class="modal-dialog modal-lg" role="document">
<h3 class="panel-title"> <div class="modal-content">
<i class="fa fa-cog"></i> Configuração WireGuard <div class="modal-header">
</h3> <h4 class="modal-title">WireGuard Setup Instructions</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div> </div>
<div class="panel-body"> <div class="modal-body">
{if $formatted_config} <h5>Mobile Devices (Android/iOS):</h5>
<div class="form-group"> <ol>
<label>Arquivo de Configuração:</label> <li>Install the WireGuard app from Google Play Store or App Store</li>
<pre class="wireguard-config" style="background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; padding: 15px; font-family: 'Courier New', monospace; font-size: 13px; line-height: 1.4; color: #333; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word; max-height: 350px; overflow-y: auto;">{$formatted_config|escape}</pre> <li>Open the app and tap the "+" button</li>
</div> <li>Select "Scan from QR code"</li>
<div class="text-center"> <li>Scan the QR code displayed above</li>
<button class="btn btn-primary" onclick="downloadFormattedConfig()"> <li>Give your tunnel a name and save</li>
<i class="fa fa-download"></i> Baixar Configuração </ol>
</button>
<button class="btn btn-default" onclick="copyFormattedConfig()"> <h5>Desktop (Windows/macOS/Linux):</h5>
<i class="fa fa-copy"></i> Copiar para Área de Transferência <ol>
</button> <li>Download and install WireGuard from <a href="https://www.wireguard.com/install/" target="_blank">wireguard.com</a></li>
</div> <li>Click "Add Tunnel" → "Add empty tunnel..."</li>
{else} <li>Copy the configuration above and paste it</li>
<p class="text-muted">Configuração não disponível</p> <li>Save and activate the tunnel</li>
{/if} </ol>
<h5>Command Line (Linux):</h5>
<ol>
<li>Save the configuration to a file (e.g., <code>wg0.conf</code>)</li>
<li>Run: <code>sudo wg-quick up wg0</code></li>
<li>To stop: <code>sudo wg-quick down wg0</code></li>
</ol>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/if}
{* Modal de Instruções *} <script src="https://api.qrserver.com/v1/create-qr-code/?size=250x250&format=svg&data=test" style="display:none;"></script>
<div class="modal fade" id="instructionsModal" tabindex="-1" role="dialog"> <script>
<div class="modal-dialog modal-lg" role="document"> var configForQR = null;
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Instruções de Configuração WireGuard</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Fechar"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<h5>Dispositivos Móveis (Android/iOS):</h5>
<ol>
<li>Instale o aplicativo WireGuard da Google Play Store ou App Store</li>
<li>Abra o aplicativo e toque no botão "+"</li>
<li>Selecione "Escanear código QR"</li>
<li>Escaneie o código QR exibido acima</li>
<li>Dê um nome ao seu túnel e salve</li>
</ol>
<h5>Desktop (Windows/macOS/Linux):</h5> function generateQRCode(configText) {
<ol>
<li>Baixe e instale o WireGuard de <a href="https://www.wireguard.com/install/" target="_blank">wireguard.com</a></li>
<li>Clique em "Adicionar Túnel" → "Adicionar túnel vazio..."</li>
<li>Copie a configuração acima e cole</li>
<li>Salve e ative o túnel</li>
</ol>
<h5>Linha de Comando (Linux):</h5>
<ol>
<li>Salve a configuração em um arquivo (ex: <code>wg0.conf</code>)</li>
<li>Execute: <code>sudo wg-quick up wg0</code></li>
<li>Para parar: <code>sudo wg-quick down wg0</code></li>
</ol>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Fechar</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js" onload="initQRCode()" onerror="loadQRCodeFallback()"></script>
<script>
var qrCodeLibraryLoaded = false;
var configForQR = null;
function initQRCode() {
qrCodeLibraryLoaded = true;
console.log('QRCode library loaded successfully');
if (configForQR) {
generateQRCode(configForQR);
}
}
function loadQRCodeFallback() {
console.log('Primary QRCode library failed, trying fallback...');
var script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js';
script.onload = function() {
console.log('Fallback QRCode library loaded');
qrCodeLibraryLoaded = true;
if (configForQR) {
generateQRCodeFallback(configForQR);
}
};
script.onerror = function() {
console.error('Both QRCode libraries failed to load');
var qrCodeElement = document.getElementById('qrcode'); var qrCodeElement = document.getElementById('qrcode');
if (qrCodeElement) {
qrCodeElement.innerHTML = '<p class="text-danger">Unable to load QR Code library</p>';
}
};
document.head.appendChild(script);
}
function generateQRCode(configText) {
var qrCodeElement = document.getElementById('qrcode');
console.log('Generating QR Code with qrcode.js...');
console.log('Config text length:', configText.length);
if (qrCodeElement && configText && configText.trim() !== '') {
// Check if QRCode library is loaded
if (typeof QRCode === 'undefined') {
console.error('Biblioteca QRCode não disponível, tentando fallback');
generateQRCodeFallback(configText);
return;
}
try { if (qrCodeElement && configText && configText.trim() !== '') {
QRCode.toCanvas(qrCodeElement, configText, { try {
width: 250, var qrUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=' + encodeURIComponent(configText);
margin: 3, var img = document.createElement('img');
color: { img.src = qrUrl;
dark: '#000000', img.alt = 'WireGuard QR Code';
light: '#FFFFFF' img.style.border = '3px solid #ddd';
} img.style.borderRadius = '4px';
}, function(error) { img.onerror = function() {
if (error) { qrCodeElement.innerHTML = '<p class="text-danger">Failed to generate QR code</p>';
console.error('Falha na geração do QR Code:', error); };
qrCodeElement.innerHTML = '<p class="text-danger">Falha na geração do QR Code: ' + error.message + '</p>'; qrCodeElement.innerHTML = '';
} else { qrCodeElement.appendChild(img);
console.log('QR Code gerado com sucesso'); } catch (e) {
} console.error('QR code generation failed:', e);
}); qrCodeElement.innerHTML = '<p class="text-danger">Failed to generate QR code</p>';
} catch (e) {
console.error('Exceção durante a geração do QR code:', e);
qrCodeElement.innerHTML = '<p class="text-danger">Erro na geração do QR Code: ' + e.message + '</p>';
}
} else {
console.error('Falha na geração do QR Code: elemento ou configuração ausente');
if (qrCodeElement) {
qrCodeElement.innerHTML = '<p class="text-warning">Configuração não disponível para QR code</p>';
}
}
}
function generateQRCodeFallback(configText) {
var qrCodeElement = document.getElementById('qrcode');
console.log('Trying fallback QR code generation...');
if (qrCodeElement && configText) {
try {
// Create QR code using online service as fallback
var qrUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=' + encodeURIComponent(configText);
var img = document.createElement('img');
img.src = qrUrl;
img.alt = 'WireGuard QR Code';
img.style.border = '3px solid #ddd';
img.style.borderRadius = '4px';
img.onload = function() {
console.log('Fallback QR code loaded successfully');
};
img.onerror = function() {
qrCodeElement.innerHTML = '<p class="text-danger">Falha ao gerar código QR</p>';
};
qrCodeElement.innerHTML = '';
qrCodeElement.appendChild(img);
} catch (e) {
console.error('Falha na geração do QR code de fallback:', e);
qrCodeElement.innerHTML = '<p class="text-danger">Falha na geração do código QR</p>';
}
}
}
// Generate QR Code if formatted config is available
{if $formatted_config}
document.addEventListener('DOMContentLoaded', function() {
configForQR = {$formatted_config|json_encode};
if (qrCodeLibraryLoaded) {
generateQRCode(configForQR);
} else {
console.log('Waiting for QRCode library to load...');
// Timeout fallback
setTimeout(function() {
if (!qrCodeLibraryLoaded && configForQR) {
console.log('Timeout reached, using fallback QR generation');
generateQRCodeFallback(configForQR);
} }
}, 3000);
}
});
{else}
console.log('No formatted config available for QR code');
{/if}
function downloadFormattedConfig() {
{if $formatted_config}
var config = {$formatted_config|json_encode};
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(config));
element.setAttribute('download', '{$peer_name|default:"wireguard"}.conf');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
{/if}
}
function copyFormattedConfig() {
{if $formatted_config}
var config = {$formatted_config|json_encode};
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(config).then(function() {
alert('Configuração copiada para a área de transferência!');
}).catch(function(err) {
fallbackCopyTextToClipboard(config);
});
} else {
fallbackCopyTextToClipboard(config);
}
{/if}
}
function fallbackCopyTextToClipboard(text) {
var textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand('copy');
if (successful) {
alert('Configuração copiada para a área de transferência!');
} else { } else {
alert('Falha ao copiar configuração. Por favor, copie manualmente.'); if (qrCodeElement) {
qrCodeElement.innerHTML = '<p class="text-warning">Configuration not available for QR code</p>';
}
} }
} catch (err) {
alert('Falha ao copiar configuração. Por favor, copie manualmente.');
} }
document.body.removeChild(textArea);
}
// Legacy functions for backward compatibility {if $formatted_config}
function downloadConfig() { document.addEventListener('DOMContentLoaded', function() {
var textarea = document.querySelector('textarea'); configForQR = {$formatted_config|json_encode};
if (textarea) { generateQRCode(configForQR);
var config = textarea.value; });
{/if}
function downloadFormattedConfig() {
{if $formatted_config}
var config = {$formatted_config|json_encode};
var element = document.createElement('a'); var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(config)); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(config));
element.setAttribute('download', '{$peer_name|default:"wireguard"}.conf'); element.setAttribute('download', '{$peer_name|default:"wireguard"}.conf');
@@ -294,76 +154,128 @@ function downloadConfig() {
document.body.appendChild(element); document.body.appendChild(element);
element.click(); element.click();
document.body.removeChild(element); document.body.removeChild(element);
{/if}
} }
}
function copyConfig() { function copyFormattedConfig() {
var textarea = document.querySelector('textarea'); {if $formatted_config}
if (textarea) { var config = {$formatted_config|json_encode};
textarea.select(); if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(config).then(function() {
alert('Configuration copied to clipboard!');
}).catch(function(err) {
fallbackCopyTextToClipboard(config);
});
} else {
fallbackCopyTextToClipboard(config);
}
{/if}
}
function fallbackCopyTextToClipboard(text) {
var textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try { try {
document.execCommand('copy'); var successful = document.execCommand('copy');
alert('Configuração copiada para a área de transferência!'); if (successful) {
alert('Configuration copied to clipboard!');
} else {
alert('Failed to copy configuration. Please copy manually.');
}
} catch (err) { } catch (err) {
alert('Falha ao copiar configuração. Por favor, selecione e copie manualmente.'); alert('Failed to copy configuration. Please copy manually.');
}
document.body.removeChild(textArea);
}
// Legacy functions for backward compatibility
function downloadConfig() {
var textarea = document.querySelector('textarea');
if (textarea) {
var config = textarea.value;
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(config));
element.setAttribute('download', '{$peer_name|default:"wireguard"}.conf');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
} }
} }
}
function showInstructions() { function copyConfig() {
$('#instructionsModal').modal('show'); var textarea = document.querySelector('textarea');
} if (textarea) {
</script> textarea.select();
try {
document.execCommand('copy');
alert('Configuration copied to clipboard!');
} catch (err) {
alert('Failed to copy configuration. Please select and copy manually.');
}
}
}
<style> function showInstructions() {
.panel-heading .fa { $('#instructionsModal').modal('show');
margin-right: 5px; }
} </script>
.center-block { <style>
display: block; .panel-heading .fa {
margin-left: auto; margin-right: 5px;
margin-right: auto; }
}
code { .center-block {
word-break: break-all; display: block;
} margin-left: auto;
margin-right: auto;
}
.table-condensed td { code {
padding: 5px; word-break: break-all;
} }
.modal-body h5 { .table-condensed td {
color: #337ab7; padding: 5px;
margin-top: 20px; }
margin-bottom: 10px;
}
.modal-body h5:first-child { .modal-body h5 {
margin-top: 0; color: #337ab7;
} margin-top: 20px;
margin-bottom: 10px;
}
/***** Modal close button alignment *****/ .modal-body h5:first-child {
.modal-header { margin-top: 0;
position: relative; }
}
.modal-header .close {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
font-size: 28px;
line-height: 1;
}
.wireguard-config { /***** Modal close button alignment *****/
max-height: 350px; .modal-header {
overflow-y: auto; position: relative;
} }
.modal-header .close {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
font-size: 28px;
line-height: 1;
}
#qrcode canvas { .wireguard-config {
border: 1px solid #ddd; max-height: 350px;
border-radius: 4px; overflow-y: auto;
} }
</style>
#qrcode canvas {
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
+281
View File
@@ -0,0 +1,281 @@
{if $error}
<div class="alert alert-danger">
<strong>Erro:</strong> {$error}
</div>
{else}
<div class="row">
<div class="col-md-6">
{if $formatted_config}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<i class="fa fa-qrcode"></i> Código QR
</h3>
</div>
<div class="panel-body text-center">
<div id="qrcode" style="display: inline-block; margin-bottom: 10px;"></div>
<p class="text-muted">Escaneie este código QR com seu cliente WireGuard</p>
<div class="text-center" style="margin-top: 15px;">
<button class="btn btn-info btn-sm" onclick="showInstructions()">
<i class="fa fa-info-circle"></i> Instruções de Instalação
</button>
</div>
</div>
</div>
{else}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<i class="fa fa-qrcode"></i> Código QR
</h3>
</div>
<div class="panel-body text-center">
<p class="text-muted">Configuração não disponível</p>
</div>
</div>
{/if}
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<i class="fa fa-cog"></i> Configuração WireGuard
</h3>
</div>
<div class="panel-body">
{if $formatted_config}
<div class="form-group">
<label>Arquivo de Configuração:</label>
<pre class="wireguard-config" style="background: #f8f9fa;text-align:left; border: 1px solid #dee2e6; border-radius: 4px; padding: 15px; font-family: 'Courier New', monospace; font-size: 13px; line-height: 1.4; color: #333; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word; max-height: 350px; overflow-y: auto;">{$formatted_config|escape}</pre>
</div>
<div class="text-center">
<button class="btn btn-primary" onclick="downloadFormattedConfig()">
<i class="fa fa-download"></i> Baixar Configuração
</button>
<button class="btn btn-default" onclick="copyFormattedConfig()">
<i class="fa fa-copy"></i> Copiar para Área de Transferência
</button>
</div>
{else}
<p class="text-muted">Configuração não disponível</p>
{/if}
</div>
</div>
</div>
</div>
{/if}
{* Modal de Instruções *}
<div class="modal fade" id="instructionsModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Instruções de Configuração WireGuard</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Fechar"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<h5>Dispositivos Móveis (Android/iOS):</h5>
<ol>
<li>Instale o aplicativo WireGuard da Google Play Store ou App Store</li>
<li>Abra o aplicativo e toque no botão "+"</li>
<li>Selecione "Escanear código QR"</li>
<li>Escaneie o código QR exibido acima</li>
<li>Dê um nome ao seu túnel e salve</li>
</ol>
<h5>Desktop (Windows/macOS/Linux):</h5>
<ol>
<li>Baixe e instale o WireGuard de <a href="https://www.wireguard.com/install/" target="_blank">wireguard.com</a></li>
<li>Clique em "Adicionar Túnel" → "Adicionar túnel vazio..."</li>
<li>Copie a configuração acima e cole</li>
<li>Salve e ative o túnel</li>
</ol>
<h5>Linha de Comando (Linux):</h5>
<ol>
<li>Salve a configuração em um arquivo (ex: <code>wg0.conf</code>)</li>
<li>Execute: <code>sudo wg-quick up wg0</code></li>
<li>Para parar: <code>sudo wg-quick down wg0</code></li>
</ol>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Fechar</button>
</div>
</div>
</div>
</div>
<script src="https://api.qrserver.com/v1/create-qr-code/?size=250x250&format=svg&data=test" style="display:none;"></script>
<script>
var configForQR = null;
function generateQRCode(configText) {
var qrCodeElement = document.getElementById('qrcode');
if (qrCodeElement && configText && configText.trim() !== '') {
try {
var qrUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=' + encodeURIComponent(configText);
var img = document.createElement('img');
img.src = qrUrl;
img.alt = 'WireGuard QR Code';
img.style.border = '3px solid #ddd';
img.style.borderRadius = '4px';
img.onerror = function() {
qrCodeElement.innerHTML = '<p class="text-danger">Failed to generate QR code</p>';
};
qrCodeElement.innerHTML = '';
qrCodeElement.appendChild(img);
} catch (e) {
console.error('QR code generation failed:', e);
qrCodeElement.innerHTML = '<p class="text-danger">Failed to generate QR code</p>';
}
} else {
if (qrCodeElement) {
qrCodeElement.innerHTML = '<p class="text-warning">Configuration not available for QR code</p>';
}
}
}
{if $formatted_config}
document.addEventListener('DOMContentLoaded', function() {
configForQR = {$formatted_config|json_encode};
generateQRCode(configForQR);
});
{/if}
function downloadFormattedConfig() {
{if $formatted_config}
var config = {$formatted_config|json_encode};
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(config));
element.setAttribute('download', '{$peer_name|default:"wireguard"}.conf');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
{/if}
}
function copyFormattedConfig() {
{if $formatted_config}
var config = {$formatted_config|json_encode};
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(config).then(function() {
alert('Configuration copied to clipboard!');
}).catch(function(err) {
fallbackCopyTextToClipboard(config);
});
} else {
fallbackCopyTextToClipboard(config);
}
{/if}
}
function fallbackCopyTextToClipboard(text) {
var textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand('copy');
if (successful) {
alert('Configuration copied to clipboard!');
} else {
alert('Failed to copy configuration. Please copy manually.');
}
} catch (err) {
alert('Failed to copy configuration. Please copy manually.');
}
document.body.removeChild(textArea);
}
// Legacy functions for backward compatibility
function downloadConfig() {
var textarea = document.querySelector('textarea');
if (textarea) {
var config = textarea.value;
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(config));
element.setAttribute('download', '{$peer_name|default:"wireguard"}.conf');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
function copyConfig() {
var textarea = document.querySelector('textarea');
if (textarea) {
textarea.select();
try {
document.execCommand('copy');
alert('Configuration copied to clipboard!');
} catch (err) {
alert('Failed to copy configuration. Please select and copy manually.');
}
}
}
function showInstructions() {
$('#instructionsModal').modal('show');
}
</script>
<style>
.panel-heading .fa {
margin-right: 5px;
}
.center-block {
display: block;
margin-left: auto;
margin-right: auto;
}
code {
word-break: break-all;
}
.table-condensed td {
padding: 5px;
}
.modal-body h5 {
color: #337ab7;
margin-top: 20px;
margin-bottom: 10px;
}
.modal-body h5:first-child {
margin-top: 0;
}
/***** Modal close button alignment *****/
.modal-header {
position: relative;
}
.modal-header .close {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
font-size: 28px;
line-height: 1;
}
.wireguard-config {
max-height: 350px;
overflow-y: auto;
}
#qrcode canvas {
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
+13 -7
View File
@@ -360,8 +360,10 @@ function wgdashboard_TerminateAccount($params)
if(!isset($publicKey)) throw new Exception('Failed to terminate peer because it doesn\'t exist.'); if(!isset($publicKey)) throw new Exception('Failed to terminate peer because it doesn\'t exist.');
$configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0';
$publicKeyEncoded = urlencode($publicKey);
$deleteResult = wgdashboard_API($params, 'deletePeers/' . urlencode($configName) . '?id=' . $publicKeyEncoded, [], 'DELETE'); $deleteResult = wgdashboard_API($params, 'deletePeers/' . urlencode($configName), [
'peers' => [$publicKey]
], 'POST');
if($deleteResult['status_code'] !== 200) throw new Exception('Failed to delete the peer, received error code: ' . $deleteResult['status_code'] . '. Enable module debug log for more info.'); if($deleteResult['status_code'] !== 200) throw new Exception('Failed to delete the peer, received error code: ' . $deleteResult['status_code'] . '. Enable module debug log for more info.');
@@ -379,8 +381,10 @@ function wgdashboard_SuspendAccount($params)
if(!isset($publicKey)) throw new Exception('Failed to suspend peer because it doesn\'t exist.'); if(!isset($publicKey)) throw new Exception('Failed to suspend peer because it doesn\'t exist.');
$configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0';
$publicKeyEncoded = urlencode($publicKey);
$suspendResult = wgdashboard_API($params, 'restrictPeers/' . urlencode($configName) . '?id=' . $publicKeyEncoded, [], 'POST'); $suspendResult = wgdashboard_API($params, 'restrictPeers/' . urlencode($configName), [
'peers' => [$publicKey]
], 'POST');
if($suspendResult['status_code'] !== 200) throw new Exception('Failed to suspend the peer, received error code: ' . $suspendResult['status_code'] . '. Enable module debug log for more info.'); if($suspendResult['status_code'] !== 200) throw new Exception('Failed to suspend the peer, received error code: ' . $suspendResult['status_code'] . '. Enable module debug log for more info.');
} catch(Exception $err) { } catch(Exception $err) {
@@ -397,8 +401,10 @@ function wgdashboard_UnsuspendAccount($params)
if(!isset($publicKey)) throw new Exception('Failed to unsuspend peer because it doesn\'t exist.'); if(!isset($publicKey)) throw new Exception('Failed to unsuspend peer because it doesn\'t exist.');
$configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0'; $configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0';
$publicKeyEncoded = urlencode($publicKey);
$unsuspendResult = wgdashboard_API($params, 'allowAccessPeers/' . urlencode($configName) . '?id=' . $publicKeyEncoded, [], 'POST'); $unsuspendResult = wgdashboard_API($params, 'allowAccessPeers/' . urlencode($configName), [
'peers' => [$publicKey]
], 'POST');
if($unsuspendResult['status_code'] !== 200) throw new Exception('Failed to unsuspend the peer, received error code: ' . $unsuspendResult['status_code'] . '. Enable module debug log for more info.'); if($unsuspendResult['status_code'] !== 200) throw new Exception('Failed to unsuspend the peer, received error code: ' . $unsuspendResult['status_code'] . '. Enable module debug log for more info.');
} catch(Exception $err) { } catch(Exception $err) {
@@ -421,7 +427,7 @@ function wgdashboard_ChangePackage($params)
'allowed_ip' => $allowedIps, 'allowed_ip' => $allowedIps,
]; ];
$updateResult = wgdashboard_API($params, 'updatePeer/' . urlencode($configName) . '/' . urlencode($peerId), $updateData, 'POST'); $updateResult = wgdashboard_API($params, 'updatePeerSettings/' . urlencode($configName) . '/' . urlencode($peerId), $updateData, 'POST');
if($updateResult['status_code'] !== 200) throw new Exception('Failed to update the peer, received error code: ' . $updateResult['status_code'] . '. Enable module debug log for more info.'); if($updateResult['status_code'] !== 200) throw new Exception('Failed to update the peer, received error code: ' . $updateResult['status_code'] . '. Enable module debug log for more info.');
} catch(Exception $err) { } catch(Exception $err) {