rune-workshop

A web ui to make runes
git clone git://jb55.com/rune-workshop
Log | Files | Refs | README

rune-workshop.js (7728B)


      1 
      2 let STATE = {}
      3 
      4 async function go() {
      5 	const el = document.querySelector("#workshop")
      6 
      7 	el.innerHTML = await render_workshop()
      8 
      9 	load_selected()
     10 
     11 	setInterval(try_update_command, 10000)
     12 }
     13 
     14 function now_unix() {
     15 	return Math.floor(new Date().getTime() / 1000)
     16 }
     17 
     18 function try_update_command() {
     19 	let r = STATE.restrictions
     20 	if (r && r.duration) {
     21 		let updated = now_unix() + r.duration
     22 		if (updated != r.time) {
     23 			r.time = updated
     24 			update_command(STATE.restrictions)
     25 		}
     26 	}
     27 }
     28 
     29 function get_rpcs() {
     30 	return fetch("schemas.json").then(res => res.json())
     31 }
     32 
     33 function load_selected() {
     34 	//const method = window.location.hash.substr(1) || "commando"
     35 
     36 	//select_method(method)
     37 }
     38 
     39 function duration_scale_multiplier(v) {
     40 	switch (v) {
     41 		case "minutes": return 60
     42 		case "hours":   return 60*60
     43 		case "days":    return 60*60*24
     44 		case "weeks":   return 60*60*24*7
     45 		case "months":  return 60*60*24*30
     46 		case "years":   return 60*60*24*365
     47 	}
     48 
     49 	return 0
     50 }
     51 
     52 function change_duration() {
     53 	const value = document.querySelector("#duration-input").value
     54 	const scale = document.querySelector("#duration-scale").value
     55 	const mod = duration_scale_multiplier(scale)
     56 
     57 	let r = STATE.restrictions = STATE.restrictions || {}
     58 
     59 	if (value) {
     60 		r.duration = mod * value
     61 		r.time = (Math.floor(new Date().getTime() / 1000)) + r.duration
     62 	} else {
     63 		delete r.time
     64 		delete r.duration
     65 	}
     66 
     67 	update_command(r)
     68 }
     69 
     70 function render_time_restriction()
     71 {
     72 	return `
     73 	<span class="param-name">Duration</span>
     74 	<input type="number" onkeyup="change_duration()" id="duration-input"></input>
     75 	<select onchange="change_duration()" id="duration-scale">
     76 	  <option value=minutes>Minutes</option>
     77 	  <option value=hours>Hours</option>
     78 	  <option value=days>Days</option>
     79 	  <option value=weeks>Weeks</option>
     80 	  <option value=months>Months</option>
     81 	  <option value=years>Years</option>
     82 	</select>
     83 	`
     84 }
     85 
     86 function change_id(el)
     87 {
     88 	const r = STATE.restrictions = STATE.restrictions || {}
     89 
     90 	r.id = el.value
     91 
     92 	update_command(r)
     93 }
     94 
     95 function render_id_restriction()
     96 {
     97 	return `
     98 	<span class="param-name">Node ID</span>
     99 	<input style="width: 100%" type="text" onkeyup="change_id(this)" id="id-input"></input>
    100 	`
    101 }
    102 
    103 function render_rate_restriction()
    104 {
    105 	return `
    106 	<span class="param-name">Rate</span>
    107 	<input type="number" onkeyup="change_rate(this)" id="rate-input"></input>
    108 	Requests Per Minute
    109 	`
    110 }
    111 
    112 function change_rate(el) {
    113 	const r = STATE.restrictions = STATE.restrictions || {}
    114 
    115 	r.rate = el.value
    116 
    117 	update_command(r)
    118 }
    119 
    120 function render_generic_restrictions_section(rpcs)
    121 {
    122 	return `
    123 	<h2>Restrictions</h2>
    124 	<div>
    125 		${render_id_restriction()}
    126 	</div>
    127 	<div>
    128 		${render_time_restriction()}
    129 	</div>
    130 	<div>
    131 		${render_rate_restriction()}
    132 	</div>
    133 	<div>
    134 		<span class="param-name">Method</span>
    135 		${render_method_selector(rpcs)}
    136 	</div>
    137 	`
    138 }
    139 
    140 async function render_workshop() {
    141 	const rpcs = await get_rpcs()
    142 	STATE.rpcs = rpcs
    143 
    144 	return `
    145 	${render_generic_restrictions_section(rpcs)}
    146 	<div id="method">
    147 	</div>
    148 	`
    149 }
    150 
    151 function select_method(sel) {
    152 	const method = sel.value
    153 	const el = document.querySelector("#method")
    154 	STATE.restrictions = STATE.restrictions || {}
    155 	STATE.restrictions.params = {}
    156 
    157 	if (method) {
    158 		let rpc = STATE.rpcs[method]
    159 		rpc.method = method
    160 		update_restriction({method})
    161 		el.innerHTML = render_rpc(rpc)
    162 	} else {
    163 		update_command(STATE.restrictions)
    164 		el.innerHTML = ""
    165 	}
    166 
    167 }
    168 
    169 function render_rpc(rpc) {
    170 	const params = render_params(rpc)
    171 
    172 	return `
    173 	<h2>${rpc.method} parameters</h2>
    174 
    175 	${params}
    176 	
    177 	<pre>${JSON.stringify(rpc,null,4)}</pre>
    178 	`
    179 }
    180 
    181 function render_params(rpc) {
    182 	const required = rpc.required.reduce((obj, param) => {
    183 		obj[param] = true
    184 		return obj
    185 	}, {})
    186 
    187 	const params = Object.keys(rpc.properties)
    188 
    189 	return params.map(p => render_param(required, rpc, p)).join("\n")
    190 }
    191 
    192 function render_type(p)
    193 {
    194 	let type = p.type;
    195 	if (p.type === "array" && p.items) {
    196 		type = `[${p.items.type}]`
    197 	}
    198 	return `<span class="badge type-${p.type}">${type}</span>`
    199 }
    200 
    201 function get_description(p)
    202 {
    203 	if (p.oneOf) {
    204 		return p.oneOf.map(o => o.description).filter(d => d).join("<br/><br/>")
    205 	}
    206 	
    207 	return p.description;
    208 }
    209 
    210 /*
    211  * 58	object
    212  * 54	string
    213  * 22	array
    214  * 21	u32
    215  * 18	pubkey
    216  * 15	msat
    217  * 13	u64
    218  * 11	hex
    219  * 11	boolean
    220  * 10	u16
    221  * 7	short_channel_id
    222  * 6	number
    223  * 6	hash
    224  * 6	feerate
    225  * 4	outpoint
    226  * 3	txid
    227  * 3	secret
    228  * 3	integer
    229  * 2	msat_or_all
    230  * 1	short_channel_id_dir
    231  * 1	outputdesc
    232  * 1	msat_or_any
    233  * 1	hexstr
    234  * 1	addresstype
    235  */
    236 function get_type(p) {
    237 	// TODO: oneOf stuff
    238 	return p.type
    239 }
    240 
    241 function get_input_type(type) {
    242 	switch (type) {
    243 		case "u16":
    244 		case "number":
    245 		case "u32": 
    246 		case "u64": return "number"
    247 
    248 		case "string": return "text"
    249 		default: return "text"
    250 	}
    251 }
    252 
    253 function render_type_options(type)
    254 {
    255 	if (type === 'number') {
    256 		return `
    257 		<option value="=">Equals</option>
    258 		<option value=">">Greater Than</option>
    259 		<option value="<">Less Than</option>
    260 		`
    261 	}
    262 
    263 	return `
    264 	    <option value="=">Equals</option>
    265 	    <option value="^">Starts With</option>
    266 	`
    267 }
    268 
    269 function update_restriction(data)
    270 {
    271 	let r = STATE.restrictions = STATE.restrictions || {}
    272 	r = r.params = r.params || {}
    273 
    274 	let v = r[data.method] = r[data.method] || {}
    275 
    276 	if (data.param_name) {
    277 		const selkey = selector_key(data.method, data.param_name)
    278 		const value = document.querySelector(`#${selkey}-input`).value
    279 		const op = document.querySelector(`#${selkey}`).value
    280 		v[data.param_name] = {value,op}
    281 		if (value === "") {
    282 			delete v[data.param_name]
    283 		}
    284 	}
    285 
    286 	update_command(STATE.restrictions)
    287 }
    288 
    289 function update_command(r)
    290 {
    291 	const command_el = document.querySelector("#command")
    292 	command_el.value = build_command(r)
    293 }
    294 
    295 function restriction(v, old=false) {
    296 	if (old) {
    297 		return v
    298 	} else {
    299 		return [v]
    300 	}
    301 }
    302 
    303 function build_command(r)
    304 {
    305 	let rs = []
    306 	if (r.params) {
    307 		for (const method of Object.keys(r.params)) {
    308 			rs.push(restriction(`method=${method}`))
    309 
    310 			for (const pname of Object.keys(r.params[method])) {
    311 				const {value, op} = r.params[method][pname]
    312 				rs.push(restriction(`pname${pname}${op}${value}`))
    313 			}
    314 		}
    315 	}
    316 
    317 	if (r.rate) {
    318 		rs.push(restriction(`rate=${r.rate}`))
    319 	}
    320 
    321 	if (r.time) {
    322 		rs.push(restriction(`time<${r.time}`))
    323 	}
    324 
    325 	if (r.id) {
    326 		rs.push(restriction(`id=${r.id}`))
    327 	}
    328 
    329 	const out = JSON.stringify(rs)
    330 
    331 	return `lightning-cli commando-rune restrictions='${out}'`
    332 }
    333 
    334 function selector_key(method, param_name)
    335 {
    336 	return method + param_name
    337 }
    338 
    339 function render_restriction_tool(rpc, param_name, param)
    340 {
    341 	const type = get_type(param) || "string"
    342 	const input_type = get_input_type(type)
    343 
    344 	const method = rpc.method
    345 	const json_p = JSON.stringify({ method, param_name, param })
    346 	const selkey = selector_key(method, param_name)
    347 	const updater = `update_restriction(${json_p})`
    348 
    349 	return `
    350 	<div class="restriction-tool">
    351 	  <select onchange='${updater}' id="${selkey}">
    352 		${render_type_options(input_type)}
    353 	  </select>
    354 	  <input id='${selkey}-input' onkeyup='${updater}' type="${input_type}"></input>
    355 	</div>
    356 	`
    357 }
    358 
    359 function render_param(required, rpc, param) {
    360 	const p = rpc.properties[param]
    361 
    362 	const is_req = !!required[param]
    363 	const req = is_req ? "param-required" : ""
    364 	const desc = get_description(p)
    365 	const typ = get_type(p)
    366 
    367 	return `
    368 	<span class="param-name ${req}">${param}</span>
    369 	${render_type(p)}
    370 	<!--
    371 		<span class="required">${is_req? "required" : ""}</span>
    372 	-->
    373 	<blockquote>
    374 		${desc}
    375 		${render_restriction_tool(rpc, param, p)}
    376 	</blockquote>
    377 	`
    378 }
    379 
    380 function render_method_selector(rpcs) {
    381 	let methods = Object.keys(rpcs)
    382 	methods.unshift("")
    383 	
    384 	const options = methods.map(method =>
    385 		`<option id="${method}" value="${method}">${method}</option>`)
    386 		.join("\n")
    387 
    388 	return `
    389 	<select id="method_selector" onchange="select_method(this)">
    390 		${options}
    391 	</select>
    392 	`
    393 }
    394 
    395 
    396 go()