summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-cloudflared/htdocs/luci-static/resources/view
diff options
context:
space:
mode:
Diffstat (limited to 'applications/luci-app-cloudflared/htdocs/luci-static/resources/view')
-rw-r--r--applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/config.js102
-rw-r--r--applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/log.js125
-rw-r--r--applications/luci-app-cloudflared/htdocs/luci-static/resources/view/cloudflared/tunnels.js101
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)
+ ]);
+ }
+});