forked from openwrt/luci
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
luci-app-cloudflared: configure Cloudflare Zero Trust Tunnel
Configure a tunnel and see logs of the daemon. Signed-off-by: Hilman Maulana <[email protected]> Signed-off-by: Sergey Ponomarev <[email protected]>
- Loading branch information
1 parent
f63e823
commit faf6c4f
Showing
40 changed files
with
6,139 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# This is free software, licensed under the Apache License, Version 2.0 | ||
# | ||
# Copyright (C) 2024 Hilman Maulana <[email protected]> | ||
|
||
include $(TOPDIR)/rules.mk | ||
|
||
LUCI_TITLE:=LuCI for Cloudflared | ||
LUCI_DEPENDS:=+cloudflared | ||
LUCI_DESCRIPTION:=LuCI support for Cloudflare Zero Trust Tunnels | ||
|
||
PKG_MAINTAINER:=Hilman Maulana <[email protected]>, Sergey Ponomarev <[email protected]> | ||
PKG_VERSION:=1.0 | ||
PKG_LICENSE:=Apache-2.0 | ||
|
||
include ../../luci.mk | ||
|
||
# call BuildPackage - OpenWrt buildroot signature |
118 changes: 118 additions & 0 deletions
118
applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/config.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/* This is free software, licensed under the Apache License, Version 2.0 | ||
* | ||
* Copyright (C) 2024 Hilman Maulana <[email protected]> | ||
*/ | ||
|
||
'use strict'; | ||
'require form'; | ||
'require poll'; | ||
'require rpc'; | ||
'require uci'; | ||
'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; | ||
}); | ||
} | ||
|
||
function renderStatus(isRunning) { | ||
var spanTemp = '<label class="cbi-value-title">Status</label><div class="cbi-value-field"><em><span style="color:%s">%s</span></em></div>'; | ||
var renderHTML; | ||
if (isRunning) { | ||
renderHTML = String.format(spanTemp, 'green', _('Running')); | ||
} else { | ||
renderHTML = String.format(spanTemp, 'red', _('Not Running')); | ||
} | ||
|
||
return renderHTML; | ||
} | ||
|
||
return view.extend({ | ||
load: function () { | ||
return Promise.all([ | ||
uci.load('cloudflared') | ||
]); | ||
}, | ||
|
||
render: function (data) { | ||
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, 'service_status', _('Status')); | ||
o.load = function () { | ||
poll.add(function () { | ||
return L.resolveDefault(getServiceStatus()).then(function (res) { | ||
var view = document.getElementById('cbi-cloudflared-config-service_status'); | ||
if (view) { | ||
view.innerHTML = renderStatus(res); | ||
} | ||
}); | ||
}); | ||
}; | ||
o.value = _('Collecting data...'); | ||
|
||
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', _('Debug 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(); | ||
} | ||
}); |
125 changes: 125 additions & 0 deletions
125
applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/log.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* This is free software, licensed under the Apache License, Version 2.0 | ||
* | ||
* Copyright (C) 2024 Hilman Maulana <[email protected]> | ||
*/ | ||
|
||
'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(); | ||
} | ||
}); |
Oops, something went wrong.