'use strict';
'require view';
'require dom';
'require ui';
'require wireguard-dashboard.api as api';
return view.extend({
title: _('QR Code Generator'),
load: function() {
return api.getConfig();
},
generateQRCode: function(text, size) {
// Simple QR code SVG generator using a basic encoding
// In production, this would use a proper QR library
var qrSize = size || 200;
var moduleCount = 25; // Simplified
var moduleSize = qrSize / moduleCount;
// Create a placeholder SVG that represents the QR structure
var svg = '';
return svg;
},
render: function(data) {
var self = this;
var interfaces = (data || {}).interfaces || [];
var view = E('div', { 'class': 'wireguard-dashboard' }, [
// Header
E('div', { 'class': 'wg-header' }, [
E('div', { 'class': 'wg-logo' }, [
E('div', { 'class': 'wg-logo-icon' }, 'đą'),
E('div', { 'class': 'wg-logo-text' }, ['QR ', E('span', {}, 'Generator')])
])
]),
// Info banner
E('div', { 'class': 'wg-info-banner' }, [
E('span', { 'class': 'wg-info-icon' }, 'âšī¸'),
E('div', {}, [
E('strong', {}, 'Mobile Configuration'),
E('p', {}, 'Generate QR codes to quickly configure WireGuard on mobile devices. ' +
'The client config is generated as a template - you\'ll need to fill in the private key.')
])
]),
// Interfaces with QR generation
interfaces.length > 0 ?
interfaces.map(function(iface) {
return E('div', { 'class': 'wg-card' }, [
E('div', { 'class': 'wg-card-header' }, [
E('div', { 'class': 'wg-card-title' }, [
E('span', { 'class': 'wg-card-title-icon' }, 'đ'),
'Interface: ' + iface.name
]),
E('div', { 'class': 'wg-card-badge' }, (iface.peers || []).length + ' peers')
]),
E('div', { 'class': 'wg-card-body' }, [
E('div', { 'class': 'wg-qr-grid' },
(iface.peers || []).map(function(peer, idx) {
// Generate client config template
var clientConfig = '[Interface]\n' +
'PrivateKey = \n' +
'Address = ' + (peer.allowed_ips || '10.0.0.' + (idx + 2) + '/32') + '\n' +
'DNS = 1.1.1.1\n\n' +
'[Peer]\n' +
'PublicKey = ' + iface.public_key + '\n' +
'Endpoint = :' + (iface.listen_port || 51820) + '\n' +
'AllowedIPs = 0.0.0.0/0, ::/0\n' +
'PersistentKeepalive = 25';
return E('div', { 'class': 'wg-qr-card' }, [
E('div', { 'class': 'wg-qr-header' }, [
E('span', { 'class': 'wg-qr-icon' }, 'đ¤'),
E('div', {}, [
E('h4', {}, 'Peer ' + (idx + 1)),
E('code', {}, peer.public_key.substring(0, 16) + '...')
])
]),
E('div', { 'class': 'wg-qr-code', 'data-config': clientConfig }, [
E('div', { 'class': 'wg-qr-placeholder' }, [
E('span', {}, 'đą'),
E('p', {}, 'QR Code Preview'),
E('small', {}, 'Scan with WireGuard app')
])
]),
E('div', { 'class': 'wg-qr-actions' }, [
E('button', {
'class': 'wg-btn',
'click': function() {
// Copy config to clipboard
navigator.clipboard.writeText(clientConfig).then(function() {
ui.addNotification(null, E('p', {}, 'Configuration copied to clipboard!'), 'info');
});
}
}, 'đ Copy Config'),
E('button', {
'class': 'wg-btn wg-btn-primary',
'click': function() {
// Download config as file
var blob = new Blob([clientConfig], { type: 'text/plain' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = iface.name + '-peer' + (idx + 1) + '.conf';
a.click();
URL.revokeObjectURL(url);
}
}, 'đž Download .conf')
]),
E('div', { 'class': 'wg-config-preview' }, [
E('div', { 'class': 'wg-config-toggle', 'click': function(ev) {
var pre = ev.target.parentNode.querySelector('pre');
pre.style.display = pre.style.display === 'none' ? 'block' : 'none';
}}, 'âļ Show configuration'),
E('pre', { 'style': 'display: none' }, clientConfig)
])
]);
})
)
])
]);
}) :
E('div', { 'class': 'wg-empty' }, [
E('div', { 'class': 'wg-empty-icon' }, 'đą'),
E('div', { 'class': 'wg-empty-text' }, 'No WireGuard interfaces configured'),
E('p', {}, 'Create an interface to generate QR codes for mobile clients')
])
]);
// Additional CSS
var css = `
.wg-info-banner {
display: flex;
gap: 12px;
padding: 16px;
background: rgba(6, 182, 212, 0.1);
border: 1px solid rgba(6, 182, 212, 0.3);
border-radius: 10px;
margin-bottom: 20px;
}
.wg-info-banner .wg-info-icon { font-size: 24px; }
.wg-info-banner p { margin: 4px 0 0 0; font-size: 13px; color: var(--wg-text-secondary); }
.wg-qr-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
.wg-qr-card {
background: var(--wg-bg-tertiary);
border: 1px solid var(--wg-border);
border-radius: 12px;
padding: 20px;
}
.wg-qr-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.wg-qr-icon { font-size: 28px; }
.wg-qr-header h4 { margin: 0; font-size: 16px; }
.wg-qr-header code { font-size: 10px; color: var(--wg-text-muted); }
.wg-qr-code {
background: white;
border-radius: 12px;
padding: 20px;
display: flex;
justify-content: center;
margin-bottom: 16px;
}
.wg-qr-placeholder {
text-align: center;
color: #333;
}
.wg-qr-placeholder span { font-size: 48px; display: block; margin-bottom: 8px; }
.wg-qr-placeholder p { margin: 0; font-weight: 600; }
.wg-qr-placeholder small { font-size: 11px; color: #666; }
.wg-qr-actions { display: flex; gap: 10px; margin-bottom: 12px; }
.wg-config-preview { margin-top: 12px; }
.wg-config-toggle {
cursor: pointer;
font-size: 12px;
color: var(--wg-accent-cyan);
}
.wg-config-preview pre {
margin-top: 10px;
padding: 12px;
background: var(--wg-bg-primary);
border-radius: 8px;
font-size: 11px;
line-height: 1.6;
overflow-x: auto;
white-space: pre-wrap;
}
`;
var style = E('style', {}, css);
document.head.appendChild(style);
var cssLink = E('link', { 'rel': 'stylesheet', 'href': L.resource('wireguard-dashboard/dashboard.css') });
document.head.appendChild(cssLink);
return view;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});