secubox-openwrt/luci-app-wireguard-dashboard/htdocs/luci-static/resources/view/wireguard-dashboard/qrcode.js

238 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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 = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + qrSize + ' ' + qrSize + '" width="' + qrSize + '" height="' + qrSize + '">';
svg += '<rect width="100%" height="100%" fill="white"/>';
// Simplified pattern - in real implementation, use proper QR encoding
// Draw finder patterns (corners)
var drawFinder = function(x, y) {
var s = moduleSize * 7;
svg += '<rect x="' + x + '" y="' + y + '" width="' + s + '" height="' + s + '" fill="black"/>';
svg += '<rect x="' + (x + moduleSize) + '" y="' + (y + moduleSize) + '" width="' + (s - moduleSize * 2) + '" height="' + (s - moduleSize * 2) + '" fill="white"/>';
svg += '<rect x="' + (x + moduleSize * 2) + '" y="' + (y + moduleSize * 2) + '" width="' + (s - moduleSize * 4) + '" height="' + (s - moduleSize * 4) + '" fill="black"/>';
};
drawFinder(0, 0);
drawFinder(qrSize - moduleSize * 7, 0);
drawFinder(0, qrSize - moduleSize * 7);
// Add some random-looking modules for visual effect
var hash = 0;
for (var i = 0; i < text.length; i++) {
hash = ((hash << 5) - hash) + text.charCodeAt(i);
}
for (var row = 8; row < moduleCount - 8; row++) {
for (var col = 8; col < moduleCount - 8; col++) {
if (((hash + row * col) % 3) === 0) {
svg += '<rect x="' + (col * moduleSize) + '" y="' + (row * moduleSize) + '" width="' + moduleSize + '" height="' + moduleSize + '" fill="black"/>';
}
}
}
svg += '</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 = <YOUR_PRIVATE_KEY>\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 = <YOUR_SERVER_IP>:' + (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
});