]> git.ipfire.org Git - people/mfischer/ipfire-2.x.git/blob - html/html/include/pakfire.js
pakfire: Implement feedback from mailing list discussion
[people/mfischer/ipfire-2.x.git] / html / html / include / pakfire.js
1 /*#############################################################################
2 # #
3 # IPFire.org - A linux based firewall #
4 # Copyright (C) 2007-2021 IPFire Team <info@ipfire.org> #
5 # #
6 # This program is free software: you can redistribute it and/or modify #
7 # it under the terms of the GNU General Public License as published by #
8 # the Free Software Foundation, either version 3 of the License, or #
9 # (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
18 # #
19 #############################################################################*/
20
21 "use strict";
22
23 // Pakfire Javascript functions (requires jQuery)
24 class PakfireJS {
25 constructor() {
26 //--- Public properties ---
27 // Translation strings
28 this.i18n = new PakfireI18N();
29
30 //--- Private properties ---
31 // Status flags (access outside constructor only with setter/getter)
32 this._states = Object.create(null);
33 this._states.running = false;
34 this._states.reboot = false;
35 this._states.failure = false;
36
37 // Status refresh helper
38 this._autoRefresh = {
39 delay: 1000, //Delay between requests (minimum: 500, default: 1s)
40 jsonAction: 'getstatus', //CGI POST action parameter
41 timeout: 5000, //XHR timeout (0 to disable, default: 5s)
42
43 delayTimer: null, //setTimeout reference
44 jqXHR: undefined, //jQuery.ajax promise reference
45 get runningDelay() { //Waiting for end of delay
46 return (this.delayTimer !== null);
47 },
48 get runningXHR() { //Waiting for CGI response
49 return (this.jqXHR && (this.jqXHR.state() === 'pending'));
50 },
51 get isRunning() {
52 return (this.runningDelay || this.runningXHR);
53 }
54 };
55
56 // Return to main screen helper
57 this._pageReload = {
58 delay: 1000, //Delay before page reload (default: 1s)
59 enabled: false, //Reload disabled by default
60
61 delayTimer: null, //setTimeout reference
62 get isTriggered() { //Reload timer started
63 return (this.delayTimer !== null);
64 }
65 };
66 }
67
68 //### Public properties ###
69
70 // Note on using the status flags
71 // running: Pakfire is performing a task.
72 // Writing "true" activates the periodic AJAX/JSON status polling, writing "false" stops polling.
73 // When the task has been completed, status polling stops and this returns to "false".
74 // The page can then be reloaded to go back to the main screen. Writing "false" does not trigger a reload.
75 // "refreshInterval" and "setupPageReload" can be used to adjust the respective behaviour.
76 // reboot: An update requires a reboot.
77 // If set to "true", a link to the reboot menu is shown after the task is completed.
78 // failure: An error has occured.
79 // To display the error log, the page does not return to the main screen.
80
81 // Pakfire is running (true/false)
82 set running(state) {
83 if(this._states.running !== state) {
84 this._states.running = state;
85 this._states_onChange('running');
86 }
87 }
88 get running() {
89 return this._states.running;
90 }
91
92 // Reboot needed (true/false)
93 set reboot(state) {
94 if(this._states.reboot !== state) {
95 this._states.reboot = state;
96 this._states_onChange('reboot');
97 }
98 }
99 get reboot() {
100 return this._states.reboot;
101 }
102
103 // Error encountered (true/false)
104 set failure(state) {
105 if(this._states.failure !== state) {
106 this._states.failure = state;
107 this._states_onChange('failure');
108 }
109 }
110 get failure() {
111 return this._states.failure;
112 }
113
114 // Status refresh interval in ms
115 set refreshInterval(delay) {
116 if(delay < 500) {
117 delay = 500; //enforce reasonable minimum
118 }
119 this._autoRefresh.delay = delay;
120 }
121 get refreshInterval() {
122 return this._autoRefresh.delay;
123 }
124
125 // Configure page reload after successful task (returns to main screen)
126 // delay: In ms
127 setupPageReload(enabled, delay) {
128 if(delay < 0) {
129 delay = 0;
130 }
131 this._pageReload.delay = delay;
132 this._pageReload.enabled = enabled;
133 }
134
135 // Document loaded (call once from jQuery.ready)
136 documentReady() {
137 // Status refresh late start
138 if(this.running && (! this._autoRefresh.isRunning)) {
139 this._autoRefresh_runNow();
140 }
141 }
142
143 // Reload entire CGI page (clears POST/GET data from history)
144 documentReload() {
145 let url = window.location.origin + window.location.pathname;
146 window.location.replace(url);
147 }
148
149 //### Private properties ###
150
151 // Pakfire status change handler
152 // property: Affected status (running, reboot, ...)
153 _states_onChange(property) {
154 // Always update UI
155 if(this.running) {
156 $('#pflog-status').text(this.i18n.get('working'));
157 $('#pflog-action').empty();
158 } else {
159 if(this.failure) {
160 $('#pflog-status').text(this.i18n.get('finished error'));
161 } else {
162 $('#pflog-status').text(this.i18n.get('finished'));
163 }
164 if(this.reboot) { //Enable return or reboot links in UI
165 $('#pflog-action').html(this.i18n.get('link_return') + " &bull; " + this.i18n.get('link_reboot'));
166 } else {
167 $('#pflog-action').html(this.i18n.get('link_return'));
168 }
169 }
170
171 // Start/stop status refresh if Pakfire started/stopped
172 if(property === 'running') {
173 if(this.running) {
174 this._autoRefresh_runNow();
175 } else {
176 this._autoRefresh_clearSchedule();
177 }
178 }
179
180 // Always stay in the log viewer if Pakfire failed
181 if(property === 'failure') {
182 if(this.failure) {
183 this._pageReload_cancel();
184 }
185 }
186 }
187
188 //--- Status refresh scheduling functions ---
189
190 // Immediately perform AJAX status refresh request
191 _autoRefresh_runNow() {
192 if(this._autoRefresh.runningXHR) {
193 return; // Don't send multiple requests
194 }
195 this._autoRefresh_clearSchedule(); // Stop scheduled refresh, will send immediately
196
197 // Send AJAX request, attach listeners
198 this._autoRefresh.jqXHR = this._JSON_get(this._autoRefresh.jsonAction, this._autoRefresh.timeout);
199 this._autoRefresh.jqXHR.done(function() { // Request succeeded
200 if(this.running) { // Keep refreshing while Pakfire is running
201 this._autoRefresh_scheduleRun();
202 }
203 });
204 this._autoRefresh.jqXHR.fail(function() { // Request failed
205 this._autoRefresh_scheduleRun(); // Try refreshing until valid status is received
206 });
207 }
208
209 // Schedule next refresh
210 _autoRefresh_scheduleRun() {
211 if(this._autoRefresh.runningDelay || this._autoRefresh.runningXHR) {
212 return; // Refresh already scheduled or in progress
213 }
214 this._autoRefresh.delayTimer = window.setTimeout(function() {
215 this._autoRefresh.delayTimer = null;
216 this._autoRefresh_runNow();
217 }.bind(this), this._autoRefresh.delay);
218 }
219
220 // Stop scheduled refresh (can still be refreshed up to 1x if XHR is already sent)
221 _autoRefresh_clearSchedule() {
222 if(this._autoRefresh.runningDelay) {
223 window.clearTimeout(this._autoRefresh.delayTimer);
224 this._autoRefresh.delayTimer = null;
225 }
226 }
227
228 // Start delayed page reload to return to main screen
229 _pageReload_trigger() {
230 if((! this._pageReload.enabled) || this._pageReload.isTriggered) {
231 return; // Disabled or already started
232 }
233 this._pageReload.delayTimer = window.setTimeout(function() {
234 this._pageReload.delayTimer = null;
235 this.documentReload();
236 }.bind(this), this._pageReload.delay);
237 }
238
239 // Stop scheduled reload
240 _pageReload_cancel() {
241 if(this._pageReload.isTriggered) {
242 window.clearTimeout(this._pageReload.delayTimer);
243 this._pageReload.delayTimer = null;
244 }
245 }
246
247 //--- JSON request & data handling ---
248
249 // Load JSON data from Pakfire CGI, using a POST request
250 // action: POST paramter "json-[action]"
251 // maxTime: XHR timeout, 0 = no timeout
252 _JSON_get(action, maxTime = 0) {
253 return $.ajax({
254 url: '/cgi-bin/pakfire.cgi',
255 method: 'POST',
256 timeout: maxTime,
257 context: this,
258 data: {'ACTION': `json-${action}`},
259 dataType: 'json' //automatically check and convert result
260 })
261 .done(function(response) {
262 this._JSON_process(action, response);
263 });
264 }
265
266 // Process successful response from Pakfire CGI
267 // action: POST paramter "json-[action]" used to send request
268 // data: JSON data object
269 _JSON_process(action, data) {
270 // Pakfire status refresh
271 if(action === this._autoRefresh.jsonAction) {
272 // Update status flags
273 this.running = (data['running'] != '0');
274 this.reboot = (data['reboot'] != '0');
275 this.failure = (data['failure'] != '0');
276
277 // Update timer display
278 if(this.running && data['running_since']) {
279 $('#pflog-time').text(this.i18n.get('since') + " " + data['running_since']);
280 } else {
281 $('#pflog-time').empty();
282 }
283
284 // Print log messages
285 let messages = "";
286 data['messages'].forEach(function(line) {
287 messages += `${line}\n`;
288 });
289 $('#pflog-messages').text(messages);
290
291 // Pakfire finished without errors, return to main screen
292 if((! this.running) && (! this.failure)) {
293 this._pageReload_trigger();
294 }
295 }
296 }
297 }
298
299 // Simple translation strings helper
300 // Format: {key: "translation"}
301 class PakfireI18N {
302 constructor() {
303 this._strings = Object.create(null); //Object without prototypes
304 }
305
306 // Get translation
307 get(key) {
308 if(Object.prototype.hasOwnProperty.call(this._strings, key)) {
309 return this._strings[key];
310 }
311 return `(undefined string '${key}')`;
312 }
313
314 // Load key/translation object
315 load(translations) {
316 if(translations instanceof Object) {
317 Object.assign(this._strings, translations);
318 }
319 }
320 }
321
322 //### Initialize Pakfire ###
323 const pakfire = new PakfireJS();
324
325 $(function() {
326 pakfire.documentReady();
327 });