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()