diff options
Diffstat (limited to 'applications/luci-app-cloudflared/htdocs/luci-static/resources/view')
3 files changed, 328 insertions, 0 deletions
diff --git a/applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/config.js b/applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/config.js new file mode 100644 index 0000000000..4ae395f054 --- /dev/null +++ b/applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/config.js @@ -0,0 +1,102 @@ +/* This is free software, licensed under the Apache License, Version 2.0 + * + * Copyright (C) 2024 Hilman Maulana <hilman0.0maulana@gmail.com> + */ + +'use strict'; +'require form'; +'require rpc'; +'require view'; + +var callServiceList = rpc.declare({ + object: 'service', + method: 'list', + params: ['name'], + expect: { '': {} } +}); + +function getServiceStatus() { + return L.resolveDefault(callServiceList('cloudflared'), {}).then(function (res) { + var isRunning = false; + try { + isRunning = res['cloudflared']['instances']['cloudflared']['running']; + } catch (ignored) {} + return isRunning; + }); +} + +return view.extend({ + load: function () { + return Promise.all([ + getServiceStatus() + ]); + }, + + render: function (data) { + let isRunning = data[0]; + var m, s, o; + + m = new form.Map('cloudflared', _('Cloudflare Zero Trust Tunnel'), + _('Cloudflare Zero Trust Security services help you get maximum security both from outside and within the network.') + '<br />' + + _('Create and manage your network on the <a %s>Cloudflare Zero Trust</a> dashboard.') + .format('href="https://one.dash.cloudflare.com" target="_blank"') + '<br />' + + _('See <a %s>documentation</a>.') + .format('href="https://openwrt.org/docs/guide-user/services/vpn/cloudfare_tunnel" target="_blank"') + ); + + s = m.section(form.NamedSection, 'config', 'cloudflared'); + + o = s.option(form.DummyValue, '_status', _('Status')); + o.rawhtml = true; + o.cfgvalue = function(section_id) { + var span = '<b><span style="color:%s">%s</span></b>'; + var renderHTML = isRunning ? + String.format(span, 'green', _('Running')) : + String.format(span, 'red', _('Not Running')); + return renderHTML; + }; + + o = s.option(form.Flag, 'enabled', _('Enable')); + o.rmempty = false; + + o = s.option(form.TextValue, 'token', _('Token'), + _('The tunnel token is shown in the dashboard once you create a tunnel.') + ); + o.optional = true; + o.rmempty = false; + o.monospace = true; + + o = s.option(form.FileUpload, 'config', _('Config file path'), + _('See <a %s>documentation</a>.') + .format('href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/configure-tunnels/local-management/configuration-file/" target="_blank"') + ); + o.default = '/etc/cloudflared/config.yml'; + o.root_directory = '/etc/cloudflared/'; + o.optional = true; + + o = s.option(form.FileUpload, 'origincert', _('Certificate of Origin'), + _('The account certificate for your zones authorizing the client to serve as an Origin for that zone') + '<br />' + + _('Obtain a certificate <a %s>here</a>.') + .format('href="https://dash.cloudflare.com/argotunnel" target="_blank"') + ); + o.default = '/etc/cloudflared/cert.pem'; + o.root_directory = '/etc/cloudflared/'; + o.optional = true; + + o = s.option(form.ListValue, 'region', _('Region'), + _('The region to which connections are established.') + ); + o.value('us', _('United States')); + o.optional = true; + + o = s.option(form.ListValue, 'loglevel', _('Logging level')); + o.value('fatal', _('Fatal')); + o.value('error', _('Error')); + o.value('warn', _('Warning')); + o.value('info', _('Info')); + o.value('debug', _('Debug')); + o.default = 'info'; + + return m.render(); + } +});
\ No newline at end of file diff --git a/applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/log.js b/applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/log.js new file mode 100644 index 0000000000..bc2c9e96fc --- /dev/null +++ b/applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/log.js @@ -0,0 +1,125 @@ +/* This is free software, licensed under the Apache License, Version 2.0 + * + * Copyright (C) 2024 Hilman Maulana <hilman0.0maulana@gmail.com> + */ + +'use strict'; +'require fs'; +'require ui'; +'require view'; +'require poll'; + +function formatLogEntry(logObj) { + var formattedTime = new Date(logObj.time).toISOString().replace('T', ' ').split('.')[0]; + var tunnelIDMessage = logObj.tunnelID ? ', ID: ' + logObj.tunnelID : ''; + var errorMessage = logObj.error ? ', Error: ' + logObj.error : ''; + var ipMessage = logObj.ip ? ', IP: ' + logObj.ip : ''; + var configMessage = logObj.config ? ', Config: ' + JSON.stringify(logObj.config) : ''; + var connectionMessage = logObj.connection ? ', Connection: ' + JSON.stringify(logObj.connection) : ''; + var locationMessage = logObj.location ? ', Location: ' + logObj.location : ''; + var protocolMessage = logObj.protocol ? ', Protocol: ' + logObj.protocol : ''; + + return '[' + formattedTime + '] [' + logObj.level + '] : ' + logObj.message + ipMessage + tunnelIDMessage + errorMessage + configMessage + connectionMessage + locationMessage + protocolMessage; +} + +return view.extend({ + handleSaveApply: null, + handleSave: null, + handleReset: null, + load: function() { + poll.add(function () { + return fs.read('/var/log/cloudflared.log').then(function(res) { + if (!res || res.trim() === '') { + ui.addNotification(null, E('p', {}, _('Unable to read the interface info from /var/log/cloudflared.log.'))); + return ''; + } + + var logs = res.trim().split('\n').map(function(entry) { + try { + var logObj = JSON.parse(entry); + return logObj.time && logObj.message && logObj.level + ? formatLogEntry(logObj) + : ''; + } catch (error) { + console.error('Error parsing log entry:', error); + return ''; + } + }); + + logs = logs.filter(function(entry) { + return entry.trim() !== ''; + }); + + var info = logs.join('\n'); + var view = document.getElementById('syslog'); + var filterLevel = document.getElementById('filter-level').value; + var logDirection = document.getElementById('log-direction').value; + + if (view) { + var filteredLogs; + if (filterLevel !== 'all') { + filteredLogs = logs.filter(function(entry) { + var logLevel = entry.match(/\[.*\] \[(.*)\]/)[1].toLowerCase(); + return logLevel.includes(filterLevel.toLowerCase()); + }); + } else { + filteredLogs = logs; + } + + if (logDirection === 'up') { + filteredLogs = filteredLogs.reverse(); + } + + info = filteredLogs.join('\n'); + view.innerHTML = info; + } + + return info; + }); + }); + + return Promise.resolve(''); + }, + render: function(info) { + return E([], [ + E('h2', { 'class': 'section-title' }, _('Log')), + E('div', { 'id': 'logs' }, [ + E('label', { 'for': 'filter-level', 'style': 'margin-right: 8px;' }, _('Filter Level:')), + E('select', { 'id': 'filter-level', 'style': 'margin-right: 8px;' }, [ + E('option', { 'value': 'all', 'selected': 'selected' }, _('All')), + E('option', { 'value': 'info' }, _('Info')), + E('option', { 'value': 'warn' }, _('Warn')), + E('option', { 'value': 'error' }, _('Error')), + ]), + E('label', { 'for': 'log-direction', 'style': 'margin-right: 8px;' }, _('Log Direction:')), + E('select', { 'id': 'log-direction', 'style': 'margin-right: 8px;' }, [ + E('option', { 'value': 'down', 'selected': 'selected' }, _('Down')), + E('option', { 'value': 'up' }, _('Up')), + ]), + E('button', { + 'id': 'download-log', + 'class': 'cbi-button cbi-button-save', + 'click': L.bind(this.handleDownloadLog, this), + 'style': 'margin-bottom: 8px;' + }, _('Download Log')), + E('textarea', { + 'id': 'syslog', + 'class': 'cbi-input-textarea', + 'style': 'height: 500px; overflow-y: scroll;', + 'readonly': 'readonly', + 'wrap': 'off', + 'rows': 1 + }, [ info ]) + ]) + ]); + }, + + handleDownloadLog: function() { + var logs = document.getElementById('syslog').value; + var blob = new Blob([logs], { type: 'text/plain' }); + var link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = 'cloudflared.log'; + link.click(); + } +}); diff --git a/applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/tunnels.js b/applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/tunnels.js new file mode 100644 index 0000000000..23fb4c47ae --- /dev/null +++ b/applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/tunnels.js @@ -0,0 +1,101 @@ +/* This is free software, licensed under the Apache License, Version 2.0 + * + * Copyright (C) 2024 Sergey Ponomarev <stokito@gmail.com> + */ + +'use strict'; +'require view'; +'require fs'; + +function listTunnels() { + let command = '/usr/bin/cloudflared'; + let commandArgs = ['tunnel', 'list', '-o', 'json']; + return fs.exec(command, commandArgs).then(function (res) { + if (res.code === 0) { + return JSON.parse(res.stdout); + } else { + throw new Error(res.stdout + ' ' + res.stderr); + } + }); +} + +return view.extend({ + handleSaveApply: null, + handleSave: null, + handleReset: null, + + load: function () { + return Promise.all([ + listTunnels() + ]); + }, + + render: function (data) { + var tunnels = data[0]; + + var tunnelRows = tunnels.map(function (tunnel, index) { + var rowClass = index % 2 === 0 ? 'cbi-rowstyle-1' : 'cbi-rowstyle-2'; + var tunneldate = new Date(tunnel.created_at).toLocaleString(); + return E('tr', { 'class': 'tr ' + rowClass }, [ + E('td', {'class': 'td'}, tunnel.name), + E('td', {'class': 'td'}, tunnel.id), + E('td', {'class': 'td'}, tunneldate), + E('td', {'class': 'td'}, tunnel.connections.length) + ]); + }); + + var tunnelTable = [ + E('h3', _('Tunnels Information')), + E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', {'class': 'th'}, _('Name')), + E('th', {'class': 'th'}, _('ID')), + E('th', {'class': 'th'}, _('Created At')), + E('th', {'class': 'th'}, _('Connections')) + ]), + E(tunnelRows) + ]) + ]; + + var connectionsTables = tunnels.map(function (tunnel) { + var connectionsTable; + if (tunnel.connections.length > 0) { + var connectionRows = tunnel.connections.map(function (connection, index) { + var rowClass = index % 2 === 0 ? 'cbi-rowstyle-1' : 'cbi-rowstyle-2'; + var connectiondate = new Date(connection.opened_at).toLocaleString(); + return E('tr', { 'class': 'tr ' + rowClass }, [ + E('td', {'class': 'td'}, connection.id), + E('td', {'class': 'td'}, connection.origin_ip), + E('td', {'class': 'td'}, connectiondate), + E('td', {'class': 'td'}, connection.colo_name) + ]); + }); + + connectionsTable = E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', {'class': 'th'}, _('Connection ID')), + E('th', {'class': 'th'}, _('Origin IP')), + E('th', {'class': 'th'}, _('Opened At')), + E('th', {'class': 'th'}, _('Data Center')) + ]), + E(connectionRows) + ]); + } else { + connectionsTable = E('div', {'class':'cbi-value center'}, [ + E('em', _('No connections')) + ]); + } + + return E('div', {'class': 'cbi-section'}, [ + E('h3', _('Connections') + ' ' + tunnel.name), + E(connectionsTable) + ]); + }); + + return E([], [ + E('h2', { 'class': 'section-title' }, _('Tunnels')), + E('div', {'class': 'cbi-section'}, tunnelTable), + E(connectionsTables) + ]); + } +}); |