2056ce9094731a35c1b31172b940b073cf0e9105
[project/luci2/ui.git] / luci2 / htdocs / luci2 / uci.js
1 Class.extend({
2 init: function()
3 {
4 this.state = {
5 newidx: 0,
6 values: { },
7 creates: { },
8 changes: { },
9 deletes: { },
10 reorder: { }
11 };
12 },
13
14 callLoad: L.rpc.declare({
15 object: 'uci',
16 method: 'get',
17 params: [ 'config' ],
18 expect: { values: { } }
19 }),
20
21 callOrder: L.rpc.declare({
22 object: 'uci',
23 method: 'order',
24 params: [ 'config', 'sections' ]
25 }),
26
27 callAdd: L.rpc.declare({
28 object: 'uci',
29 method: 'add',
30 params: [ 'config', 'type', 'name', 'values' ],
31 expect: { section: '' }
32 }),
33
34 callSet: L.rpc.declare({
35 object: 'uci',
36 method: 'set',
37 params: [ 'config', 'section', 'values' ]
38 }),
39
40 callDelete: L.rpc.declare({
41 object: 'uci',
42 method: 'delete',
43 params: [ 'config', 'section', 'options' ]
44 }),
45
46 callApply: L.rpc.declare({
47 object: 'uci',
48 method: 'apply',
49 params: [ 'timeout', 'rollback' ]
50 }),
51
52 callConfirm: L.rpc.declare({
53 object: 'uci',
54 method: 'confirm'
55 }),
56
57 createSID: function(conf)
58 {
59 var v = this.state.values;
60 var n = this.state.creates;
61 var sid;
62
63 do {
64 sid = "new%06x".format(Math.random() * 0xFFFFFF);
65 } while ((n[conf] && n[conf][sid]) || (v[conf] && v[conf][sid]));
66
67 return sid;
68 },
69
70 reorderSections: function()
71 {
72 var v = this.state.values;
73 var n = this.state.creates;
74 var r = this.state.reorder;
75
76 if ($.isEmptyObject(r))
77 return L.deferrable();
78
79 L.rpc.batch();
80
81 /*
82 gather all created and existing sections, sort them according
83 to their index value and issue an uci order call
84 */
85 for (var c in r)
86 {
87 var o = [ ];
88
89 if (n[c])
90 for (var s in n[c])
91 o.push(n[c][s]);
92
93 for (var s in v[c])
94 o.push(v[c][s]);
95
96 if (o.length > 0)
97 {
98 o.sort(function(a, b) {
99 return (a['.index'] - b['.index']);
100 });
101
102 var sids = [ ];
103
104 for (var i = 0; i < o.length; i++)
105 sids.push(o[i]['.name']);
106
107 this.callOrder(c, sids);
108 }
109 }
110
111 this.state.reorder = { };
112 return L.rpc.flush();
113 },
114
115 load: function(packages)
116 {
117 var self = this;
118 var seen = { };
119 var pkgs = [ ];
120
121 if (!$.isArray(packages))
122 packages = [ packages ];
123
124 L.rpc.batch();
125
126 for (var i = 0; i < packages.length; i++)
127 if (!seen[packages[i]] && !self.state.values[packages[i]])
128 {
129 pkgs.push(packages[i]);
130 seen[packages[i]] = true;
131 self.callLoad(packages[i]);
132 }
133
134 return L.rpc.flush().then(function(responses) {
135 for (var i = 0; i < responses.length; i++)
136 self.state.values[pkgs[i]] = responses[i];
137
138 return pkgs;
139 });
140 },
141
142 unload: function(packages)
143 {
144 if (!$.isArray(packages))
145 packages = [ packages ];
146
147 for (var i = 0; i < packages.length; i++)
148 {
149 delete this.state.values[packages[i]];
150 delete this.state.creates[packages[i]];
151 delete this.state.changes[packages[i]];
152 delete this.state.deletes[packages[i]];
153 }
154 },
155
156 add: function(conf, type, name)
157 {
158 var n = this.state.creates;
159 var sid = name || this.createSID(conf);
160
161 if (!n[conf])
162 n[conf] = { };
163
164 n[conf][sid] = {
165 '.type': type,
166 '.name': sid,
167 '.create': name,
168 '.anonymous': !name,
169 '.index': 1000 + this.state.newidx++
170 };
171
172 return sid;
173 },
174
175 remove: function(conf, sid)
176 {
177 var n = this.state.creates;
178 var c = this.state.changes;
179 var d = this.state.deletes;
180
181 /* requested deletion of a just created section */
182 if (n[conf] && n[conf][sid])
183 {
184 delete n[conf][sid];
185 }
186 else
187 {
188 if (c[conf])
189 delete c[conf][sid];
190
191 if (!d[conf])
192 d[conf] = { };
193
194 d[conf][sid] = true;
195 }
196 },
197
198 sections: function(conf, type, cb)
199 {
200 var sa = [ ];
201 var v = this.state.values[conf];
202 var n = this.state.creates[conf];
203 var c = this.state.changes[conf];
204 var d = this.state.deletes[conf];
205
206 if (!v)
207 return sa;
208
209 for (var s in v)
210 if (!d || d[s] !== true)
211 if (!type || v[s]['.type'] == type)
212 sa.push($.extend({ }, v[s], c ? c[s] : undefined));
213
214 if (n)
215 for (var s in n)
216 if (!type || n[s]['.type'] == type)
217 sa.push(n[s]);
218
219 sa.sort(function(a, b) {
220 return a['.index'] - b['.index'];
221 });
222
223 for (var i = 0; i < sa.length; i++)
224 sa[i]['.index'] = i;
225
226 if (typeof(cb) == 'function')
227 for (var i = 0; i < sa.length; i++)
228 cb.call(this, sa[i], sa[i]['.name']);
229
230 return sa;
231 },
232
233 get: function(conf, sid, opt)
234 {
235 var v = this.state.values;
236 var n = this.state.creates;
237 var c = this.state.changes;
238 var d = this.state.deletes;
239
240 if (typeof(sid) == 'undefined')
241 return undefined;
242
243 /* requested option in a just created section */
244 if (n[conf] && n[conf][sid])
245 {
246 if (!n[conf])
247 return undefined;
248
249 if (typeof(opt) == 'undefined')
250 return n[conf][sid];
251
252 return n[conf][sid][opt];
253 }
254
255 /* requested an option value */
256 if (typeof(opt) != 'undefined')
257 {
258 /* check whether option was deleted */
259 if (d[conf] && d[conf][sid])
260 {
261 if (d[conf][sid] === true)
262 return undefined;
263
264 for (var i = 0; i < d[conf][sid].length; i++)
265 if (d[conf][sid][i] == opt)
266 return undefined;
267 }
268
269 /* check whether option was changed */
270 if (c[conf] && c[conf][sid] && typeof(c[conf][sid][opt]) != 'undefined')
271 return c[conf][sid][opt];
272
273 /* return base value */
274 if (v[conf] && v[conf][sid])
275 return v[conf][sid][opt];
276
277 return undefined;
278 }
279
280 /* requested an entire section */
281 if (v[conf])
282 return v[conf][sid];
283
284 return undefined;
285 },
286
287 set: function(conf, sid, opt, val)
288 {
289 var v = this.state.values;
290 var n = this.state.creates;
291 var c = this.state.changes;
292 var d = this.state.deletes;
293
294 if (typeof(sid) == 'undefined' ||
295 typeof(opt) == 'undefined' ||
296 opt.charAt(0) == '.')
297 return;
298
299 if (n[conf] && n[conf][sid])
300 {
301 if (typeof(val) != 'undefined')
302 n[conf][sid][opt] = val;
303 else
304 delete n[conf][sid][opt];
305 }
306 else if (typeof(val) != 'undefined')
307 {
308 /* do not set within deleted section */
309 if (d[conf] && d[conf][sid] === true)
310 return;
311
312 /* only set in existing sections */
313 if (!v[conf] || !v[conf][sid])
314 return;
315
316 if (!c[conf])
317 c[conf] = { };
318
319 if (!c[conf][sid])
320 c[conf][sid] = { };
321
322 /* undelete option */
323 if (d[conf] && d[conf][sid])
324 d[conf][sid] = L.filterArray(d[conf][sid], opt);
325
326 c[conf][sid][opt] = val;
327 }
328 else
329 {
330 /* only delete in existing sections */
331 if (!v[conf] || !v[conf][sid])
332 return;
333
334 if (!d[conf])
335 d[conf] = { };
336
337 if (!d[conf][sid])
338 d[conf][sid] = [ ];
339
340 if (d[conf][sid] !== true)
341 d[conf][sid].push(opt);
342 }
343 },
344
345 unset: function(conf, sid, opt)
346 {
347 return this.set(conf, sid, opt, undefined);
348 },
349
350 get_first: function(conf, type, opt)
351 {
352 var sid = undefined;
353
354 L.uci.sections(conf, type, function(s) {
355 if (typeof(sid) != 'string')
356 sid = s['.name'];
357 });
358
359 return this.get(conf, sid, opt);
360 },
361
362 set_first: function(conf, type, opt, val)
363 {
364 var sid = undefined;
365
366 L.uci.sections(conf, type, function(s) {
367 if (typeof(sid) != 'string')
368 sid = s['.name'];
369 });
370
371 return this.set(conf, sid, opt, val);
372 },
373
374 unset_first: function(conf, type, opt)
375 {
376 return this.set_first(conf, type, opt, undefined);
377 },
378
379 swap: function(conf, sid1, sid2)
380 {
381 var s1 = this.get(conf, sid1);
382 var s2 = this.get(conf, sid2);
383 var n1 = s1 ? s1['.index'] : NaN;
384 var n2 = s2 ? s2['.index'] : NaN;
385
386 if (isNaN(n1) || isNaN(n2))
387 return false;
388
389 s1['.index'] = n2;
390 s2['.index'] = n1;
391
392 this.state.reorder[conf] = true;
393
394 return true;
395 },
396
397 save: function()
398 {
399 L.rpc.batch();
400
401 var v = this.state.values;
402 var n = this.state.creates;
403 var c = this.state.changes;
404 var d = this.state.deletes;
405
406 var self = this;
407 var snew = [ ];
408 var pkgs = { };
409
410 if (n)
411 for (var conf in n)
412 {
413 for (var sid in n[conf])
414 {
415 var r = {
416 config: conf,
417 values: { }
418 };
419
420 for (var k in n[conf][sid])
421 {
422 if (k == '.type')
423 r.type = n[conf][sid][k];
424 else if (k == '.create')
425 r.name = n[conf][sid][k];
426 else if (k.charAt(0) != '.')
427 r.values[k] = n[conf][sid][k];
428 }
429
430 snew.push(n[conf][sid]);
431
432 self.callAdd(r.config, r.type, r.name, r.values);
433 }
434
435 pkgs[conf] = true;
436 }
437
438 if (c)
439 for (var conf in c)
440 {
441 for (var sid in c[conf])
442 self.callSet(conf, sid, c[conf][sid]);
443
444 pkgs[conf] = true;
445 }
446
447 if (d)
448 for (var conf in d)
449 {
450 for (var sid in d[conf])
451 {
452 var o = d[conf][sid];
453 self.callDelete(conf, sid, (o === true) ? undefined : o);
454 }
455
456 pkgs[conf] = true;
457 }
458
459 return L.rpc.flush().then(function(responses) {
460 /*
461 array "snew" holds references to the created uci sections,
462 use it to assign the returned names of the new sections
463 */
464 for (var i = 0; i < snew.length; i++)
465 snew[i]['.name'] = responses[i];
466
467 return self.reorderSections();
468 }).then(function() {
469 pkgs = L.toArray(pkgs);
470
471 self.unload(pkgs);
472
473 return self.load(pkgs);
474 });
475 },
476
477 apply: function(timeout)
478 {
479 var self = this;
480 var date = new Date();
481 var deferred = $.Deferred();
482
483 if (typeof(timeout) != 'number' || timeout < 1)
484 timeout = 10;
485
486 self.callApply(timeout, true).then(function(rv) {
487 if (rv != 0)
488 {
489 deferred.rejectWith(self, [ rv ]);
490 return;
491 }
492
493 var try_deadline = date.getTime() + 1000 * timeout;
494 var try_confirm = function()
495 {
496 return self.callConfirm().then(function(rv) {
497 if (rv != 0)
498 {
499 if (date.getTime() < try_deadline)
500 window.setTimeout(try_confirm, 250);
501 else
502 deferred.rejectWith(self, [ rv ]);
503
504 return;
505 }
506
507 deferred.resolveWith(self, [ rv ]);
508 });
509 };
510
511 window.setTimeout(try_confirm, 1000);
512 });
513
514 return deferred;
515 },
516
517 changes: L.rpc.declare({
518 object: 'uci',
519 method: 'changes',
520 expect: { changes: { } }
521 }),
522
523 readable: function(conf)
524 {
525 return L.session.hasACL('uci', conf, 'read');
526 },
527
528 writable: function(conf)
529 {
530 return L.session.hasACL('uci', conf, 'write');
531 }
532 });