Project

General

Profile

Download (21.9 KB) Statistics
| Branch: | Tag: | Revision:
1
/*
2
 * pfSenseHelpers.js
3
 *
4
 * part of pfSense (https://www.pfsense.org)
5
 * Copyright (c) 2004-2016 Rubicon Communications, LLC (Netgate)
6
 * All rights reserved.
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions are met:
10
 *
11
 * 1. Redistributions of source code must retain the above copyright notice,
12
 *    this list of conditions and the following disclaimer.
13
 *
14
 * 2. Redistributions in binary form must reproduce the above copyright
15
 *    notice, this list of conditions and the following disclaimer in
16
 *    the documentation and/or other materials provided with the
17
 *    distribution.
18
 *
19
 * 3. All advertising materials mentioning features or use of this software
20
 *    must display the following acknowledgment:
21
 *    "This product includes software developed by the pfSense Project
22
 *    for use in the pfSense® software distribution. (http://www.pfsense.org/).
23
 *
24
 * 4. The names "pfSense" and "pfSense Project" must not be used to
25
 *    endorse or promote products derived from this software without
26
 *    prior written permission. For written permission, please contact
27
 *    coreteam@pfsense.org.
28
 *
29
 * 5. Products derived from this software may not be called "pfSense"
30
 *    nor may "pfSense" appear in their names without prior written
31
 *    permission of the Electric Sheep Fencing, LLC.
32
 *
33
 * 6. Redistributions of any form whatsoever must retain the following
34
 *    acknowledgment:
35
 *
36
 * "This product includes software developed by the pfSense Project
37
 * for use in the pfSense software distribution (http://www.pfsense.org/).
38
 *
39
 * THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
40
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
41
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
42
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
43
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
44
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
45
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
46
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
47
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
48
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
49
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
50
 * OF THE POSSIBILITY OF SUCH DAMAGE.
51
 */
52

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

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

    
63
// Hides the <div> in which the specified group input element lives so that the input,
64
// its label and help text are hidden
65
function hideGroupInput(id, hide) {
66
	if (hide)
67
		$('#' + id).parent('div').addClass('hidden');
68
	else
69
		$('#' + id).parent('div').removeClass('hidden');
70
}
71

    
72
// Hides the <div> in which the specified checkbox lives so that the checkbox, its label and help text are hidden
73
function hideCheckbox(id, hide) {
74
	if (hide)
75
		$('#' + id).parent().parent().parent('div').addClass('hidden');
76
	else
77
		$('#' + id).parent().parent().parent('div').removeClass('hidden');
78
}
79

    
80
// Disables the specified input element
81
function disableInput(id, disable) {
82
	$('#' + id).prop("disabled", disable);
83
}
84

    
85
// Hides all elements of the specified class. This will usually be a section
86
function hideClass(s_class, hide) {
87
	if (hide)
88
		$('.' + s_class).hide();
89
	else
90
		$('.' + s_class).show();
91
}
92

    
93
function hideSelect(id, hide) {
94
	if (hide)
95
		$('#' + id).parent('div').parent('div').addClass('hidden');
96
	else
97
		$('#' + id).parent('div').parent('div').removeClass('hidden');
98
}
99

    
100
function hideMultiCheckbox(id, hide) {
101
	if (hide)
102
		$("[name=" + id + "]").parent().addClass('hidden');
103
	else
104
		$("[name=" + id + "]").parent().removeClass('hidden');
105
}
106

    
107
// 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
108
function hideIpAddress(id, hide) {
109
	if (hide)
110
		$('#' + id).parent().parent().parent('div').addClass('hidden');
111
	else
112
		$('#' + id).parent().parent().parent('div').removeClass('hidden');
113
}
114

    
115
// Hides all elements of the specified class belonging to a multiselect.
116
function hideMultiClass(s_class, hide) {
117
	if (hide)
118
		$('.' + s_class).parent().parent().hide();
119
	else
120
		$('.' + s_class).parent().parent().show();
121
}
122

    
123
// Hides div whose label contains the specified text. (Good for StaticText)
124
function hideLabel(text, hide) {
125

    
126
	var element = $('label:contains(' + text + ')');
127

    
128
	if (hide)
129
		element.parent('div').addClass('hidden');
130
	else
131
		element.parent('div').removeClass('hidden');
132
}
133

    
134
// Hides the '/' and the subnet mask of an Ip_Address/subnet_mask group
135
function hideMask(name, hide) {
136
	if (hide) {
137
		$('[id^=' + name + ']').hide();
138
		$('[id^=' + name + ']').prev('span').hide();
139
		$('[id^=' + name + ']').parent('div').removeClass('input-group');
140
	} else {
141
		$('[id^=' + name + ']').show();
142
		$('[id^=' + name + ']').prev('span').show();
143
		$('[id^=' + name + ']').parent('div').addClass('input-group');
144
	}
145
}
146

    
147
// Toggle table row checkboxes and background colors on the pages that use sortable tables:
148
//	/usr/local/www/firewall_nat.php
149
//	/usr/local/www/firewall_nat_1to1.php
150
//	/usr/local/www/firewall_nat_out.php
151
//	/usr/local/www/firewall_rules.php
152
//	/usr/local/www/vpn_ipsec.php
153
// Striping of the tables is handled here, NOT with the Bootstrap table-striped class because it would
154
// get confused when rows are sorted or deleted.
155

    
156
function fr_toggle(id, prefix) {
157
	if (!prefix)
158
		prefix = 'fr';
159

    
160
	var checkbox = document.getElementById(prefix + 'c' + id);
161
	checkbox.checked = !checkbox.checked;
162
	fr_bgcolor(id, prefix);
163
}
164

    
165
// Change background color of selected row based on state of checkbox
166
function fr_bgcolor(id, prefix) {
167
	if (!prefix)
168
		prefix = 'fr';
169

    
170
	var row = $('#' + prefix + id);
171

    
172
	if ($('#' + prefix + 'c' + id).prop('checked') ) {
173
		row.addClass('active');
174
	} else {
175
		row.removeClass('active');
176
	}
177
}
178

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

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

    
186
function setMasks() {
187
	// Find all ipaddress masks and make dynamic based on address family of input
188
	$('span.pfIpMask + select').each(function (idx, select){
189
		var input = $(select).prevAll('input[type=text]');
190

    
191
		input.on('change', function(e){
192
			var isV6 = (input.val().indexOf(':') != -1), min = 0, max = 128;
193
			if (!isV6)
194
				max = 32;
195

    
196
			if (input.val() == "")
197
				return;
198

    
199
			while (select.options.length > max)
200
				select.remove(0);
201

    
202
			if (select.options.length < max) {
203
				for (var i=select.options.length; i<=max; i++)
204
					select.options.add(new Option(i, i), 0);
205
			}
206
		});
207

    
208
		// Fire immediately
209
		input.change();
210
	});
211
}
212

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

    
217
	$('#' + id).parent('div').parent('div').find('input, select, checkbox, button').each(function() {	 // For each <span></span>
218
		var fromId = this.id;
219
		var toId = decrStringInt(fromId);
220
		var helpSpan;
221

    
222

    
223
		if (!$(this).hasClass('pfIpMask') && !$(this).hasClass('btn')) {
224
			if ($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group')) {
225
				helpSpan = $('#' + fromId).parent('div').parent('div').find('span:last').clone();
226
			} else {
227
				helpSpan = $('#' + fromId).parent('div').find('span:last').clone();
228
			}
229

    
230
			if ($(helpSpan).hasClass('help-block')) {
231
				if ($('#' + decrStringInt(fromId)).parent('div').hasClass('input-group')) {
232
					$('#' + decrStringInt(fromId)).parent('div').after(helpSpan);
233
				} else {
234
					$('#' + decrStringInt(fromId)).after(helpSpan);
235
				}
236
			}
237
		}
238
	});
239
}
240

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

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

    
248
  return newStr || str;
249
}
250

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

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

    
258
  return newStr || str;
259
}
260

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

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

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

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

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

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

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

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

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

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

    
299
	renumber();
300
	checkLastRow();
301

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

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

    
315
function add_row() {
316
	// Find the last repeatable group
317
	var lastRepeatableGroup = $('.repeatable:last');
318

    
319
	// If the number of repeats exceeds the maximum, do not add another clone
320
	if ($('.repeatable').length >= lastRepeatableGroup.attr('max_repeats')) {
321
		// Alert user if alert message is specified
322
		if (typeof lastRepeatableGroup.attr('max_repeats_alert') !== 'undefined') {
323
			alert(lastRepeatableGroup.attr('max_repeats_alert'));
324
		}
325
		return;
326
	}
327

    
328
	// Clone it
329
	var newGroup = lastRepeatableGroup.clone();
330

    
331
	// Increment the suffix number for each input element in the new group
332
	$(newGroup).find('input').each(function() {
333
		$(this).prop("id", bumpStringInt(this.id));
334
		$(this).prop("name", bumpStringInt(this.name));
335
		if (!$(this).is('[id^=delete]'))
336
			$(this).val('');
337
	});
338

    
339
	// Increment the suffix number for the deleterow button element in the new group
340
	$(newGroup).find('[id^=deleterow]').each(function() {
341
		$(this).prop("id", bumpStringInt(this.id));
342
		$(this).prop("name", bumpStringInt(this.name));
343
	});
344

    
345
	// Do the same for selectors
346
	$(newGroup).find('select').each(function() {
347
		$(this).prop("id", bumpStringInt(this.id));
348
		$(this).prop("name", bumpStringInt(this.name));
349
		// If this selector lists mask bits, we need it to be reset to all 128 options
350
		// and no items selected, so that automatic v4/v6 selection still works
351
		if ($(this).is('[id^=address_subnet]')) {
352
			$(this).empty();
353
			for (idx=128; idx>0; idx--) {
354
				$(this).append($('<option>', {
355
					value: idx,
356
					text: idx
357
				}));
358
			}
359
		}
360
	});
361

    
362
	// And for "for" tags
363
//	$(newGroup).find('label').attr('for', bumpStringInt($(newGroup).find('label').attr('for')));
364

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

    
367
	// Insert the updated/cloned row
368
	$(lastRepeatableGroup).after(newGroup);
369

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

    
376
	setMasks();
377

    
378
	checkLastRow();
379

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

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

    
399
			delete_row(event.target.id);
400
		} else {
401
			alert('The last row may not be deleted.');
402
		}
403
	});
404

    
405
}
406

    
407
// These are action buttons, not submit buttons
408
$('[id^=addrow]').prop('type','button');
409
$('[id^=delete]').prop('type','button');
410

    
411
// on click . .
412
$('[id^=addrow]').click(function() {
413
	add_row();
414
});
415

    
416
$('[id^=delete]').click(function(event) {
417
	if ($('.repeatable').length > 1) {
418
		if ((typeof retainhelp) == "undefined")
419
			moveHelpText($(this).attr("id"));
420

    
421
		delete_row($(this).attr("id"));
422
	} else {
423
		alert('The last row may not be deleted.');
424
	}
425
});
426

    
427
// "More information" handlers --------------------------------------------------------------------
428

    
429
// If there is an infoblock, automatically add an info icon that toggles its display
430

    
431
var sfx = 0;
432

    
433
$('.infoblock').each(function() {
434
	// If the block has the class "blockopen" it is initially open
435
	if (! $(this).hasClass("blockopen")) {
436
		$(this).hide();
437
	} else {
438
		$(this).removeClass("blockopen");
439
	}
440

    
441
	// Add the "i" icon before the infoblock, incrementing the icon id for each block (in case there are multiple infoblocks on a page)
442
	$(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>');
443
	$(this).removeClass("infoblock");
444
	$(this).addClass("infoblock" + sfx.toString());
445
	sfx++;
446
});
447

    
448
// Show the help on clicking the info icon
449
$('[id^="showinfo"]').click(function() {
450
	var id = $(this).attr("id");
451

    
452
	$('.' + "infoblock" + id.substr(8)).toggle();
453
	document.getSelection().removeAllRanges();		// Ensure the text is un-selected (Chrome browser quirk)
454
});
455
// ------------------------------------------------------------------------------------------------
456

    
457
// Put a dummy row into any empty table to keep IE happy
458
$('tbody').each(function(){
459
	$(this).html($.trim($(this).html()))
460
});
461

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

    
464
// Hide configuration button for panels without configuration
465
$('.container .panel-heading a.config').each(function (idx, el){
466
	var config = $(el).parents('.panel').children('.panel-footer');
467
	if (config.length == 1)
468
		$(el).removeClass('hidden');
469
});
470

    
471
// Initial state & toggle icons of collapsed panel
472
$('.container .panel-heading a[data-toggle="collapse"]').each(function (idx, el){
473
	var body = $(el).parents('.panel').children('.panel-body')
474
	var isOpen = body.hasClass('in');
475

    
476
	$(el).children('i').toggleClass('fa-plus-circle', !isOpen);
477
	$(el).children('i').toggleClass('fa-minus-circle', isOpen);
478

    
479
	body.on('shown.bs.collapse', function(){
480
		$(el).children('i').toggleClass('fa-minus-circle', true);
481
		$(el).children('i').toggleClass('fa-plus-circle', false);
482
	});
483

    
484
	body.on('hidden.bs.collapse', function(){
485
		$(el).children('i').toggleClass('fa-minus-circle', false);
486
		$(el).children('i').toggleClass('fa-plus-circle', true);
487
	});
488
});
489

    
490
// Separator bar stuff ------------------------------------------------------------------------
491

    
492
// Globals
493
gColor = 'bg-info';
494
newSeparator = false;
495
saving = false;
496
dirty = false;
497

    
498
$("#addsep").prop('type' ,'button');
499

    
500
$("#addsep").click(function() {
501
	if (newSeparator) {
502
		return(false);
503
	}
504

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

    
509
	$('#ruletable > tbody:last').append('<tr>' +
510
		'<td class="' + gColor + '" colspan="' + sepcols + '"><input id="newsep" placeholder="' + svbtnplaceholder + '" class="col-md-12" type="text" /></td>' +
511
		'<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>' +
512
		'<button class="btn btn-info btn-sm" id="btncncsep"><i class="fa fa-undo icon-embed-btn"></i>' + cncltxt + '</button>' +
513
		'&nbsp;&nbsp;&nbsp;&nbsp;' +
514
		'&nbsp;&nbsp;<a id="sepclrblue" value="bg-info"><i class="fa fa-circle text-info icon-pointer"></i></a>' +
515
		'&nbsp;&nbsp;<a id="sepclrred" value="bg-danger"><i class="fa fa-circle text-danger icon-pointer"></i></a>' +
516
		'&nbsp;&nbsp;<a id="sepclrgreen" value="bg-success"><i class="fa fa-circle text-success icon-pointer"></i></a>' +
517
		'&nbsp;&nbsp;<a id="sepclrorange" value="bg-warning"><i class="fa fa-circle text-warning icon-pointer"></i></button>' +
518
		'</td></tr>');
519

    
520
	$('#newsep').focus();
521
	newSeparator = true;
522

    
523
	$("#btnnewsep").prop('type' ,'button');
524

    
525
	// Watch escape and enter keys
526
	$('#newsep').keyup(function(e) {
527
		if (e.which == 27) {
528
			$('#btncncsep').trigger('click');
529
		}
530
	});
531

    
532
	$('#newsep').keypress(function(e) {
533
		if (e.which == 13) {
534
			$('#btnnewsep').trigger('click');
535
		}
536
	});
537

    
538
	handle_colors();
539

    
540
	// Replace the temporary separator bar with the final version containing the
541
	// user's text and a delete icon
542
	$("#btnnewsep").click(function() {
543
		var septext = escapeHtml($('#newsep').val());
544
		sepcols = $( "#ruletable tr th" ).length - 1;
545

    
546
		$(this).parents('tr').replaceWith('<tr class="ui-sortable-handle separator">' +
547
			'<td class="' + gColor + '" colspan="' + sepcols + '">' + '<span class="' + gColor + '">' + septext + '</span></td>' +
548
			'<td class="' + gColor + '"><a href="#"><i class="fa fa-trash sepdel"></i></a>' +
549
			'</td></tr>');
550

    
551
		$('#order-store').removeAttr('disabled');
552
		newSeparator = false;
553
		dirty = true;
554
	});
555

    
556
	// Cancel button
557
	$('#btncncsep').click(function(e) {
558
		e.preventDefault();
559
		$(this).parents('tr').remove();
560
		newSeparator = false;
561
	});
562
});
563

    
564
// Delete a separator row
565
$(function(){
566
	$('table').on('click','tr a .sepdel',function(e){
567
		e.preventDefault();
568
		$(this).parents('tr').remove();
569
		$('#order-store').removeAttr('disabled');
570
		dirty = true;
571
	});
572
});
573

    
574
// Compose an input array containing the row #, color and text for each separator
575
function save_separators() {
576
	var row = 0;
577
	var sepinput;
578
	var sepnum = 0;
579

    
580
	$('#ruletable > tbody > tr').each(function() {
581
		if ($(this).hasClass('separator')) {
582
			seprow = $(this).next('tr').attr("id");
583
			if (seprow == undefined) {
584
				seprow = "fr" + row;
585
			}
586

    
587
			sepinput = '<input type="hidden" name="separator[' + sepnum + '][row]" value="' + seprow + '"></input>';
588
			$('form').append(sepinput);
589
			sepinput = '<input type="hidden" name="separator[' + sepnum + '][text]" value="' + escapeHtml($(this).find('td').text()) + '"></input>';
590
			$('form').append(sepinput);
591
			sepinput = '<input type="hidden" name="separator[' + sepnum + '][color]" value="' + $(this).find('td').prop('class') + '"></input>';
592
			$('form').append(sepinput);
593
			sepinput = '<input type="hidden" name="separator[' + sepnum + '][if]" value="' + iface + '"></input>';
594
			$('form').append(sepinput);
595
			sepnum++;
596
		} else {
597
			if ($(this).parent('tbody').hasClass('user-entries')) {
598
				row++;
599
			}
600
		}
601
	});
602
}
603

    
604
function reindex_rules(section) {
605
	var row = 0;
606

    
607
	section.find('tr').each(function() {
608
		if (this.id) {
609
			$(this).attr("id", "fr" + row);
610
			$(this).attr("onclick", "fr_toggle(" + row + ")")
611
			$(this).find('input:checkbox:first').each(function() {
612
				$(this).attr("id", "frc" + row);
613
				$(this).attr("onclick", "fr_toggle(" + row + ")");
614
			});
615

    
616
			row++;
617
		}
618
	});
619
}
620

    
621
function handle_colors() {
622
	$('[id^=sepclr]').prop("type", "button");
623

    
624
	$('[id^=sepclr]').click(function () {
625
		var color =	 $(this).attr('value');
626
		// Clear all the color classes
627
		$(this).parent('td').prop('class', '');
628
		$(this).parent('td').prev('td').prop('class', '');
629
		// Install our new color class
630
		$(this).parent('td').addClass(color);
631
		$(this).parent('td').prev('td').addClass(color);
632
		// Set the global color
633
		gColor = color;
634
	});
635
}
636

    
637
//JS equivalent to PHP htmlspecialchars()
638
function escapeHtml(text) {
639
	var map = {
640
		'&': '&amp;',
641
		'<': '&lt;',
642
		'>': '&gt;',
643
		'"': '&quot;',
644
		"'": '&#039;'
645
	};
646

    
647
	return text.replace(/[&<>"']/g, function(m) { return map[m]; });
648
}
649
// --------------------------------------------------------------------------------------------
650

    
651
// Select every option in the specified multiselect
652
function AllServers(id, selectAll) {
653
   for (i = 0; i < id.length; i++)	   {
654
	   id.eq(i).prop('selected', selectAll);
655
   }
656
}
657

    
658
// Move all selected options from one multiselect to another
659
function moveOptions(From, To)	{
660
	var len = From.length;
661
	var option;
662

    
663
	if (len > 0) {
664
		for (i=0; i<len; i++) {
665
			if (From.eq(i).is(':selected')) {
666
				option = From.eq(i).val();
667
				value  = From.eq(i).text();
668
				To.append(new Option(value, option));
669
				From.eq(i).remove();
670
			}
671
		}
672
	}
673
}
674

    
675

    
676
// ------------- Service start/stop/restart functions.
677
// If a start/stop/restart button is clicked, parse the button name and make a POST via AJAX
678
$('[id*=restartservice-], [id*=stopservice-], [id*=startservice-]').click(function(event) {
679
	var args = this.id.split('-');
680
	var action, name, mode_zone, id;
681

    
682
	if (args[0] == "openvpn") {
683
		action = args[1];
684
		name = args[0];
685
		mode_zone = args[2];
686
		id = args[3];
687
	} else if (args[0] == "cpativeportal") {
688
		action = args[1];
689
		name = args[0];
690
		mode_zone = args[2];
691
		id = args[3];
692
	} else {
693
		action = args[0];
694
		name = args[1];
695
	}
696

    
697
	$(this).children('i').removeClass().addClass('fa fa-cog fa-spin text-success');
698
	this.blur();
699

    
700
	ajaxRequest = $.ajax(
701
		{
702
			url: "/status_services.php",
703
			type: "post",
704
			data: {
705
				ajax: 		"ajax",
706
				mode: 		action,
707
				service: 	name,
708
				vpnmode: 	mode_zone,
709
				zone: 		mode_zone,
710
				id: 		id
711
			}
712
		}
713
	);
714

    
715
	// Once the AJAX call has returned, refresh the page to show the new service
716
	ajaxRequest.done(function (response, textStatus, jqXHR) {
717
		location.reload(true);
718
	});
719
});
(2-2/4)