mirror of
				https://git.openwrt.org/project/luci.git
				synced 2025-10-31 10:49:03 +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
	 Chris Swan
					Chris Swan