luci2: implement Class.require() and Class.instantiate()
[project/luci2/ui.git] / luci2 / htdocs / luci2 / view / system.software.js
1 L.ui.view.extend({
2         title: L.tr('Package management'),
3
4         opkg: {
5                 updateLists: L.rpc.declare({
6                         object: 'luci2.opkg',
7                         method: 'update',
8                         expect: { '': { } }
9                 }),
10
11                 _allPackages: L.rpc.declare({
12                         object: 'luci2.opkg',
13                         method: 'list',
14                         params: [ 'offset', 'limit', 'pattern' ],
15                         expect: { '': { } }
16                 }),
17
18                 _installedPackages: L.rpc.declare({
19                         object: 'luci2.opkg',
20                         method: 'list_installed',
21                         params: [ 'offset', 'limit', 'pattern' ],
22                         expect: { '': { } }
23                 }),
24
25                 _findPackages: L.rpc.declare({
26                         object: 'luci2.opkg',
27                         method: 'find',
28                         params: [ 'offset', 'limit', 'pattern' ],
29                         expect: { '': { } }
30                 }),
31
32                 _fetchPackages: function(action, offset, limit, pattern)
33                 {
34                         var packages = [ ];
35
36                         return action(offset, limit, pattern).then(function(list) {
37                                 if (!list.total || !list.packages)
38                                         return { length: 0, total: 0 };
39
40                                 packages.push.apply(packages, list.packages);
41                                 packages.total = list.total;
42
43                                 if (limit <= 0)
44                                         limit = list.total;
45
46                                 if (packages.length >= limit)
47                                         return packages;
48
49                                 L.rpc.batch();
50
51                                 for (var i = offset + packages.length; i < limit; i += 100)
52                                         action(i, (Math.min(i + 100, limit) % 100) || 100, pattern);
53
54                                 return L.rpc.flush();
55                         }).then(function(lists) {
56                                 for (var i = 0; i < lists.length; i++)
57                                 {
58                                         if (!lists[i].total || !lists[i].packages)
59                                                 continue;
60
61                                         packages.push.apply(packages, lists[i].packages);
62                                         packages.total = lists[i].total;
63                                 }
64
65                                 return packages;
66                         });
67                 },
68
69                 listPackages: function(offset, limit, pattern)
70                 {
71                         return this._fetchPackages(this._allPackages, offset, limit, pattern);
72                 },
73
74                 installedPackages: function(offset, limit, pattern)
75                 {
76                         return this._fetchPackages(this._installedPackages, offset, limit, pattern);
77                 },
78
79                 findPackages: function(offset, limit, pattern)
80                 {
81                         return this._fetchPackages(this._findPackages, offset, limit, pattern);
82                 },
83
84                 installPackage: L.rpc.declare({
85                         object: 'luci2.opkg',
86                         method: 'install',
87                         params: [ 'package' ],
88                         expect: { '': { } }
89                 }),
90
91                 removePackage: L.rpc.declare({
92                         object: 'luci2.opkg',
93                         method: 'remove',
94                         params: [ 'package' ],
95                         expect: { '': { } }
96                 }),
97
98                 getConfig: L.rpc.declare({
99                         object: 'luci2.opkg',
100                         method: 'config_get',
101                         expect: { config: '' }
102                 }),
103
104                 setConfig: L.rpc.declare({
105                         object: 'luci2.opkg',
106                         method: 'config_set',
107                         params: [ 'data' ]
108                 }),
109
110                 isInstalled: function(pkg)
111                 {
112                         return this._installedPackages(0, 1, pkg).then(function(list) {
113                                 return (!isNaN(list.total) && list.total > 0);
114                         });
115                 }
116         },
117
118         updateDiskSpace: function()
119         {
120                 return L.system.getDiskInfo().then(function(info) {
121                         $('#package_space').empty().append(
122                                 new L.ui.progress({
123                                         value:  info.root.used / 1024,
124                                         max:    info.root.total / 1024,
125                                         format: '%d ' + L.tr('kB') + ' / %d ' + L.tr('kB') + ' ' + L.trc('Used disk space', 'used') + ' (%d%%)'
126                                 }).render());
127                 });
128         },
129
130         installRemovePackage: function(pkgname, installed)
131         {
132                 var self = this;
133
134                 var dspname   = pkgname.replace(/^.+\//, '');
135                 var action    = installed ? self.opkg.removePackage : self.opkg.installPackage;
136                 var title     = (installed ? L.tr('Removing package "%s" …') : L.tr('Installing package "%s" …')).format(dspname);
137                 var confirm   = (installed ? L.tr('Really remove package "%h" ?') : L.tr('Really install package "%h" ?')).format(dspname);
138
139                 L.ui.dialog(title, confirm, {
140                         style:   'confirm',
141                         confirm: function() {
142                                 L.ui.dialog(title, L.tr('Waiting for package manager …'), { style: 'wait' });
143
144                                 action.call(self.opkg, pkgname).then(function(res) {
145                                         self.fetchInstalledList().then(function() { return self.fetchPackageList(); }).then(function() {
146                                                 var output = [ ];
147
148                                                 if (res.stdout)
149                                                         output.push($('<pre />').text(res.stdout));
150
151                                                 if (res.stderr)
152                                                         output.push($('<pre />').addClass('alert-message').text(res.stderr));
153
154                                                 output.push(res.code ? L.tr('Package manager failed with status %d.').format(res.code) : L.tr('Package manager finished successfully.'));
155
156                                                 L.ui.dialog(title, output, { style: 'close' });
157
158                                                 if (name)
159                                                         $('#package_url').val('');
160                                         });
161                                 });
162                         }
163                 });
164         },
165
166         fetchInstalledList: function()
167         {
168                 var self = this;
169                 return self.opkg.installedPackages(0, 0, '*').then(function(list) {
170                         self.installedList = { };
171                         for (var i = 0; i < list.length; i++)
172                                 self.installedList[list[i][0]] = true;
173                 });
174         },
175
176         fetchPackageList: function(offset, interactive)
177         {
178                 if (interactive)
179                         L.ui.loading(true);
180
181                 if (typeof(offset) == 'undefined')
182                         offset = parseInt($('#package_filter').attr('offset')) || 0;
183
184                 var self = this;
185
186                 var pattern = $('#package_filter').val() || '';
187                 var action;
188
189                 if (pattern.length)
190                 {
191                         action = $('#package_which').prop('checked') ? self.opkg.installedPackages : self.opkg.findPackages;
192                         pattern = '*' + pattern + '*';
193
194                         $('#package_filter').next().attr('src', L.globals.resource + '/icons/cbi/remove.gif');
195                 }
196                 else
197                 {
198                         action = $('#package_which').prop('checked') ? self.opkg.installedPackages : self.opkg.listPackages;
199                         pattern = '*';
200
201                         $('#package_filter').next().attr('src', L.globals.resource + '/icons/cbi/find.gif');
202                 }
203
204                 $('#package_filter').attr('offset', offset);
205
206                 var install_disabled = $('#package_install').attr('disabled');
207
208                 return action.call(self.opkg, offset, 100, pattern).then(function(list) {
209                         var packageTable = new L.ui.table({
210                                 placeholder: L.tr('No matching packages found.'),
211                                 columns: [ {
212                                         caption: L.trc('Package table header', 'Package'),
213                                         key:     0
214                                 }, {
215                                         caption: L.trc('Package table header', 'Version'),
216                                         key:     1,
217                                         format:  function(v) {
218                                                 return (v.length > 15 ? v.substring(0, 14) + '…' : v);
219                                         }
220                                 }, {
221                                         caption: L.trc('Package table header', 'Description'),
222                                         key:     2
223                                 }, {
224                                         caption: L.trc('Package table header', 'Installation Status'),
225                                         key:     0,
226                                         width:   '120px',
227                                         format: function(v, n) {
228                                                 var inst = self.installedList[list[n][0]];
229                                                 return L.ui.button(inst ? L.trc('Package state', 'Installed') : L.trc('Package state', 'Not installed'), inst ? 'success' : 'danger')
230                                                         .css('width', '100%')
231                                                         .attr('disabled', install_disabled)
232                                                         .attr('pkgname', list[n][0])
233                                                         .attr('installed', inst)
234                                                         .click(function() {
235                                                                 self.installRemovePackage(this.getAttribute('pkgname'), this.getAttribute('installed') == 'true');
236                                                         });
237                                         }
238                                 } ]
239                         });
240
241                         packageTable.rows(list);
242                         packageTable.insertInto('#package_table');
243
244                         if (offset > 0)
245                                 $('#package_prev')
246                                         .attr('offset', offset - 100)
247                                         .attr('disabled', false)
248                                         .text('« %d - %d'.format(offset - 100 + 1, offset));
249                         else
250                                 $('#package_prev')
251                                         .attr('disabled', true)
252                                         .text('« %d - %d'.format(1, Math.min(100, list.total)));
253
254                         if ((offset + 100) < list.total)
255                                 $('#package_next')
256                                         .attr('offset', offset + 100)
257                                         .attr('disabled', false)
258                                         .text('%d - %d »'.format(offset + 100 + 1, Math.min(offset + 200, list.total)));
259                         else
260                                 $('#package_next')
261                                         .attr('disabled', true)
262                                         .text('%d - %d »'.format(list.total - (list.total % 100) + 1, list.total));
263
264                         if (interactive)
265                                 L.ui.loading(false);
266                 }).then(self.updateDiskSpace);
267         },
268
269         execute: function()
270         {
271                 var self = this;
272
273                 $('textarea, input.cbi-button-save').attr('disabled', !this.options.acls.software);
274                 $('#package_update, #package_url, #package_install').attr('disabled', !this.options.acls.software);
275
276                 return $.when(
277                         self.opkg.getConfig().then(function(config) {
278                                 $('#config textarea')
279                                         .attr('rows', (config.match(/\n/g) || [ ]).length + 1)
280                                         .val(config);
281
282                                 $('#config button')
283                                         .click(function() {
284                                                 var data = ($('#config textarea').val() || '').replace(/\r/g, '').replace(/\n?$/, '\n');
285                                                 L.ui.loading(true);
286                                                 self.opkg.setConfig(data).then(function() {
287                                                         $('#config textarea')
288                                                                 .attr('rows', (data.match(/\n/g) || [ ]).length + 1)
289                                                                 .val(data);
290
291                                                         L.ui.loading(false);
292                                                 });
293                                         });
294                         }),
295                         self.fetchInstalledList(),
296                         self.updateDiskSpace()
297                 ).then(function() {
298                         $('#package_prev, #package_next').click(function(ev) {
299                                 if (!this.getAttribute('disabled'))
300                                 {
301                                         self.fetchPackageList(parseInt(this.getAttribute('offset')), true);
302                                         this.blur();
303                                 }
304                         });
305
306                         $('#package_filter').next().click(function(ev) {
307                                 $('#package_filter').val('');
308                                 self.fetchPackageList(0, true);
309                         });
310
311                         $('#package_filter').keyup(function(ev) {
312                                 if (ev.which != 13)
313                                         return true;
314
315                                 ev.preventDefault();
316                                 self.fetchPackageList(0, true);
317                                 return false;
318                         });
319
320                         $('#package_which').click(function(ev) {
321                                 this.blur();
322                                 self.fetchPackageList(0, true);
323                         });
324
325                         $('#package_url').keyup(function(ev) {
326                                 if (ev.which != 13)
327                                         return true;
328
329                                 ev.preventDefault();
330
331                                 if (this.value)
332                                         self.installRemovePackage(this.value, false);
333                         });
334
335                         $('#package_install').click(function(ev) {
336                                 var name = $('#package_url').val();
337                                 if (name)
338                                         self.installRemovePackage(name, false);
339                         });
340
341                         $('#package_update').click(function(ev) {
342                                 L.ui.dialog(L.tr('Updating package lists'), L.tr('Waiting for package manager …'), { style: 'wait' });
343                                 self.opkg.updateLists().then(function(res) {
344                                         var output = [ ];
345
346                                         if (res.stdout)
347                                                 output.push($('<pre />').text(res.stdout));
348
349                                         if (res.stderr)
350                                                 output.push($('<pre />').addClass('alert-message').text(res.stderr));
351
352                                         output.push(res.code ? L.tr('Package manager failed with status %d.').format(res.code) : L.tr('Package manager finished successfully.'));
353
354                                         L.ui.dialog(L.tr('Updating package lists'), output, { style: 'close' });
355                                 });
356                         });
357
358                         return self.fetchPackageList(0);
359                 });
360         }
361 });