luci2: move most RPC proxy function declarations into the views using them to reduce...
[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 });