object-editor.uc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. // Copyright (C) 2025 Felix Fietkau <[email protected]>
  3. 'use strict';
  4. function __get_edit_object(ctx, entry, argv, name)
  5. {
  6. if (type(entry.edit_object) == "function")
  7. return call(entry.edit_object, entry, ctx.model.scope, ctx, argv);
  8. if (name)
  9. return ctx.data.edit[name];
  10. return ctx.data.edit;
  11. }
  12. function get_edit_object(ctx, entry, argv, name)
  13. {
  14. let obj = __get_edit_object(ctx, entry, argv, name);
  15. if (!obj)
  16. ctx.invalid_argument();
  17. return obj;
  18. }
  19. function get_param_object(ctx, obj, spec, argv)
  20. {
  21. if (type(spec.get_object) != "function")
  22. return obj;
  23. return call(spec.get_object, spec, ctx.model.scope, ctx, spec, obj, argv);
  24. }
  25. function call_change_cb(ctx, entry, argv, named)
  26. {
  27. if (!length(named) || type(entry.change_cb) != "function")
  28. return;
  29. call(entry.change_cb, entry, ctx.model.scope, ctx, argv);
  30. }
  31. function check_duplicate(ctx, val, new_val)
  32. {
  33. for (let i = 0; i < length(new_val); i++) {
  34. let v = new_val[i];
  35. if ((val && index(val, v) >= 0) ||
  36. (i && index(slice(new_val, 0, i), v) >= 0)) {
  37. ctx.invalid_argument("Duplicate value: %s", v);
  38. return true;
  39. }
  40. }
  41. }
  42. export function add_call(ctx, argv, named)
  43. {
  44. let spec = this.named_args;
  45. let obj = get_edit_object(ctx, this, argv);
  46. if (!obj)
  47. return;
  48. for (let name, val in named) {
  49. let cur = spec[name];
  50. if (type(cur.add) == "function") {
  51. call(cur.add, cur, ctx.model.scope, ctx, val);
  52. continue;
  53. }
  54. if (cur.attribute)
  55. name = cur.attribute;
  56. let cur_obj = get_param_object(ctx, obj, cur, argv);
  57. cur_obj[name] ??= [];
  58. if (!cur.allow_duplicate &&
  59. check_duplicate(ctx, obj[name], val))
  60. return;
  61. push(cur_obj[name], ...val);
  62. }
  63. call_change_cb(ctx, this, argv, named);
  64. return ctx.ok();
  65. };
  66. export function set_call(ctx, argv, named)
  67. {
  68. let spec = this.named_args;
  69. let obj = get_edit_object(ctx, this, argv);
  70. if (!obj)
  71. return;
  72. for (let name, val in named) {
  73. let cur = spec[name];
  74. if (!cur)
  75. continue;
  76. if (type(cur.set) == "function") {
  77. call(cur.set, cur, ctx.model.scope, ctx, val);
  78. continue;
  79. }
  80. if (cur.attribute)
  81. name = cur.attribute;
  82. let cur_obj = get_param_object(ctx, obj, cur, argv);
  83. if (val == null) {
  84. delete cur_obj[name];
  85. continue;
  86. }
  87. if (cur.multiple && !cur.allow_duplicate &&
  88. check_duplicate(ctx, obj[name], val))
  89. return;
  90. cur_obj[name] = val;
  91. }
  92. call_change_cb(ctx, this, argv, named);
  93. return ctx.ok();
  94. };
  95. export function remove_call(ctx, argv, named)
  96. {
  97. let spec = this.named_args;
  98. let obj = get_edit_object(ctx, this, argv);
  99. if (!obj)
  100. return;
  101. for (let name, val in named) {
  102. let cur = spec[name];
  103. if (type(cur.remove) == "function") {
  104. call(cur.remove, cur, ctx.model.scope, ctx, val);
  105. continue;
  106. }
  107. if (cur.attribute)
  108. name = cur.attribute;
  109. let cur_obj = get_param_object(ctx, obj, cur, argv);
  110. let data = cur_obj[name];
  111. if (!data)
  112. continue;
  113. for (let idx in val) {
  114. let orig_idx = idx;
  115. if (idx != "" + +idx) {
  116. let cur_idx = index(data, idx);
  117. if (cur_idx >= 0)
  118. idx = cur_idx + 1;
  119. else
  120. idx = null;
  121. } else if (+idx > length(data))
  122. idx = null;
  123. if (idx == null) {
  124. ctx.invalid_argument('Invalid value: %s', orig_idx);
  125. continue;
  126. }
  127. data[+idx - 1] = null;
  128. }
  129. cur_obj[name] = filter(data, (v) => v != null);
  130. if (cur.attribute_allow_empty && !length(cur_obj[name]))
  131. delete cur_obj[name];
  132. }
  133. if (length(ctx.result.errors) > 0)
  134. return;
  135. call_change_cb(ctx, this, argv, named);
  136. return ctx.ok();
  137. };
  138. export function show_call(ctx, argv, named)
  139. {
  140. let obj = get_edit_object(ctx, this, argv);
  141. if (!obj)
  142. return;
  143. let data = {};
  144. for (let name, spec in this.attribute_info) {
  145. let val;
  146. if (type(spec.get) == "function") {
  147. val = call(spec.get, spec, ctx.model.scope, ctx);
  148. } else {
  149. let cur_obj = get_param_object(ctx, obj, spec, argv);
  150. val = cur_obj[spec.attribute ?? name];
  151. }
  152. val ??= spec.default;
  153. if (val != null)
  154. data[name] = val;
  155. }
  156. return ctx.table("Values", data);
  157. };
  158. function param_values(ctx, argv, named_args, spec)
  159. {
  160. let obj = get_edit_object(ctx, this, argv);
  161. if (!obj)
  162. return;
  163. let values;
  164. if (type(spec.get) == "function")
  165. values = call(spec.get, spec, ctx.model.scope, ctx);
  166. else {
  167. let cur_obj = get_param_object(ctx, obj, spec, argv);
  168. values = cur_obj[spec.attribute];
  169. }
  170. let ret = {};
  171. let idx = 0;
  172. for (let value in values)
  173. ret["" + (++idx)] = value;
  174. return ret;
  175. }
  176. function add_params(orig_params)
  177. {
  178. let params = {};
  179. for (let name, val in orig_params) {
  180. if (!val.multiple)
  181. continue;
  182. val = { ...val };
  183. delete val.required;
  184. delete val.allow_empty;
  185. params[name] = val;
  186. }
  187. return params;
  188. }
  189. function set_params(orig_params)
  190. {
  191. let params = {};
  192. for (let name, val in orig_params) {
  193. val = { ...val };
  194. if (!val.required)
  195. val.allow_empty = true;
  196. else
  197. delete val.allow_empty;
  198. delete val.required;
  199. params[name] = val;
  200. }
  201. return params;
  202. }
  203. function remove_params(orig_params)
  204. {
  205. let params = {};
  206. for (let name, val in orig_params) {
  207. if (!val.multiple)
  208. continue;
  209. val = { ...val };
  210. val.attribute_allow_empty = val.allow_empty;
  211. delete val.required;
  212. delete val.allow_empty;
  213. val.args = {
  214. type: "enum",
  215. get_object: val.get_object,
  216. attribute: val.attribute ?? name,
  217. no_validate: true,
  218. value: param_values,
  219. force_helptext: true,
  220. };
  221. params[name] = val;
  222. }
  223. return params;
  224. }
  225. export function new(info, node)
  226. {
  227. let params = info.named_args;
  228. let ret = {
  229. add: {
  230. help: "Add list parameter entries",
  231. args: info.args,
  232. named_args: add_params(params),
  233. call: add_call,
  234. edit_object: info.edit_object,
  235. change_cb: info.change_cb,
  236. ...(info.add ?? {}),
  237. },
  238. show: {
  239. help: "Show parameter values",
  240. args: info.args,
  241. call: show_call,
  242. attribute_info: params,
  243. ...(info.show ?? {}),
  244. },
  245. set: {
  246. help: "Set parameter values",
  247. args: info.args,
  248. named_args: set_params(params),
  249. call: set_call,
  250. edit_object: info.edit_object,
  251. change_cb: info.change_cb,
  252. ...(info.set ?? {}),
  253. },
  254. remove: {
  255. help: "Remove parameter values",
  256. args: info.args,
  257. named_args: remove_params(params),
  258. call: remove_call,
  259. edit_object: info.edit_object,
  260. change_cb: info.change_cb,
  261. ...(info.remove ?? {}),
  262. }
  263. };
  264. if (!length(ret.add.named_args)) {
  265. delete ret.add;
  266. delete ret.remove;
  267. }
  268. if (node)
  269. for (let cmd, val in ret)
  270. node[cmd] = val;
  271. return ret;
  272. };
  273. export function object_destroy_call(ctx, argv, named)
  274. {
  275. let type_info, type_name;
  276. let info = this.object_info;
  277. if (info.types) {
  278. type_name = shift(argv);
  279. if (!type_name)
  280. return ctx.invalid_argument();
  281. type_info = info.types[type_name];
  282. } else {
  283. type_info = info.type;
  284. type_name = type_info.name;
  285. }
  286. if (!type_info)
  287. return ctx.invalid_argument();
  288. let obj_name = type_info.object ?? type_name;
  289. let name = shift(argv);
  290. if (type_info.delete) {
  291. if (!call(type_info.delete, info, ctx.model.scope, ctx, type, name))
  292. return;
  293. } else {
  294. let obj = ctx.data.object_edit[obj_name];
  295. if (!obj)
  296. return ctx.unknown_error();
  297. if (!obj[name])
  298. return ctx.not_found();
  299. delete obj[name];
  300. }
  301. if (info.change_cb)
  302. call(info.change_cb, info, ctx.model.scope, ctx, argv);
  303. return ctx.ok(`Deleted ${type_name} '${name}'`);
  304. };
  305. const create_edit_param = {
  306. help: "Edit object after creating",
  307. };
  308. export function object_create_params(node)
  309. {
  310. if (!node.show)
  311. return {};
  312. let orig_params = node.show.attribute_info;
  313. let params = {};
  314. for (let name, val in orig_params) {
  315. if (val.change_only)
  316. continue;
  317. params[name] = val;
  318. }
  319. params.edit ??= create_edit_param;
  320. return params;
  321. };
  322. export function object_create_call(ctx, argv, named)
  323. {
  324. let type_info, type_name;
  325. let info = this.object_info;
  326. if (info.types) {
  327. type_name = shift(argv);
  328. if (!type_name)
  329. return ctx.invalid_argument();
  330. type_info = info.types[type_name];
  331. } else {
  332. type_info = info.type;
  333. type_name = type_info.name;
  334. }
  335. if (!type_info)
  336. return ctx.invalid_argument();
  337. let obj_name = type_info.object ?? type_name;
  338. let name = shift(argv);
  339. let obj, data;
  340. if (type_info.add) {
  341. data = call(type_info.add, info, ctx.model.scope, ctx, type_name, name, named);
  342. if (!data)
  343. return;
  344. } else {
  345. data = {};
  346. }
  347. let entry = type_info.node.set;
  348. if (entry) {
  349. ctx.apply_defaults();
  350. let subctx = ctx.clone();
  351. subctx.data.name = name;
  352. subctx.data.edit = data;
  353. try {
  354. call(entry.call, entry, ctx.model.scope, subctx, argv, named);
  355. } catch (e) {
  356. ctx.model.exception(e);
  357. return ctx.unknown_error();
  358. }
  359. if (!subctx.result.ok) {
  360. ctx.result = subctx.result;
  361. return;
  362. }
  363. }
  364. if (type_info.insert) {
  365. if (!call(type_info.insert, info, ctx.model.scope, ctx, type_name, name, data, named))
  366. return;
  367. } else {
  368. ctx.data.object_edit[obj_name] ??= {};
  369. obj = ctx.data.object_edit[obj_name];
  370. obj[name] = data;
  371. }
  372. if (named.edit)
  373. ctx.select(info.type ? "edit" : type_name, name);
  374. return ctx.ok(`Added ${type_name} '${name}'`);
  375. };
  376. function object_lookup(ctx, entry, type_name)
  377. {
  378. let info = entry.object_info;
  379. let type_info = info.types ? info.types[type_name] : info.type;
  380. if (!type_info)
  381. return {};
  382. if (type_info.get_object) {
  383. let objs = call(type_info.get_object, info, ctx.model.scope, ctx, type_name);
  384. if (type(objs) != "object")
  385. objs = {};
  386. return objs;
  387. }
  388. let obj_name = type_info.object ?? (info.types ? type_name : type_info.name);
  389. return ctx.data.object_edit[obj_name];
  390. }
  391. function object_values(ctx, entry, type_name)
  392. {
  393. let obj = object_lookup(ctx, entry, type_name);
  394. if (!obj)
  395. return [];
  396. return keys(obj);
  397. }
  398. export function object_list_call(ctx, argv, named)
  399. {
  400. let info = this.object_info;
  401. let type_name = info.types ? argv[0] : info.type.name;
  402. return ctx.list(type_name + " list", object_values(ctx, this, type_name));
  403. };
  404. function object_show_call_single(ctx, entry, type_info, type_name, name)
  405. {
  406. let obj = object_lookup(ctx, entry, type_name);
  407. if (!obj)
  408. return;
  409. entry = obj[name];
  410. if (!entry)
  411. return;
  412. let callctx = ctx.clone();
  413. callctx.data.name = name;
  414. callctx.data.edit = entry;
  415. call(type_info.node.show.call, type_info.node.show, ctx.model.scope, callctx, [], {});
  416. if (callctx.result.ok)
  417. return callctx.result.data;
  418. }
  419. export function object_show_call(ctx, argv, named)
  420. {
  421. let info = this.object_info;
  422. let type_name = info.type.name;
  423. if (argv[0]) {
  424. let data = object_show_call_single(ctx, this, info.type, type_name, argv[0]);
  425. if (!data)
  426. return;
  427. return ctx.table("Values", data);
  428. }
  429. let ret = {};
  430. for (let name in object_values(ctx, this, type_name)) {
  431. let data = object_show_call_single(ctx, this, info.type, type_name, name);
  432. if (!data)
  433. continue;
  434. ret[type_name + " " + name] = data;
  435. }
  436. return ctx.multi_table(type_name + " list", ret);
  437. };
  438. export function edit_create_destroy(info, node)
  439. {
  440. let type_arg = [];
  441. if (info.types)
  442. type_arg = [{
  443. name: "type",
  444. help: "Type",
  445. type: "enum",
  446. required: true,
  447. value: keys(info.types),
  448. }];
  449. let name_arg = {
  450. name: "name",
  451. help: "Name",
  452. type: "string",
  453. required: true,
  454. };
  455. let delete_name_arg = {
  456. ...name_arg,
  457. type: "enum",
  458. value: function(ctx, argv) {
  459. return object_values(ctx, this, argv[0]);
  460. }
  461. };
  462. let show_name_arg = {
  463. ...delete_name_arg,
  464. required: false,
  465. };
  466. let named_args = info.named_args ?? {};
  467. let create_params = {};
  468. if (info.types) {
  469. for (let name, val in info.types)
  470. create_params[name] = { ...object_create_params(val.node), ...named_args };
  471. } else {
  472. create_params = { ...object_create_params(info.type.node), ...named_args };
  473. }
  474. let types_info = info.types ? "(" + join(", ", keys(info.types)) + ")" : info.type.name;
  475. let cmds = {
  476. destroy: {
  477. object_info: info,
  478. help: "Delete " + types_info,
  479. args: [ ...type_arg, delete_name_arg ],
  480. call: object_destroy_call,
  481. },
  482. list: {
  483. object_info: info,
  484. help: "List " + types_info,
  485. args: [ ...type_arg ],
  486. call: object_list_call,
  487. },
  488. create: {
  489. object_info: info,
  490. help: "Create " + types_info,
  491. args: [ ...type_arg, name_arg ],
  492. type_params: create_params,
  493. named_args: function(ctx, argv) {
  494. if (!this.object_info.types)
  495. return this.type_params;
  496. if (!argv[0])
  497. return;
  498. return this.type_params[argv[0]];
  499. },
  500. call: object_create_call,
  501. },
  502. };
  503. let info_types = info.types;
  504. if (!info_types) {
  505. info_types = {};
  506. info_types[info.type.name] = info.type;
  507. cmds.show = {
  508. object_info: info,
  509. help: "Show " + types_info,
  510. args: [ show_name_arg ],
  511. call: object_show_call,
  512. };
  513. }
  514. for (let name, val in info_types) {
  515. let cmd_name = info.types ? name : "edit";
  516. cmds[cmd_name] = {
  517. object_name: name,
  518. object_info: info,
  519. help: "Edit " + name,
  520. args: [
  521. {
  522. ...name_arg,
  523. type: "enum",
  524. value: function(ctx, argv) {
  525. return object_values(ctx, this, this.object_name);
  526. }
  527. }
  528. ],
  529. select_node: val.node_name,
  530. select: function(ctx, argv) {
  531. let name = argv[0];
  532. if (!name) {
  533. ctx.missing_argument();
  534. return;
  535. }
  536. let obj = object_lookup(ctx, this, this.object_name);
  537. if (!obj) {
  538. ctx.invalid_argument("Object not found");
  539. return;
  540. }
  541. let entry = obj[name];
  542. if (!entry) {
  543. ctx.invalid_argument(`${name} not found: %s`, name);
  544. return;
  545. }
  546. return ctx.set(`${this.object_name} "${name}"`, {
  547. name,
  548. edit: entry,
  549. object_edit: entry,
  550. });
  551. }
  552. };
  553. }
  554. if (node)
  555. for (let cmd, val in cmds)
  556. node[cmd] = val;
  557. return cmds;
  558. };