mirror of
https://git.openwrt.org/project/luci.git
synced 2025-12-20 08:49:59 +08:00
luci-app-csshnpd: Add new package
Adding LuCI web interface for csshnpd package Signed-off-by: Chris Swan <chris@atsign.com>
This commit is contained in:
15
applications/luci-app-csshnpd/Makefile
Normal file
15
applications/luci-app-csshnpd/Makefile
Normal file
@@ -0,0 +1,15 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-csshnpd
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
PKG_MAINTAINER:=Chris Swan <chris@atsign.com>
|
||||
PKG_RELEASE:=1
|
||||
|
||||
LUCI_TITLE:=NoPorts Web UI
|
||||
LUCI_DESCRIPTION:=LuCI config app for NoPorts daemon (csshnpd)
|
||||
LUCI_DEPENDS:=+luci-base +csshnpd
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
include ../../luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
|
||||
function validateAtsign(section_id, value) {
|
||||
if (value.length < 1) {
|
||||
return _('Must not be empty and should start with @ (e.g., "@a").');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateDevice(section_id, value) {
|
||||
if (value.length < 1) {
|
||||
return _('Must be at least one character long (e.g., "a").');
|
||||
}
|
||||
if (value.length == 1) {
|
||||
if (!/^[a-z]+$/.test(value)) {
|
||||
return _('First character should be a lowercase letter (e.g., "a").');
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!/^[a-z][a-z0-9_-]+$/.test(value)) {
|
||||
return _('Device names may contain a-z 0-9 _ or - (e.g., "my_thing1").');
|
||||
}
|
||||
if (value.length > 36) {
|
||||
return _('Maximum device name length is 36 characters.');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateOTP(section_id, value) {
|
||||
if (value.length != 6) {
|
||||
return _('Must be six characters (e.g., "S3CR3T").');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function firstAt(section_id, value) {
|
||||
if (value && !value.startsWith('@')) {
|
||||
value = '@' + value; // Ensure @ at start
|
||||
}
|
||||
return this.super('write', [section_id, value]);
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
render: function() {
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('sshnpd', _('NoPorts'),
|
||||
_('Daemon Configuration'));
|
||||
|
||||
s = m.section(form.TypedSection, 'sshnpd', _('sshnpd config'));
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.Value, 'atsign', _('Device atSign'),
|
||||
_('The device atSign e.g. @device'));
|
||||
o.default = '@device';
|
||||
o.validate = validateAtsign;
|
||||
o.write = firstAt;
|
||||
|
||||
o = s.option(form.Value, 'manager', _('Manager atSign'),
|
||||
_('The manager atSign e.g. @manager'));
|
||||
o.default = '@manager';
|
||||
o.validate = validateAtsign;
|
||||
o.write = firstAt;
|
||||
|
||||
o = s.option(form.Value, 'device', _('Device name'),
|
||||
_('The name for this device e.g. openwrt'));
|
||||
o.default = 'openwrt';
|
||||
o.validate = validateDevice;
|
||||
|
||||
s.option(form.Value, 'args', _('Additional arguments'),
|
||||
_('Further command line arguments for the NoPorts daemon'));
|
||||
|
||||
o = s.option(form.Value, 'otp', _('Enrollment OTP/SPP'),
|
||||
_('One Time Passcode (OTP) for device atSign enrollment'));
|
||||
o.default = '000000';
|
||||
o.validate = validateOTP;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enabled'),
|
||||
_('Check here to enable the service'));
|
||||
o.default = '1';
|
||||
o.rmempty = false;
|
||||
|
||||
return m.render();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require dom';
|
||||
'require fs';
|
||||
'require ui';
|
||||
'require uci';
|
||||
'require network';
|
||||
|
||||
|
||||
return view.extend({
|
||||
handleCommand: function(exec, args) {
|
||||
let buttons = document.querySelectorAll('.diag-action > .cbi-button');
|
||||
|
||||
for (let i = 0; i < buttons.length; i++)
|
||||
buttons[i].setAttribute('disabled', 'true');
|
||||
|
||||
return fs.exec(exec, args).then(function(res) {
|
||||
let out = document.querySelector('textarea');
|
||||
|
||||
dom.content(out, [ res.stdout || '', res.stderr || '' ]);
|
||||
}).catch(function(err) {
|
||||
ui.addNotification(null, E('p', [ err ]))
|
||||
}).finally(function() {
|
||||
for (let i = 0; i < buttons.length; i++)
|
||||
buttons[i].removeAttribute('disabled');
|
||||
});
|
||||
},
|
||||
|
||||
handleEnroll: function() {
|
||||
return this.handleCommand('at_enroll.sh', "");
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return uci.load('sshnpd').then(function() {
|
||||
let atsign = uci.get_first('sshnpd','','atsign'),
|
||||
keyfile = '/root/.atsign/keys/'+atsign+'_key.atKeys';
|
||||
return L.resolveDefault(fs.stat(keyfile), {});
|
||||
});
|
||||
},
|
||||
|
||||
render: function(res) {
|
||||
|
||||
const has_atkey = res.path;
|
||||
const atsign = uci.get_first('sshnpd','','atsign');
|
||||
const device = uci.get_first('sshnpd','','device');
|
||||
const otp = uci.get_first('sshnpd','','otp');
|
||||
const enrollready = atsign && device && otp && !has_atkey;
|
||||
|
||||
const instructions = E('div', { 'class': 'cbi-map-descr'}, _('Press the Enroll button then run this command on a system where '+atsign+' is activated:'));
|
||||
|
||||
const enrollcmd = E('code','at_activate approve -a '+atsign+' --arx noports --drx '+device);
|
||||
|
||||
let table = E('table', { 'class': 'table' }, [
|
||||
E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td left' }, [
|
||||
E('span', { 'class': 'diag-action' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': ui.createHandlerFn(this, 'handleEnroll')
|
||||
}, [ _('Enroll') ])
|
||||
])
|
||||
]),
|
||||
])
|
||||
]);
|
||||
|
||||
const cmdwindow = E('div', {'class': 'cbi-section'}, [
|
||||
E('div', { 'id' : 'command-output'},
|
||||
E('textarea', {
|
||||
'id': 'widget.command-output',
|
||||
'style': 'width: 100%; font-family:monospace; white-space:pre',
|
||||
'readonly': true,
|
||||
'wrap': 'on',
|
||||
'rows': '20'
|
||||
})
|
||||
)
|
||||
]);
|
||||
|
||||
let view = E('div', { 'class': 'cbi-map'}, [
|
||||
E('h2', {}, [ _('NoPorts atSign Enrollment') ]),
|
||||
atsign ? E([]) : E('div', { 'class': 'cbi-map-descr'}, _('atSign must be configured')),
|
||||
device ? E([]) : E('div', { 'class': 'cbi-map-descr'}, _('Device must be configured')),
|
||||
otp ? E([]) : E('div', { 'class': 'cbi-map-descr'}, _('OTP must be configured. An OTP can be generated using:')),
|
||||
otp ? E([]) : E('code','at_activate otp -a '+atsign),
|
||||
has_atkey ? E('div', { 'class': 'cbi-map-descr'}, _('Existing key found at: '+has_atkey)) : E([]),
|
||||
enrollready ? instructions : E([]),
|
||||
enrollready ? enrollcmd : E([]),
|
||||
enrollready ? table : E([]),
|
||||
enrollready ? cmdwindow : E([]),
|
||||
]);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"admin/network/sshnpd": {
|
||||
"title": "NoPorts",
|
||||
"order": 70,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-csshnpd" ]
|
||||
}
|
||||
},
|
||||
|
||||
"admin/network/sshnpd/config": {
|
||||
"title": "NoPorts Config",
|
||||
"order": 1,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "sshnpd/config"
|
||||
}
|
||||
},
|
||||
|
||||
"admin/network/sshnpd/enroll": {
|
||||
"title": "NoPorts Enrollment",
|
||||
"order": 2,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "sshnpd/enroll"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"luci-app-csshnpd": {
|
||||
"description": "Grant UCI access for luci-app-csshnpd",
|
||||
"read": {
|
||||
"uci": [ "sshnpd" ],
|
||||
"file": {
|
||||
"/usr/bin/at_enroll.sh": ["exec"],
|
||||
"/root/.atsign/keys/*": ["stat"]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "sshnpd" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user