mirror of
https://github.com/IdalecioSch/WGDashboard-WHMCS.git
synced 2026-07-27 16:25:49 -03:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b33bef3d71 | |||
| d5a38398f7 | |||
| 662b1e58ee | |||
| aa337f9b29 |
@@ -12,36 +12,34 @@ WHMCS module for integration with [WGDashboard](https://github.com/donaldzou/WGD
|
||||
## 🚀 Installation
|
||||
|
||||
1. Upload the module files to the WHMCS directory:
|
||||
|
||||
```
|
||||
/modules/servers/wgdashboard/
|
||||
```
|
||||
|
||||
2. File structure:
|
||||
|
||||
```
|
||||
modules/server/wgdashboard/
|
||||
├── wgdashboard.php
|
||||
└── clientarea.tpl
|
||||
```
|
||||
|
||||
3. In the WHMCS admin panel, navigate to:
|
||||
**Setup → Products/Services → Servers → Add New Server**
|
||||
|
||||
4. Configure the server:
|
||||
|
||||
- **Hostname**: IP or domain of WGDashboard (e.g., `10.0.238.222` or `vpn.example.com`)
|
||||
- **Module**: WGDashboard
|
||||
- **Port**: API port (e.g., `10086`)
|
||||
- **Password**: WGDashboard API Key
|
||||
|
||||
5. Click **Test Connection** to validate the configuration.
|
||||
|
||||
## ⚙️ Product Configuration
|
||||
|
||||
1. Create or edit a product in **Setup → Products/Services**
|
||||
|
||||
2. In the **Module Settings** tab:
|
||||
|
||||
- **Module Name**: WGDashboard
|
||||
- **Server Group**: Select the configured server
|
||||
|
||||
3. **Configuration Options**:
|
||||
|
||||
- **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:
|
||||
|
||||
- `{{client_email}}` - Client's email
|
||||
- `{{domain}}` - Service domain
|
||||
- `{{serviceid}}` - Service ID in WHMCS
|
||||
@@ -82,7 +81,6 @@ Client Area:
|
||||
- 📄 **Formatted Configuration**: Ready-to-use WireGuard configuration file
|
||||
- 📋 **Download**: Button to download `.conf` file
|
||||
|
||||
|
||||
### Admin Panel
|
||||
|
||||
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 Sent**: Volume of data sent From server to peer
|
||||
|
||||
|
||||
## 🌐 WGDashboard Configuration
|
||||
|
||||
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
|
||||
|
||||
**Error 401/403**: Verify API Key
|
||||
|
||||
```
|
||||
Solution: Confirm that the API Key is correct in the server's Password field
|
||||
```
|
||||
|
||||
**Error 404**: Endpoint not found
|
||||
|
||||
```
|
||||
Solution: Check if your WGDashboard version supports the API
|
||||
```
|
||||
|
||||
**Error 500**: WGDashboard internal error
|
||||
|
||||
```
|
||||
Solution: Check WGDashboard logs on the server
|
||||
```
|
||||
@@ -134,6 +134,7 @@ Solution: Check WGDashboard logs on the server
|
||||
### QR Code Doesn't Appear
|
||||
|
||||
Check:
|
||||
|
||||
- If JavaScript is enabled in the browser
|
||||
- Browser console for errors (F12)
|
||||
- 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.
|
||||
|
||||
✅ **Correct**:
|
||||
|
||||
- Hostname: `10.0.0.55`
|
||||
- Port: `10086`
|
||||
|
||||
❌ **Incorrect**:
|
||||
|
||||
- Hostname: `10.0.0.55:10086`
|
||||
- Port: (empty)
|
||||
|
||||
|
||||
## 🔄 Compatibility
|
||||
|
||||
- **WHMCS**: 7.0+
|
||||
@@ -182,6 +184,7 @@ Contributions are welcome! To contribute:
|
||||
## 📋 Changelog
|
||||
|
||||
### v1.0.0
|
||||
|
||||
- ✨ Initial release
|
||||
- ✅ Peer creation, suspension, reactivation, and termination
|
||||
- 📱 Client area with QR code and formatted configuration
|
||||
@@ -189,4 +192,4 @@ Contributions are welcome! To contribute:
|
||||
- 🔐 Local WireGuard key generation
|
||||
- 📊 Admin panel with peer statistics
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
+235
-323
@@ -1,292 +1,152 @@
|
||||
{if $error}
|
||||
<div class="alert alert-danger">
|
||||
<strong>Erro:</strong> {$error}
|
||||
</div>
|
||||
{else}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{if $formatted_config}
|
||||
{if $error}
|
||||
<div class="alert alert-danger">
|
||||
<strong>Error:</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> 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-heading">
|
||||
<h3 class="panel-title">
|
||||
<i class="fa fa-qrcode"></i> Código QR
|
||||
<i class="fa fa-cog"></i> WireGuard Configuration
|
||||
</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 class="panel-body">
|
||||
{if $formatted_config}
|
||||
<div class="form-group">
|
||||
<label>Configuration File:</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> 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>
|
||||
{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>
|
||||
{/if}
|
||||
|
||||
<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>
|
||||
{* Instructions Modal *}
|
||||
<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">WireGuard Setup Instructions</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</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; 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 class="modal-body">
|
||||
<h5>Mobile Devices (Android/iOS):</h5>
|
||||
<ol>
|
||||
<li>Install the WireGuard app from Google Play Store or App Store</li>
|
||||
<li>Open the app and tap the "+" button</li>
|
||||
<li>Select "Scan from QR code"</li>
|
||||
<li>Scan the QR code displayed above</li>
|
||||
<li>Give your tunnel a name and save</li>
|
||||
</ol>
|
||||
|
||||
<h5>Desktop (Windows/macOS/Linux):</h5>
|
||||
<ol>
|
||||
<li>Download and install WireGuard from <a href="https://www.wireguard.com/install/" target="_blank">wireguard.com</a></li>
|
||||
<li>Click "Add Tunnel" → "Add empty tunnel..."</li>
|
||||
<li>Copy the configuration above and paste it</li>
|
||||
<li>Save and activate the tunnel</li>
|
||||
</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>
|
||||
{/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">×</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>
|
||||
<script src="https://api.qrserver.com/v1/create-qr-code/?size=250x250&format=svg&data=test" style="display:none;"></script>
|
||||
<script>
|
||||
var configForQR = null;
|
||||
|
||||
<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://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');
|
||||
function generateQRCode(configText) {
|
||||
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 {
|
||||
QRCode.toCanvas(qrCodeElement, configText, {
|
||||
width: 250,
|
||||
margin: 3,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF'
|
||||
}
|
||||
}, function(error) {
|
||||
if (error) {
|
||||
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>';
|
||||
} else {
|
||||
console.log('QR Code gerado com sucesso');
|
||||
}
|
||||
});
|
||||
} 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);
|
||||
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>';
|
||||
}
|
||||
}, 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 {
|
||||
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
|
||||
function downloadConfig() {
|
||||
var textarea = document.querySelector('textarea');
|
||||
if (textarea) {
|
||||
var config = textarea.value;
|
||||
{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');
|
||||
@@ -294,76 +154,128 @@ function downloadConfig() {
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
{/if}
|
||||
}
|
||||
}
|
||||
|
||||
function copyConfig() {
|
||||
var textarea = document.querySelector('textarea');
|
||||
if (textarea) {
|
||||
textarea.select();
|
||||
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 {
|
||||
document.execCommand('copy');
|
||||
alert('Configuração copiada para a área de transferência!');
|
||||
var successful = document.execCommand('copy');
|
||||
if (successful) {
|
||||
alert('Configuration copied to clipboard!');
|
||||
} else {
|
||||
alert('Failed to copy configuration. Please copy manually.');
|
||||
}
|
||||
} 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() {
|
||||
$('#instructionsModal').modal('show');
|
||||
}
|
||||
</script>
|
||||
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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<style>
|
||||
.panel-heading .fa {
|
||||
margin-right: 5px;
|
||||
}
|
||||
function showInstructions() {
|
||||
$('#instructionsModal').modal('show');
|
||||
}
|
||||
</script>
|
||||
|
||||
.center-block {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
<style>
|
||||
.panel-heading .fa {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
code {
|
||||
word-break: break-all;
|
||||
}
|
||||
.center-block {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.table-condensed td {
|
||||
padding: 5px;
|
||||
}
|
||||
code {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.modal-body h5 {
|
||||
color: #337ab7;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.table-condensed td {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.modal-body h5:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.modal-body h5 {
|
||||
color: #337ab7;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/***** 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;
|
||||
}
|
||||
.modal-body h5:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.wireguard-config {
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
/***** 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;
|
||||
}
|
||||
|
||||
#qrcode canvas {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
.wireguard-config {
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#qrcode canvas {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -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">×</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>
|
||||
@@ -360,8 +360,10 @@ function wgdashboard_TerminateAccount($params)
|
||||
if(!isset($publicKey)) throw new Exception('Failed to terminate peer because it doesn\'t exist.');
|
||||
|
||||
$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.');
|
||||
|
||||
@@ -379,8 +381,10 @@ function wgdashboard_SuspendAccount($params)
|
||||
if(!isset($publicKey)) throw new Exception('Failed to suspend peer because it doesn\'t exist.');
|
||||
|
||||
$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.');
|
||||
} 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.');
|
||||
|
||||
$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.');
|
||||
} catch(Exception $err) {
|
||||
@@ -421,7 +427,7 @@ function wgdashboard_ChangePackage($params)
|
||||
'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.');
|
||||
} catch(Exception $err) {
|
||||
|
||||
Reference in New Issue
Block a user