mirror of
https://github.com/IdalecioSch/WGDashboard-WHMCS.git
synced 2026-07-28 00:30:07 -03:00
made it all
This commit is contained in:
@@ -0,0 +1,192 @@
|
|||||||
|
# WGDashboard WHMCS Module
|
||||||
|
|
||||||
|
WHMCS module for integration with [WGDashboard](https://github.com/donaldzou/WGDashboard), enabling automated WireGuard peer management through WHMCS.
|
||||||
|
|
||||||
|
## 📋 Requirements
|
||||||
|
|
||||||
|
- WHMCS 7.0 or higher
|
||||||
|
- WGDashboard v4.0+ with API enabled
|
||||||
|
- PHP 7.4 or higher
|
||||||
|
- PHP cURL extension enabled
|
||||||
|
|
||||||
|
## 🚀 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`)
|
||||||
|
- **Config Name**: WireGuard configuration name in the dashboard (default: `wg0`)
|
||||||
|
- **Peer Template**: (Optional) JSON template to customize peer creation
|
||||||
|
|
||||||
|
### Peer Template Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"DNS": "8.8.8.8,1.1.1.1",
|
||||||
|
"mtu": 1420,
|
||||||
|
"keepalive": 25
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Available placeholders:
|
||||||
|
- `{{client_email}}` - Client's email
|
||||||
|
- `{{domain}}` - Service domain
|
||||||
|
- `{{serviceid}}` - Service ID in WHMCS
|
||||||
|
|
||||||
|
## 🔧 Features
|
||||||
|
|
||||||
|
### Peer Automation
|
||||||
|
|
||||||
|
Module options:
|
||||||
|
|
||||||
|
- ✅ **Creation**: Automatically generates keys and creates peer in WGDashboard
|
||||||
|
- ✅ **Suspension**: Restricts peer access when service is suspended
|
||||||
|
- ✅ **Reactivation**: Restores access when service is reactivated
|
||||||
|
- ✅ **Termination**: Completely removes peer when service is cancelled
|
||||||
|
|
||||||
|
### Client Area
|
||||||
|
|
||||||
|
Client Area:
|
||||||
|
|
||||||
|
- 📱 **QR Code**: For quick configuration on mobile devices
|
||||||
|
- 📄 **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:
|
||||||
|
|
||||||
|
- **Public Key**: Peer's public key
|
||||||
|
- **Peer Name**: Peer identifier name
|
||||||
|
- **Allowed IPs**: Allowed IPs
|
||||||
|
- **Status**: Current peer state
|
||||||
|
- **Latest Handshake**: Last handshake with server
|
||||||
|
- **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:
|
||||||
|
|
||||||
|
1. Access WGDashboard settings
|
||||||
|
2. Navigate to **Settings → API**
|
||||||
|
3. Enable the API and generate a key
|
||||||
|
4. Use this key in the **Password** field of the server configuration in WHMCS
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### Peer Not Created
|
||||||
|
|
||||||
|
1. Enable **Module Debug Log** in WHMCS
|
||||||
|
2. Navigate to **Utilities → Logs → Module Log**
|
||||||
|
3. Look for "WGDashboard-WHMCS" entries
|
||||||
|
4. Analyze request and response details
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
### Port Not Recognized
|
||||||
|
|
||||||
|
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+
|
||||||
|
- **WGDashboard**: v4.0+
|
||||||
|
- **PHP**: 7.4, 8.0, 8.1, 8.2
|
||||||
|
- **WireGuard**: All versions
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
This module is provided "as is", without warranties of any kind.
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! To contribute:
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a branch for your feature
|
||||||
|
3. Commit your changes
|
||||||
|
4. Push to the branch
|
||||||
|
5. Open a Pull Request
|
||||||
|
|
||||||
|
## 🔗 Useful Links
|
||||||
|
|
||||||
|
- [WGDashboard GitHub](https://github.com/donaldzou/WGDashboard)
|
||||||
|
- [WHMCS Developer Documentation](https://developers.whmcs.com/)
|
||||||
|
- [WireGuard Official Site](https://www.wireguard.com/)
|
||||||
|
|
||||||
|
## 📋 Changelog
|
||||||
|
|
||||||
|
### v1.0.0
|
||||||
|
- ✨ Initial release
|
||||||
|
- ✅ Peer creation, suspension, reactivation, and termination
|
||||||
|
- 📱 Client area with QR code and formatted configuration
|
||||||
|
- 🌐 Portuguese translation
|
||||||
|
- 🔐 Local WireGuard key generation
|
||||||
|
- 📊 Admin panel with peer statistics
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
{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; 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://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');
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}, 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.');
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
|
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('Configuração copiada para a área de transferência!');
|
||||||
|
} catch (err) {
|
||||||
|
alert('Falha ao copiar configuração. Por favor, selecione e copie manualmente.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
@@ -0,0 +1,584 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
if (!defined("WHMCS")) {
|
||||||
|
die("This file cannot be accessed directly");
|
||||||
|
}
|
||||||
|
|
||||||
|
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||||
|
|
||||||
|
function wgdashboard_MetaData(){
|
||||||
|
return array(
|
||||||
|
'DisplayName' => 'WGDashboard',
|
||||||
|
'DefaultNonSSLPort' => '10086',
|
||||||
|
'DefaultSSLPort' => '443',
|
||||||
|
'APIVersion' => '1.0',
|
||||||
|
'RequiresServer' => true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_GetHostname(array $params) {
|
||||||
|
$hostname = $params['serverhostname'] ?? '';
|
||||||
|
if ($hostname === '') {
|
||||||
|
throw new Exception('Could not find the WGDashboard\'s hostname - did you configure server group for the product?');
|
||||||
|
}
|
||||||
|
|
||||||
|
// WHMCS sometimes stores placeholders like DOT/DASH
|
||||||
|
foreach ([ 'DOT' => '.', 'DASH' => '-' ] as $from => $to) {
|
||||||
|
$hostname = str_replace($from, $to, $hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hostname = preg_replace('#^https?://#i', '', trim($hostname));
|
||||||
|
$hostname = rtrim($hostname, '/');
|
||||||
|
|
||||||
|
// Strip port from hostname; always use $params['serverport']
|
||||||
|
if (strpos($hostname, ':') !== false) {
|
||||||
|
$hostname = explode(':', $hostname)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$protocol = (!empty($params['serversecure'])) ? 'https://' : 'http://';
|
||||||
|
$port = isset($params['serverport']) && is_numeric($params['serverport']) && (int)$params['serverport'] > 0
|
||||||
|
? (int)$params['serverport']
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return $protocol . $hostname . ($port ? (':' . $port) : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_API(array $params, $endpoint, array $data = [], $method = "GET", $dontLog = false) {
|
||||||
|
$hostname = wgdashboard_GetHostname($params);
|
||||||
|
$url = $hostname . '/api/' . $endpoint;
|
||||||
|
|
||||||
|
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt($curl, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
|
||||||
|
curl_setopt($curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
|
||||||
|
curl_setopt($curl, CURLOPT_USERAGENT, "WGDashboard-WHMCS");
|
||||||
|
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||||
|
curl_setopt($curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_301);
|
||||||
|
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
"wg-dashboard-apikey: " . $params['serverpassword'],
|
||||||
|
"Accept: application/json",
|
||||||
|
"Content-Type: application/json",
|
||||||
|
];
|
||||||
|
|
||||||
|
if($method === 'POST' || $method === 'PATCH' || $method === 'PUT') {
|
||||||
|
if(!empty($data)) {
|
||||||
|
$jsonData = json_encode($data);
|
||||||
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $jsonData);
|
||||||
|
$headers[] = "Content-Length: " . strlen($jsonData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
|
||||||
|
$response = curl_exec($curl);
|
||||||
|
$responseData = json_decode($response, true);
|
||||||
|
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||||
|
$curlErr = curl_error($curl);
|
||||||
|
|
||||||
|
if($response === false && !$dontLog) logModuleCall("WGDashboard-WHMCS", "CURL ERROR", $curlErr, "");
|
||||||
|
|
||||||
|
curl_close($curl);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'status_code' => $httpCode,
|
||||||
|
'data' => $responseData
|
||||||
|
];
|
||||||
|
|
||||||
|
if(!$dontLog) logModuleCall("WGDashboard-WHMCS", $method . " - " . $url,
|
||||||
|
isset($data) ? json_encode($data) : "",
|
||||||
|
print_r($result, true));
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_Error($func, $params, Exception $err) {
|
||||||
|
logModuleCall("WGDashboard-WHMCS", $func, $params, $err->getMessage(), $err->getTraceAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function wgdashboard_ConfigOptions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Allowed IPs' => [
|
||||||
|
'Type' => 'text',
|
||||||
|
'Size' => '40',
|
||||||
|
'Description' => 'Default allowed IPs for created peers (e.g. 10.10.0.0/24 or 0.0.0.0/0)',
|
||||||
|
'Default' => '0.0.0.0/0',
|
||||||
|
],
|
||||||
|
'Config Name' => [
|
||||||
|
'Type' => 'text',
|
||||||
|
'Size' => '30',
|
||||||
|
'Description' => 'WireGuard config name on WGDashboard (if you manage multiple configs)',
|
||||||
|
'Default' => 'wg0',
|
||||||
|
],
|
||||||
|
'Peer Template' => [
|
||||||
|
'Type' => 'textarea',
|
||||||
|
'Description' => "Optional JSON template to merge into peer creation payload. Use {{client_email}} or {{domain}} placeholders.",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_TestConnection(array $params) {
|
||||||
|
$solutions = [
|
||||||
|
0 => "Check module debug log for more detailed error.",
|
||||||
|
401 => "Authorization header either missing or not provided.",
|
||||||
|
403 => "Double check the API key (which should be in the server password field).",
|
||||||
|
404 => "Result not found.",
|
||||||
|
422 => "Validation error.",
|
||||||
|
500 => "WGDashboard errored, check panel logs.",
|
||||||
|
];
|
||||||
|
|
||||||
|
$err = "";
|
||||||
|
try {
|
||||||
|
$response = wgdashboard_API($params, 'getWireguardConfigurations');
|
||||||
|
|
||||||
|
if($response['status_code'] !== 200) {
|
||||||
|
$status_code = $response['status_code'];
|
||||||
|
$err = "Invalid status_code received: " . $status_code . ". Possible solutions: "
|
||||||
|
. (isset($solutions[$status_code]) ? $solutions[$status_code] : "None.");
|
||||||
|
}
|
||||||
|
} catch(Exception $e) {
|
||||||
|
wgdashboard_Error(__FUNCTION__, $params, $e);
|
||||||
|
$err = $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"success" => $err === "",
|
||||||
|
"error" => $err,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_GetPeerID(array $params, $raw = false) {
|
||||||
|
// Try to get from database directly first
|
||||||
|
if (!empty($params['serviceid'])) {
|
||||||
|
try {
|
||||||
|
$service = Capsule::table('tblhosting')->where('id', $params['serviceid'])->first();
|
||||||
|
if ($service && !empty($service->username)) {
|
||||||
|
if ($raw) {
|
||||||
|
// Get peer info by searching in the configuration
|
||||||
|
$configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0';
|
||||||
|
$configResult = wgdashboard_API($params, 'getWireguardConfigurationInfo?configurationName=' . urlencode($configName), [], 'GET', false); // Force logging
|
||||||
|
|
||||||
|
// Debug: Force logging for this call even with dontLog=true
|
||||||
|
if(function_exists('logModuleCall')) {
|
||||||
|
logModuleCall("WGDashboard-WHMCS", "GetPeerID RAW Debug", "Config: " . $configName . ", Looking for: " . $service->username, "Status: " . $configResult['status_code'] . "\nResponse: " . print_r($configResult, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if($configResult['status_code'] === 200 && isset($configResult['data']['data']['configurationPeers'])) {
|
||||||
|
foreach($configResult['data']['data']['configurationPeers'] as $peer) {
|
||||||
|
// Check both 'id' and 'public_key' fields
|
||||||
|
if(($peer['id'] ?? '') === $service->username || ($peer['public_key'] ?? '') === $service->username) {
|
||||||
|
return ['status_code' => 200, 'data' => $peer];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: If not found, log what peers exist
|
||||||
|
if(function_exists('logModuleCall')) {
|
||||||
|
$peerIds = array_map(function($p) { return $p['id'] ?? $p['public_key'] ?? 'no-id'; }, $configResult['data']['data']['configurationPeers']);
|
||||||
|
logModuleCall("WGDashboard-WHMCS", "GetPeerID NOT FOUND", "Looking for: " . $service->username, "Available peers: " . implode(', ', $peerIds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $service->username; // This is the public key/id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Continue to other methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get peer public key from service username first
|
||||||
|
if (!empty($params['serviceusername'])) {
|
||||||
|
if ($raw) {
|
||||||
|
// Get peer info by searching in the configuration
|
||||||
|
$configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0';
|
||||||
|
$configResult = wgdashboard_API($params, 'getWireguardConfigurationInfo?configurationName=' . urlencode($configName), [], 'GET', true);
|
||||||
|
if($configResult['status_code'] === 200 && isset($configResult['data']['data']['configurationPeers'])) {
|
||||||
|
foreach($configResult['data']['data']['configurationPeers'] as $peer) {
|
||||||
|
// Check both 'id' and 'public_key' fields
|
||||||
|
if(($peer['id'] ?? '') === $params['serviceusername'] || ($peer['public_key'] ?? '') === $params['serviceusername']) {
|
||||||
|
return ['status_code' => 200, 'data' => $peer];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $params['serviceusername']; // This is the public key/id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get from custom fields
|
||||||
|
if (!empty($params['customfields']['peer_public_key'])) {
|
||||||
|
if ($raw) {
|
||||||
|
$configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0';
|
||||||
|
$configResult = wgdashboard_API($params, 'getWireguardConfigurationInfo?configurationName=' . urlencode($configName), [], 'GET', true);
|
||||||
|
if($configResult['status_code'] === 200 && isset($configResult['data']['data']['configurationPeers'])) {
|
||||||
|
foreach($configResult['data']['data']['configurationPeers'] as $peer) {
|
||||||
|
// Check both 'id' and 'public_key' fields
|
||||||
|
if(($peer['id'] ?? '') === $params['customfields']['peer_public_key'] || ($peer['public_key'] ?? '') === $params['customfields']['peer_public_key']) {
|
||||||
|
return ['status_code' => 200, 'data' => $peer];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $params['customfields']['peer_public_key'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_GenerateKeyPair() {
|
||||||
|
if (function_exists('exec') && !in_array('exec', explode(',', ini_get('disable_functions')))) {
|
||||||
|
$privateKey = '';
|
||||||
|
$publicKey = '';
|
||||||
|
|
||||||
|
exec('wg genkey 2>/dev/null', $output, $return_code);
|
||||||
|
if ($return_code === 0 && !empty($output[0])) {
|
||||||
|
$privateKey = trim($output[0]);
|
||||||
|
|
||||||
|
// Generate public key from private key
|
||||||
|
$publicOutput = [];
|
||||||
|
exec("echo '$privateKey' | wg pubkey 2>/dev/null", $publicOutput, $pub_return_code);
|
||||||
|
if ($pub_return_code === 0 && !empty($publicOutput[0])) {
|
||||||
|
$publicKey = trim($publicOutput[0]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'private_key' => $privateKey,
|
||||||
|
'public_key' => $publicKey
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'private_key' => '',
|
||||||
|
'public_key' => ''
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_CreateAccount(array $params)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$peerId = wgdashboard_GetPeerID($params);
|
||||||
|
if(isset($peerId)) throw new Exception('Failed to create peer because it is already created.');
|
||||||
|
|
||||||
|
$clientEmail = $params['clientsdetails']['email'] ?? '';
|
||||||
|
$domain = $params['domain'] ?? '';
|
||||||
|
$serviceId = $params['serviceid'] ?? null;
|
||||||
|
|
||||||
|
$allowedIpsDefault = $params['configoptions']['Allowed IPs'] ?? $params['configoption1'] ?? '0.0.0.0/0';
|
||||||
|
$configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0';
|
||||||
|
|
||||||
|
$peerName = preg_replace('/[^a-zA-Z0-9_\-\.]/', '_', ($params['clientsdetails']['email'] ?? 'peer') . '_' . ($serviceId ?: uniqid()));
|
||||||
|
|
||||||
|
$keys = wgdashboard_GenerateKeyPair();
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
"name" => $peerName,
|
||||||
|
"DNS" => "1.1.1.1",
|
||||||
|
"endpoint_allowed_ip" => "0.0.0.0/0",
|
||||||
|
"keepalive" => 21,
|
||||||
|
"mtu" => 1420,
|
||||||
|
"preshared_key" => "",
|
||||||
|
"preshared_key_bulkAdd" => false,
|
||||||
|
"advanced_security" => "off",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Only add keys if they were successfully generated
|
||||||
|
if (!empty($keys['private_key']) && !empty($keys['public_key'])) {
|
||||||
|
$payload["private_key"] = $keys['private_key'];
|
||||||
|
$payload["public_key"] = $keys['public_key'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($params['configoptions']['Peer Template'])) {
|
||||||
|
$template = $params['configoptions']['Peer Template'];
|
||||||
|
$template = str_replace(['{{client_email}}', '{{domain}}', '{{serviceid}}'], [$clientEmail, $domain, $serviceId], $template);
|
||||||
|
$jsonTemplate = @json_decode($template, true);
|
||||||
|
if (is_array($jsonTemplate)) {
|
||||||
|
$payload = array_merge($payload, $jsonTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = wgdashboard_API($params, 'addPeers/' . urlencode($configName), $payload, 'POST');
|
||||||
|
|
||||||
|
if($result['status_code'] !== 200) {
|
||||||
|
throw new Exception('WGDashboard create failed: HTTP ' . $result['status_code'] . ' - ' . print_r($result['data'], true));
|
||||||
|
}
|
||||||
|
|
||||||
|
$publicKeyToStore = '';
|
||||||
|
if (!empty($keys['public_key'])) {
|
||||||
|
$publicKeyToStore = $keys['public_key'];
|
||||||
|
} else {
|
||||||
|
if (isset($result['data']['data']) && is_array($result['data']['data']) && !empty($result['data']['data'][0])) {
|
||||||
|
$createdPeer = $result['data']['data'][0];
|
||||||
|
$publicKeyToStore = $createdPeer['id'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($publicKeyToStore)) {
|
||||||
|
sleep(1);
|
||||||
|
$configResult = wgdashboard_API($params, 'getWireguardConfigurationInfo?configurationName=' . urlencode($configName), [], 'GET', true);
|
||||||
|
if($configResult['status_code'] === 200 && isset($configResult['data']['data']['configurationPeers'])) {
|
||||||
|
foreach($configResult['data']['data']['configurationPeers'] as $peer) {
|
||||||
|
if($peer['name'] === $peerName) {
|
||||||
|
$publicKeyToStore = $peer['id'] ?? $peer['public_key'] ?? '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($publicKeyToStore)) {
|
||||||
|
throw new Exception('Failed to obtain public key for created peer');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Capsule::table('tblhosting')->where('id', $params['serviceid'])->update([
|
||||||
|
'username' => $publicKeyToStore,
|
||||||
|
'password' => '',
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage() . "<br />" . $e->getTraceAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch(Exception $err) {
|
||||||
|
return $err->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_TerminateAccount($params)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$publicKey = wgdashboard_GetPeerID($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');
|
||||||
|
|
||||||
|
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.');
|
||||||
|
|
||||||
|
} catch(Exception $err) {
|
||||||
|
return $err->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_SuspendAccount($params)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$publicKey = wgdashboard_GetPeerID($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');
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return $err->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_UnsuspendAccount($params)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$publicKey = wgdashboard_GetPeerID($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');
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return $err->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_ChangePackage($params)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$peerId = wgdashboard_GetPeerID($params);
|
||||||
|
if(!isset($peerId)) throw new Exception('Failed to change package because peer doesn\'t exist.');
|
||||||
|
|
||||||
|
$configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0';
|
||||||
|
$allowedIps = $params['configoptions']['Allowed IPs'] ?? $params['configoption1'] ?? '0.0.0.0/0';
|
||||||
|
|
||||||
|
$updateData = [
|
||||||
|
'allowed_ip' => $allowedIps,
|
||||||
|
];
|
||||||
|
|
||||||
|
$updateResult = wgdashboard_API($params, 'updatePeer/' . 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) {
|
||||||
|
return $err->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_AdminServicesTabFields($params)
|
||||||
|
{
|
||||||
|
$fields = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$publicKey = wgdashboard_GetPeerID($params);
|
||||||
|
$fields['Public Key'] = $publicKey ?: 'Not found';
|
||||||
|
|
||||||
|
if($publicKey) {
|
||||||
|
$peerData = wgdashboard_GetPeerID($params, true);
|
||||||
|
|
||||||
|
if($peerData && $peerData['status_code'] === 200 && isset($peerData['data'])) {
|
||||||
|
$peer = $peerData['data'];
|
||||||
|
|
||||||
|
$fields['Peer Name'] = $peer['name'] ?? 'N/A';
|
||||||
|
$fields['Allowed IPs'] = $peer['allowed_ip'] ?? 'N/A';
|
||||||
|
$fields['Status'] = ucfirst($peer['status'] ?? 'N/A');
|
||||||
|
|
||||||
|
$latestHandshake = $peer['latest_handshake'] ?? null;
|
||||||
|
if ($latestHandshake && is_numeric($latestHandshake) && $latestHandshake > 0) {
|
||||||
|
$fields['Latest Handshake'] = date('Y-m-d H:i:s', $latestHandshake);
|
||||||
|
} elseif ($latestHandshake && preg_match('/^\d+:\d+:\d+$/', $latestHandshake)) {
|
||||||
|
$fields['Latest Handshake'] = $latestHandshake . ' ago';
|
||||||
|
} else {
|
||||||
|
$fields['Latest Handshake'] = 'Never';
|
||||||
|
}
|
||||||
|
|
||||||
|
$cumuReceive = floatval($peer['cumu_receive'] ?? 0);
|
||||||
|
$totalReceive = floatval($peer['total_receive'] ?? 0);
|
||||||
|
$totalReceivedMB = $cumuReceive + $totalReceive;
|
||||||
|
|
||||||
|
if ($totalReceivedMB >= 1) {
|
||||||
|
$fields['Data Received P->S'] = number_format($totalReceivedMB, 2) . ' GB';
|
||||||
|
} else {
|
||||||
|
$fields['Data Received P->S'] = number_format($totalReceivedMB*1024, 2) . ' MB';
|
||||||
|
}
|
||||||
|
|
||||||
|
$cumuSent = floatval($peer['cumu_sent'] ?? 0);
|
||||||
|
$totalSent = floatval($peer['total_sent'] ?? 0);
|
||||||
|
$totalSentMB = $cumuSent + $totalSent;
|
||||||
|
|
||||||
|
if ($totalSentMB >= 1) {
|
||||||
|
$fields['Data Sent S->P'] = number_format($totalSentMB, 2) . ' GB';
|
||||||
|
} else {
|
||||||
|
$fields['Data Sent S->P'] = number_format($totalSentMB*1024, 2) . ' MB';
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalDataMB = $totalReceivedMB + $totalSentMB;
|
||||||
|
if ($totalDataMB >= 1) {
|
||||||
|
$fields['Total Data'] = number_format($totalDataMB, 2) . ' GB';
|
||||||
|
} else {
|
||||||
|
$fields['Total Data'] = number_format($totalDataMB*1024, 2) . ' MB';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$fields['Error'] = $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wgdashboard_ClientArea(array $params) {
|
||||||
|
if($params['moduletype'] !== 'wgdashboard') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$hostname = wgdashboard_GetHostname($params);
|
||||||
|
$publicKey = wgdashboard_GetPeerID($params);
|
||||||
|
|
||||||
|
if(!$publicKey) {
|
||||||
|
return [
|
||||||
|
'templatefile' => 'clientarea',
|
||||||
|
'vars' => [
|
||||||
|
'error' => 'Peer não encontrado ou ainda não criado.',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$configName = $params['configoptions']['Config Name'] ?? $params['configoption2'] ?? 'wg0';
|
||||||
|
$peerData = wgdashboard_GetPeerID($params, true);
|
||||||
|
|
||||||
|
if(!$peerData || $peerData['status_code'] !== 200) {
|
||||||
|
return [
|
||||||
|
'templatefile' => 'clientarea',
|
||||||
|
'vars' => [
|
||||||
|
'error' => 'Falha ao recuperar dados do peer.',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$peer = $peerData['data'];
|
||||||
|
|
||||||
|
$formattedConfig = '';
|
||||||
|
if (!empty($peer['private_key'])) {
|
||||||
|
$formattedConfig = "[Interface]\n";
|
||||||
|
$formattedConfig .= "PrivateKey = " . ($peer['private_key'] ?? '') . "\n";
|
||||||
|
$formattedConfig .= "Address = " . ($peer['allowed_ip'] ?? '10.0.0.1/32') . "\n";
|
||||||
|
$formattedConfig .= "MTU = " . ($peer['mtu'] ?? '1420') . "\n";
|
||||||
|
$formattedConfig .= "DNS = " . ($peer['DNS'] ?? '1.1.1.1') . "\n";
|
||||||
|
$formattedConfig .= "\n";
|
||||||
|
$formattedConfig .= "[Peer]\n";
|
||||||
|
|
||||||
|
$serverPublicKey = '';
|
||||||
|
if (isset($peer['configuration']['PublicKey'])) {
|
||||||
|
$serverPublicKey = $peer['configuration']['PublicKey'];
|
||||||
|
} else {
|
||||||
|
$configInfoResult = wgdashboard_API($params, 'getWireguardConfigurationInfo?configurationName=' . urlencode($configName), [], 'GET', true);
|
||||||
|
if($configInfoResult['status_code'] === 200 && isset($configInfoResult['data']['data']['configurationInfo']['PublicKey'])) {
|
||||||
|
$serverPublicKey = $configInfoResult['data']['data']['configurationInfo']['PublicKey'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$formattedConfig .= "PublicKey = " . ($serverPublicKey ?: 'N/A') . "\n";
|
||||||
|
$formattedConfig .= "AllowedIPs = " . ($peer['endpoint_allowed_ip'] ?? '0.0.0.0/0') . "\n";
|
||||||
|
|
||||||
|
$endpoint = 'N/A';
|
||||||
|
if (!empty($peer['remote_endpoint'])) {
|
||||||
|
$port = '';
|
||||||
|
if (isset($peer['configuration']['ListenPort'])) {
|
||||||
|
$port = $peer['configuration']['ListenPort'];
|
||||||
|
} else {
|
||||||
|
if (isset($configInfoResult) && $configInfoResult['status_code'] === 200 && isset($configInfoResult['data']['data']['configurationInfo']['ListenPort'])) {
|
||||||
|
$port = $configInfoResult['data']['data']['configurationInfo']['ListenPort'];
|
||||||
|
} else {
|
||||||
|
$port = '51820';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$endpoint = $peer['remote_endpoint'] . ':' . $port;
|
||||||
|
}
|
||||||
|
|
||||||
|
$formattedConfig .= "Endpoint = " . $endpoint . "\n";
|
||||||
|
$formattedConfig .= "PersistentKeepalive = " . ($peer['keepalive'] ?? '21') . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'templatefile' => 'clientarea',
|
||||||
|
'vars' => [
|
||||||
|
'formatted_config' => $formattedConfig,
|
||||||
|
'peer_name' => $peer['name'] ?? 'wireguard',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (Exception $err) {
|
||||||
|
return [
|
||||||
|
'templatefile' => 'clientarea',
|
||||||
|
'vars' => [
|
||||||
|
'error' => $err->getMessage(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user