Project

General

Profile

Download (23.8 KB) Statistics
| Branch: | Tag: | Revision:
1
/*
2
 * pfSenseHelpers.js
3
 *
4
 * part of pfSense (https://www.pfsense.org)
5
 * Copyright (c) 2004-2020 Rubicon Communications, LLC (Netgate)
6
 * All rights reserved.
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20

    
21
// These helper functions are used on many/most UI pages to hide/show/disable/enable form elements where required
22

    
23
// Cause the input to be displayed as a required field by adding the element-required class to the label
24
function setRequired(id, req) {
25
	if (req)
26
		$('#' + id).parent().parent('div').find('span:first').addClass('element-required');
27
	else
28
		$('#' + id).parent().parent('div').find('span:first').removeClass('element-required');
29
}
30

    
31
// Hides the <div> in which the specified input element lives so that the input, its label and help text are hidden
32
function hideInput(id, hide) {
33
	if (hide)
34
		$('#' + id).parent().parent('div').addClass('hidden');
35
	else
36
		$('#' + id).parent().parent('div').removeClass('hidden');
37
}
38

    
39
// Hides the <div> in which the specified group input element lives so that the input,
40
// its label and help text are hidden
41
function hideGroupInput(id, hide) {
42
	if (hide)
43
		$('#' + id).parent('div').addClass('hidden');
44
	else
45
		$('#' + id).parent('div').removeClass('hidden');
46
}
47

    
48
function invisibleGroupInput(id, hide) {
49
	if (hide)
50
		$('#' + id).addClass('invisible');
51
	else
52
		$('#' + id).removeClass('invisible');
53
}
54

    
55
// Hides the <div> in which the specified checkbox lives so that the checkbox, its label and help text are hidden
56
function hideCheckbox(id, hide) {
57
	if (hide)
58
		$('#' + id).parent().parent().parent('div').addClass('hidden');
59
	else
60
		$('#' + id).parent().parent().parent('div').removeClass('hidden');
61
}
62

    
63
// Disables the specified input element
64
function disableInput(id, disable) {
65
	$('#' + id).prop("disabled", disable);
66
}
67

    
68
// Hides all elements of the specified class. This will usually be a section
69
function hideClass(s_class, hide) {
70
	if (hide)
71
		$('.' + s_class).hide();
72
	else
73
		$('.' + s_class).show();
74
}
75

    
76
function hideSelect(id, hide) {
77
	if (hide)
78
		$('#' + id).parent('div').parent('div').addClass('hidden');
79
	else
80
		$('#' + id).parent('div').parent('div').removeClass('hidden');
81
}
82

    
83
function hideMultiCheckbox(id, hide) {
84
	if (hide)
85
		$("[name=" + id + "]").parent().addClass('hidden');
86
	else
87
		$("[name=" + id + "]").parent().removeClass('hidden');
88
}
89

    
90
// Hides the <div> in which the specified IP address element lives so that the input, any mask selector, its label and help text are hidden
91
function hideIpAddress(id, hide) {
92
	if (hide)
93
		$('#' + id).parent().parent().parent('div').addClass('hidden');
94
	else
95
		$('#' + id).parent().parent().parent('div').removeClass('hidden');
96
}
97

    
98
// Hides all elements of the specified class belonging to a multiselect.
99
function hideMultiClass(s_class, hide) {
100
	if (hide)
101
		$('.' + s_class).parent().parent().hide();
102
	else
103
		$('.' + s_class).parent().parent().show();
104
}
105

    
106
// Hides div whose label contains the specified text. (Good for StaticText)
107
function hideLabel(text, hide) {
108

    
109
	var element = $('label:contains(' + text + ')');
110

    
111
	if (hide)
112
		element.parent('div').addClass('hidden');
113
	else
114
		element.parent('div').removeClass('hidden');
115
}
116

    
117
// Hides the '/' and the subnet mask of an Ip_Address/subnet_mask group
118
function hideMask(name, hide) {
119
	if (hide) {
120
		$('[id^=' + name + ']').hide();
121
		$('[id^=' + name + ']').prev('span').hide();
122
		$('[id^=' + name + ']').parent('div').removeClass('input-group');
123
	} else {
124
		$('[id^=' + name + ']').show();
125
		$('[id^=' + name + ']').prev('span').show();
126
		$('[id^=' + name + ']').parent('div').addClass('input-group');
127
	}
128
}
129

    
130
// Set the help text for a given input
131
function setHelpText(id, text) {
132
	$('#' + id).parent().parent('div').find('span:nth-child(2)').html(text);
133
}
134

    
135
// Toggle table row checkboxes and background colors on the pages that use sortable tables:
136
//	/usr/local/www/firewall_nat.php
137
//	/usr/local/www/firewall_nat_1to1.php
138
//	/usr/local/www/firewall_nat_out.php
139
//	/usr/local/www/firewall_rules.php
140
//	/usr/local/www/vpn_ipsec.php
141
// Striping of the tables is handled here, NOT with the Bootstrap table-striped class because it would
142
// get confused when rows are sorted or deleted.
143

    
144
function fr_toggle(id, prefix) {
145
	if (!prefix)
146
		prefix = 'fr';
147

    
148
	var checkbox = document.getElementById(prefix + 'c' + id);
149
	checkbox.checked = !checkbox.checked;
150
	fr_bgcolor(id, prefix);
151
}
152

    
153
// Change background color of selected row based on state of checkbox
154
function fr_bgcolor(id, prefix) {
155
	if (!prefix)
156
		prefix = 'fr';
157

    
158
	var row = $('#' + prefix + id);
159

    
160
	if ($('#' + prefix + 'c' + id).prop('checked') ) {
161
		row.addClass('active');
162
	} else {
163
		row.removeClass('active');
164
	}
165
}
166

    
167
// The following functions are used by Form_Groups assigned a class of "repeatable" and provide the ability
168
// to add/delete rows of sequentially numbered elements, their labels and their help text
169
// See firewall_aliases_edit.php for an example
170

    
171
// NOTE: retainhelp is a global var that when defined prevents any help text from being deleted as lines are inserted.
172
// IOW it causes every row to have help text, not just the last row
173

    
174
function setMasks() {
175
	// Find all ipaddress masks and make dynamic based on address family of input
176
	$('span.pfIpMask + select').each(function (idx, select){
177
		var input = $(select).prevAll('input[type=text]');
178

    
179
		input.on('change', function(e){
180
			var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128;
181
			if (!isV6)
182
				max = 32;
183

    
184
			if (input.val() == "")
185
				return;
186

    
187
			// Sometimes the mask includes '0' (e.g. for VPNs), sometimes it does not
188
			if (select.options[select.options.length - 1].value == 0) {
189
				var mm = max + 1;
190
			} else {
191
				var mm = max;
192
			}
193

    
194
			while (select.options.length > mm)
195
				select.remove(0);
196

    
197
			if (select.options.length < max) {
198
				for (var i=select.options.length; i<=max; i++)
199
					select.options.add(new Option(i, i), 0);
200
			}
201
		});
202

    
203
		// Fire immediately
204
		input.change();
205
	});
206
}
207

    
208
// Complicated function to move all help text associated with this input id to the same id
209
// on the row above. That way if you delete the last row, you don't lose the help
210
function moveHelpText(id) {
211

    
212
	$('#' + id).parent('div').parent('div').find('input, select, checkbox, button').each(function() {	 // For each <span></span>
213
		var fromId = this.id;
214
		var toId = decrStringInt(fromId);
215
		var helpSpan;
216

    
217

    
218
		if (!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) {
219
			if ($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group')) {
220
				helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone();
221
			} else {
222
				helpSpan = $('#' + fromId).parent('div').find('span:last').clone();
223
			}
224

    
225
			if ($(helpSpan).hasClass('help-block')) {
226
				if ($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group')) {
227
					$('#' + decrStringInt(fromId)).parent('div').after(helpSpan);
228
				} else {
229
					$('#' + decrStringInt(fromId)).after(helpSpan);
230
				}
231
			}
232
		}
233
	});
234
}
235

    
236
// Increment the number at the end of the string
237
function getStringInt( str )	{
238
  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
239
  return Number( data[ 2 ] );
240
}
241

    
242
// Increment the number at the end of the string
243
function bumpStringInt( str )	{
244
  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
245

    
246
  if (data)
247
	newStr = data[ 1 ] + ( Number( data[ 2 ] ) + 1 ) + data[ 3 ];
248

    
249
  return newStr || str;
250
}
251

    
252
// Decrement the number at the end of the string
253
function decrStringInt( str )	{
254
  var data = str.match(/(\D*)(\d+)(\D*)/), newStr = "";
255

    
256
  if (data)
257
	newStr = data[ 1 ] + ( Number( data[ 2 ] ) - 1 ) + data[ 3 ];
258

    
259
  return newStr || str;
260
}
261

    
262
// Called after a delete so that there are no gaps in the numbering. Most of the time the config system doesn't care about
263
// gaps, but I do :)
264
function renumber() {
265
	var idx = 0;
266

    
267
	$('.repeatable').each(function() {
268

    
269
		$(this).find('input').each(function() {
270
			$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
271
			$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
272
		});
273

    
274
		$(this).find('select').each(function() {
275
			$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
276
			$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
277
		});
278

    
279
		$(this).find('button').each(function() {
280
			$(this).prop("id", this.id.replace(/\d+$/, "") + idx);
281
			$(this).prop("name", this.name.replace(/\d+$/, "") + idx);
282
		});
283

    
284
//		$(this).find('label').attr('for', $(this).find('label').attr('for').replace(/\d+$/, "") + idx);
285

    
286
		idx++;
287
	});
288
}
289

    
290
function delete_row(rowDelBtn) {
291
	var rowLabel;
292

    
293
	// If we are deleting row zero, we need to save/restore the label
294
	if ( (rowDelBtn == "deleterow0") && ((typeof retainhelp) == "undefined")) {
295
		rowLabel = $('#' + rowDelBtn).parent('div').parent('div').find('label:first').text();
296
	}
297

    
298
	$('#' + rowDelBtn).parent('div').parent('div').remove();
299

    
300
	renumber();
301
	checkLastRow();
302

    
303
	if (rowDelBtn == "deleterow0") {
304
		$('#' + rowDelBtn).parent('div').parent('div').find('label:first').text(rowLabel);
305
	}
306
}
307

    
308
function checkLastRow() {
309
	if (($('.repeatable').length <= 1) && (! $('#deleterow0').hasClass("nowarn"))) {
310
		$('#deleterow0').hide();
311
	} else {
312
		$('[id^=deleterow]').show();
313
	}
314
}
315

    
316
function bump_input_id(newGroup) {
317
	$(newGroup).find('input').each(function() {
318
		$(this).prop("id", bumpStringInt(this.id));
319
		$(this).prop("name", bumpStringInt(this.name));
320
		if (!$(this).is('[id^=delete]'))
321
			$(this).val('');
322
	});
323

    
324
	// Increment the suffix number for the deleterow button element in the new group
325
	$(newGroup).find('[id^=deleterow]').each(function() {
326
		$(this).prop("id", bumpStringInt(this.id));
327
		$(this).prop("name", bumpStringInt(this.name));
328
	});
329

    
330
	// Do the same for selectors
331
	$(newGroup).find('select').each(function() {
332
		$(this).prop("id", bumpStringInt(this.id));
333
		$(this).prop("name", bumpStringInt(this.name));
334
		// If this selector lists mask bits, we need it to be reset to all 128 options
335
		// and no items selected, so that automatic v4/v6 selection still works
336
		if ($(this).is('[id^=address_subnet]')) {
337
			$(this).empty();
338
			for (idx=128; idx>0; idx--) {
339
				$(this).append($('<option>', {
340
					value: idx,
341
					text: idx
342
				}));
343
			}
344
		}
345
	});
346
}
347
function add_row() {
348
	// Find the last repeatable group
349
	var lastRepeatableGroup = $('.repeatable:last');
350

    
351
	// If the number of repeats exceeds the maximum, do not add another clone
352
	if ($('.repeatable').length >= lastRepeatableGroup.attr('max_repeats')) {
353
		// Alert user if alert message is specified
354
		if (typeof lastRepeatableGroup.attr('max_repeats_alert') !== 'undefined') {
355
			alert(lastRepeatableGroup.attr('max_repeats_alert'));
356
		}
357
		return;
358
	}
359

    
360
	// Clone it
361
	var newGroup = lastRepeatableGroup.clone();
362

    
363
	// Increment the suffix number for each input element in the new group
364
	bump_input_id(newGroup);
365

    
366
	// And for "for" tags
367
//	$(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));
368

    
369
	$(newGroup).find('label:first').text(""); // Clear the label. We only want it on the very first row
370

    
371
	// Insert the updated/cloned row
372
	$(lastRepeatableGroup).after(newGroup);
373

    
374
	// Delete any help text from the group we have cloned
375
	$(lastRepeatableGroup).find('.help-block').each(function() {
376
		if ((typeof retainhelp) == "undefined")
377
			$(this).remove();
378
	});
379

    
380
	setMasks();
381

    
382
	checkLastRow();
383

    
384
	// Autocomplete
385
	if ( typeof addressarray !== 'undefined') {
386
		$('[id^=address]').each(function() {
387
			if (this.id.substring(0, 8) != "address_") {
388
				$(this).autocomplete({
389
					source: addressarray
390
				});
391
			}
392
		});
393
	}
394

    
395
	// Now that we are no longer cloning the event handlers, we need to remove and re-add after a new row
396
	// has been added to the table
397
	$('[id^=delete]').unbind();
398
	$('[id^=delete]').click(function(event) {
399
		if ($('.repeatable').length > 1) {
400
			if ((typeof retainhelp) == "undefined")
401
				moveHelpText($(this).attr("id"));
402

    
403
			delete_row($(this).attr("id"));
404
		} else if ($(this).hasClass("nowarn")) {
405
			clearRow0();
406
		} else {
407
			alert('The last row may not be deleted.');
408
		}
409
	});
410
}
411

    
412
// These are action buttons, not submit buttons
413
$('[id^=addrow]').prop('type','button');
414
$('[id^=delete]').prop('type','button');
415

    
416
// on click . .
417
$('[id^=addrow]').click(function() {
418
	add_row();
419
});
420

    
421
$('[id^=delete]').click(function(event) {
422
	if ($('.repeatable').length > 1) {
423
		if ((typeof retainhelp) == "undefined")
424
			moveHelpText($(this).attr("id"));
425

    
426
		delete_row($(this).attr("id"));
427
		} else if ($(this).hasClass("nowarn")) {
428
			clearRow0();
429
		} else {
430
			alert('The last row may not be deleted.');
431
		}
432
});
433

    
434
function clearRow0() {
435
	$('#deleterow0').parent('div').parent().find('input[type=text]').val('');
436
	$('#deleterow0').parent('div').parent().find('input[type=checkbox]:checked').removeAttr('checked');
437
}
438

    
439
// "More information" handlers --------------------------------------------------------------------
440

    
441
// If there is an infoblock, automatically add an info icon that toggles its display
442

    
443
var sfx = 0;
444

    
445
$('.infoblock').each(function() {
446
	// If the block has the class "blockopen" it is initially open
447
	if (! $(this).hasClass("blockopen")) {
448
		$(this).hide();
449
	} else {
450
		$(this).removeClass("blockopen");
451
	}
452

    
453
	// Add the "i" icon before the infoblock, incrementing the icon id for each block (in case there are multiple infoblocks on a page)
454
	$(this).before('<i class="fa fa-info-circle icon-pointer" style="color: #337AB7; font-size:20px; margin-left: 10px; margin-bottom: 10px;" id="showinfo' + sfx.toString() + '" title="More information"></i>');
455
	$(this).removeClass("infoblock");
456
	$(this).addClass("infoblock" + sfx.toString());
457
	sfx++;
458
});
459

    
460
// Show the help on clicking the info icon
461
$('[id^="showinfo"]').click(function() {
462
	var id = $(this).attr("id");
463
	$('.' + "infoblock" + id.substr(8)).toggle();
464
	document.getSelection().removeAllRanges();		// Ensure the text is un-selected (Chrome browser quirk)
465
});
466
// ------------------------------------------------------------------------------------------------
467

    
468
// Put a dummy row into any empty table to keep IE happy
469
// Commented out due to https://redmine.pfsense.org/issues/7504
470
//$('tbody').each(function(){
471
//	$(this).html($.trim($(this).html()))
472
//});
473

    
474
$('tbody:empty').html("<tr><td></td></tr>");
475

    
476
// Hide configuration button for panels without configuration
477
$('.container .panel-heading a.config').each(function (idx, el){
478
	var config = $(el).parents('.panel').children('.panel-footer');
479
	if (config.length == 1)
480
		$(el).removeClass('hidden');
481
});
482

    
483
// Initial state & toggle icons of collapsed panel
484
$('.container .panel-heading a[data-toggle="collapse"]').each(function (idx, el){
485
	var body = $(el).parents('.panel').children('.panel-body')
486
	var isOpen = body.hasClass('in');
487

    
488
	$(el).children('i').toggleClass('fa-plus-circle', !isOpen);
489
	$(el).children('i').toggleClass('fa-minus-circle', isOpen);
490

    
491
	body.on('shown.bs.collapse', function(){
492
		$(el).children('i').toggleClass('fa-minus-circle', true);
493
		$(el).children('i').toggleClass('fa-plus-circle', false);
494
	});
495

    
496
	body.on('hidden.bs.collapse', function(){
497
		$(el).children('i').toggleClass('fa-minus-circle', false);
498
		$(el).children('i').toggleClass('fa-plus-circle', true);
499
	});
500
});
501

    
502
// Separator bar stuff ------------------------------------------------------------------------
503

    
504
// Globals
505
gColor = 'bg-info';
506
newSeparator = false;
507
saving = false;
508
dirty = false;
509

    
510
$("#addsep").prop('type' ,'button');
511

    
512
$("#addsep").click(function() {
513
	if (newSeparator) {
514
		return(false);
515
	}
516

    
517
	gColor = 'bg-info';
518
	// Insert a temporary bar in which the user can enter some optional text
519
	sepcols = $( "#ruletable tr th" ).length - 2;
520

    
521
	$('#ruletable > tbody:last').append('<tr>' +
522
		'<td class="' + gColor + '" colspan="' + sepcols + '"><input id="newsep" placeholder="' + svbtnplaceholder + '" class="col-md-12" type="text" /></td>' +
523
		'<td class="' + gColor + '" colspan="2"><button class="btn btn-primary btn-sm" id="btnnewsep"><i class="fa fa-save icon-embed-btn"></i>' + svtxt + '</button>' +
524
		'<button class="btn btn-info btn-sm" id="btncncsep"><i class="fa fa-undo icon-embed-btn"></i>' + cncltxt + '</button>' +
525
		'&nbsp;&nbsp;&nbsp;&nbsp;' +
526
		'&nbsp;&nbsp;<a id="sepclrblue" value="bg-info"><i class="fa fa-circle text-info icon-pointer"></i></a>' +
527
		'&nbsp;&nbsp;<a id="sepclrred" value="bg-danger"><i class="fa fa-circle text-danger icon-pointer"></i></a>' +
528
		'&nbsp;&nbsp;<a id="sepclrgreen" value="bg-success"><i class="fa fa-circle text-success icon-pointer"></i></a>' +
529
		'&nbsp;&nbsp;<a id="sepclrorange" value="bg-warning"><i class="fa fa-circle text-warning icon-pointer"></i></button>' +
530
		'</td></tr>');
531

    
532
	$('#newsep').focus();
533
	newSeparator = true;
534

    
535
	$("#btnnewsep").prop('type' ,'button');
536

    
537
	// Watch escape and enter keys
538
	$('#newsep').keyup(function(e) {
539
		if (e.which == 27) {
540
			$('#btncncsep').trigger('click');
541
		}
542
	});
543

    
544
	$('#newsep').keypress(function(e) {
545
		if (e.which == 13) {
546
			$('#btnnewsep').trigger('click');
547
		}
548
	});
549

    
550
	handle_colors();
551

    
552
	// Replace the temporary separator bar with the final version containing the
553
	// user's text and a delete icon
554
	$("#btnnewsep").click(function() {
555
		var septext = escapeHtml($('#newsep').val());
556
		sepcols = $( "#ruletable tr th" ).length - 1;
557

    
558
		$(this).parents('tr').replaceWith('<tr class="ui-sortable-handle separator">' +
559
			'<td class="' + gColor + '" colspan="' + sepcols + '">' + '<span class="' + gColor + '">' + septext + '</span></td>' +
560
			'<td class="' + gColor + '"><a href="#"><i class="fa fa-trash sepdel"></i></a>' +
561
			'</td></tr>');
562

    
563
		$('#order-store').removeAttr('disabled');
564
		newSeparator = false;
565
		dirty = true;
566
	});
567

    
568
	// Cancel button
569
	$('#btncncsep').click(function(e) {
570
		e.preventDefault();
571
		$(this).parents('tr').remove();
572
		newSeparator = false;
573
	});
574
});
575

    
576
// Delete a separator row
577
$(function(){
578
	$('table').on('click','tr a .sepdel',function(e){
579
		e.preventDefault();
580
		$(this).parents('tr').remove();
581
		$('#order-store').removeAttr('disabled');
582
		dirty = true;
583
	});
584
});
585

    
586
// Compose an input array containing the row #, color and text for each separator
587
function save_separators() {
588
	var row = 0;
589
	var sepinput;
590
	var sepnum = 0;
591

    
592
	$('#ruletable > tbody > tr').each(function() {
593
		if ($(this).hasClass('separator')) {
594
			seprow = $(this).next('tr').attr("id");
595
			if (seprow == undefined) {
596
				seprow = "fr" + row;
597
			}
598

    
599
			sepinput = '<input type="hidden" name="separator[' + sepnum + '][row]" value="' + seprow + '"></input>';
600
			$('form').append(sepinput);
601
			sepinput = '<input type="hidden" name="separator[' + sepnum + '][text]" value="' + escapeHtml($(this).find('td').text()) + '"></input>';
602
			$('form').append(sepinput);
603
			sepinput = '<input type="hidden" name="separator[' + sepnum + '][color]" value="' + $(this).find('td').prop('class') + '"></input>';
604
			$('form').append(sepinput);
605
			sepinput = '<input type="hidden" name="separator[' + sepnum + '][if]" value="' + iface + '"></input>';
606
			$('form').append(sepinput);
607
			sepnum++;
608
		} else {
609
			if ($(this).parent('tbody').hasClass('user-entries')) {
610
				row++;
611
			}
612
		}
613
	});
614
}
615

    
616
function reindex_rules(section) {
617
	var row = 0;
618

    
619
	section.find('tr').each(function() {
620
		if (this.id) {
621
			$(this).attr("id", "fr" + row);
622
			$(this).attr("onclick", "fr_toggle(" + row + ")")
623
			$(this).find('input:checkbox:first').each(function() {
624
				$(this).attr("id", "frc" + row);
625
				$(this).attr("onclick", "fr_toggle(" + row + ")");
626
			});
627

    
628
			row++;
629
		}
630
	});
631
}
632

    
633
function handle_colors() {
634
	$('[id^=sepclr]').prop("type", "button");
635

    
636
	$('[id^=sepclr]').click(function () {
637
		var color =	 $(this).attr('value');
638
		// Clear all the color classes
639
		$(this).parent('td').prop('class', '');
640
		$(this).parent('td').prev('td').prop('class', '');
641
		// Install our new color class
642
		$(this).parent('td').addClass(color);
643
		$(this).parent('td').prev('td').addClass(color);
644
		// Set the global color
645
		gColor = color;
646
	});
647
}
648

    
649
//JS equivalent to PHP htmlspecialchars()
650
function escapeHtml(text) {
651
	var map = {
652
		'&': '&amp;',
653
		'<': '&lt;',
654
		'>': '&gt;',
655
		'"': '&quot;',
656
		"'": '&#039;'
657
	};
658

    
659
	return text.replace(/[&<>"']/g, function(m) { return map[m]; });
660
}
661
// --------------------------------------------------------------------------------------------
662

    
663
// Select every option in the specified multiselect
664
function AllServers(id, selectAll) {
665
   for (i = 0; i < id.length; i++)	   {
666
	   id.eq(i).prop('selected', selectAll);
667
   }
668
}
669

    
670
// Move all selected options from one multiselect to another
671
function moveOptions(From, To)	{
672
	var len = From.length;
673
	var option;
674

    
675
	if (len > 0) {
676
		for (i=0; i<len; i++) {
677
			if (From.eq(i).is(':selected')) {
678
				option = From.eq(i).val();
679
				value  = From.eq(i).text();
680
				To.append(new Option(value, option));
681
				From.eq(i).remove();
682
			}
683
		}
684
	}
685
}
686

    
687
// ------------- Service start/stop/restart functions.
688
// If a start/stop/restart button is clicked, parse the button name and make a POST via AJAX
689
$('[id*=restartservice-], [id*=stopservice-], [id*=startservice-]').click(function(event) {
690
	var args = this.id.split('-');
691
	var action, name, mode_zone, id;
692

    
693
	if (args[0] == "openvpn") {
694
		action = args[1];
695
		name = args[0];
696
		mode_zone = args[2];
697
		id = args[3];
698
	} else if (args[0] == "captiveportal") {
699
		action = args[1];
700
		name = args[0];
701
		mode_zone = args[2];
702
		id = args[3];
703
	} else {
704
		action = args[0];
705
		args.shift();
706
		name = args.join('-');
707
	}
708

    
709
	$(this).children('i').removeClass().addClass('fa fa-cog fa-spin text-success');
710
	this.blur();
711

    
712
	ajaxRequest = $.ajax(
713
		{
714
			url: "/status_services.php",
715
			type: "post",
716
			data: {
717
				ajax: 		"ajax",
718
				mode: 		action,
719
				service: 	name,
720
				vpnmode: 	mode_zone,
721
				zone: 		mode_zone,
722
				id: 		id
723
			}
724
		}
725
	);
726

    
727
	// Once the AJAX call has returned, refresh the page to show the new service
728
	ajaxRequest.done(function (response, textStatus, jqXHR) {
729
		location.reload(true);
730
	});
731
});
732

    
733
// The scripts that follow are an EXPERIMENT in using jQuery/Javascript to automatically convert
734
// GET calls to POST calls
735
// Any anchor with the attribute "usepost" usses these functions.
736

    
737
// Any time an anchor is clicked and the "usepost" attibute is present, convert the href attribute
738
// to POST format, make a POST form and submit it
739

    
740
interceptGET();
741

    
742
function interceptGET() {
743
	$('a').click(function(e) {
744
		// Does the clicked anchor have the "usepost" attribute?
745
		var attr = $(this).attr('usepost');
746

    
747
		if (typeof attr !== typeof undefined && attr !== false) {
748
			// Automatically apply a confirmation dialog to "Delete" icons
749
			if (!($(this).hasClass('no-confirm')) && !($(this).hasClass('icon-embed-btn')) &&
750
			   ($(this).hasClass('fa-trash'))) {
751
				var msg = $.trim(this.textContent).toLowerCase();
752

    
753
				if (!msg)
754
					var msg = $.trim(this.value).toLowerCase();
755

    
756
				var q = 'Are you sure you wish to '+ msg +'?';
757

    
758
				if ($(this).attr('title') != undefined)
759
					q = 'Are you sure you wish to '+ $(this).attr('title').toLowerCase() + '?';
760

    
761
				if (!confirm(q)) {
762
					return false;
763
				}
764
			}
765

    
766
			var target = $(this).attr("href").split("?");
767

    
768
			postSubmit(get2post(target[1]),target[0]);
769
			return false;
770
		}
771
	});
772
}
773

    
774
// Convert a GET argument list such as ?name=fred&action=delete into an object of POST
775
// parameters such as {name : fred, action : delete}
776
function get2post(getargs) {
777
	var argdict = {};
778
	var argarray = getargs.split('&');
779

    
780
	argarray.forEach(function(arg) {
781
		arg = arg.split('=');
782
		argdict[arg[0]] = arg[1];
783
	});
784

    
785
	return argdict;
786
}
787

    
788
// Create a form, add, the POST data and submit it
789
function postSubmit(data, target) {
790
	var $form = $('<form>');
791

    
792
	for (var name in data) {
793
		$form.append(
794
			$("<input>")
795
				.attr("type", "hidden")
796
				.attr("name", name)
797
				.val(data[name])
798
		);
799
    }
800

    
801
	$form
802
		.attr("method", "POST")
803
		.attr("action", target)
804
		// The CSRF magic is required because we will be viewing the results of the POST
805
		.append(
806
			$("<input>")
807
				.attr("type", "hidden")
808
				.attr("name", "__csrf_magic")
809
				.val(csrfMagicToken)
810
		)
811
		.appendTo('body')
812
		.submit();
813
}
(2-2/4)