
/***
 * The Settings Panel is managed with AngularJS.
 * Following is the AngularJS Application and Controller.
 * Our $scope is initialized with a $http request that retrieves a JSON like that:
 * {
 * 	"boolean": [
 * 		"key",
 * 		...
 * 	],
 * 	"defaults": {
 * 		"key1": "default",
 * 		"key2": false,
 * 		"key3": [
 * 			4,
 * 			2,
 * 		],
 * 		...
 * 	},
 * 	"integer": [
 * 		"key",
 * 	],
 * 	"multi": [
 * 		"key",
 * 	],
 * 	"placeholders": {
 * 		"key": "placeholder",
 * 		...
 * 	},
 * 	"results": {
 * 		"boolean": [
 * 			{
 * 				"name": "key",
 * 				"value": true
 * 			},
 * 			...
 * 		],
 * 		"clients": [
 * 			{
 * 				"name": "clientname",
 * 				"value": "/etc/burp/clientconfdir/clientname"
 * 			},
 * 			...
 * 		],
 * 		"common": [
 * 			{
 * 				"name": "key",
 * 				"value": "val"
 * 			},
 * 			...
 * 		],
 * 		"integer": [
 * 			{
 * 				"name": "key",
 * 				"value": 42
 * 			},
 * 			...
 * 		],
 * 		"multi": [
 * 			{
 * 				"name": "key",
 * 				"value": [
 * 					"value1",
 * 					"value2",
 * 					...
 * 				]
 * 			},
 * 			...
 * 		],
 *    "includes": [
 *      "glob",
 *      "example*.conf",
 *      ...
 *    ],
 *    "includes_ext": [
 *      "glob",
 *      "example1.conf",
 *      "example_toto.conf",
 *      ...
 *    ]
 * 	},
 * 	"server_doc": {
 * 		"key": "documentations of the specified key from the manpage",
 * 		...
 * 	},
 * 	"string": [
 * 		"key",
 * 		...
 * 	],
 * 	"suggest": {
 * 		"key": [
 * 			"value1",
 * 			"value2",
 * 		],
 * 		[...]
 * 	}
 * }
 * The JSON is then split-ed out into several dict/arrays to build our form.
 */
{% import 'macros.html' as macros %}

var app = angular.module('MainApp', ['ngSanitize', 'frapontillo.bootstrap-switch', 'ui.select', 'mgcrea.ngStrap', 'angular-onbeforeunload', 'datatables']);

app.config(function(uiSelectConfig) {
	uiSelectConfig.theme = 'bootstrap';
});

app.controller('ConfigCtrl', ['$scope', '$http', '$scrollspy', 'DTOptionsBuilder', 'DTColumnDefBuilder', function($scope, $http, $scrollspy, DTOptionsBuilder, DTColumnDefBuilder) {
	$scope.bools = [];
	$scope.strings = [];
	$scope.clients = [];
	$scope.hierarchy = [];
	$scope.client = {};
	$scope.defaults = {};
	$scope.placeholders = {};
	$scope.all = {};
	$scope.avail = {};
	$scope.suggest = {};
	$scope.invalid = {};
	$scope.paths = {};
	$scope.revokeEnabled = false;
	$scope.inc_invalid = {};
	$scope.old = {};
	$scope.raw = {};
	$scope.spy = {};
	$scope.new = {
			'bools': undefined,
			'integers': undefined,
			'strings': undefined,
			'multis': undefined,
			'templates': undefined
		};
	$scope.add = {
			'bools': false,
			'integers': false,
			'strings': false,
			'multis': false,
			'templates': false
		};
	$scope.changed = false;
	$scope.checkbox_translation = {
			'yes':   "{{ _('yes') }}",
			'no':    "{{ _('no') }}",
			'reset': "{{ _('reset list') }}",
		};
	$scope.dtOptions = {
			{{ macros.translate_datatable() }}
			{{ macros.get_page_length() }}
		};
	$scope.dtColumnDefs = [
			DTColumnDefBuilder.newColumnDef(0),
			DTColumnDefBuilder.newColumnDef(1),
			DTColumnDefBuilder.newColumnDef(2).notSortable(),
		];
	$scope.loadConfig = function() {
		{% if client -%}
			{% if template -%}
		$http.get('{{ url_for("api.client_settings", client=client, conf=conf, template=True, server=server) }}', { headers: { 'X-From-UI': true } })
			{% else -%}
		$http.get('{{ url_for("api.client_settings", client=client, conf=conf, server=server) }}', { headers: { 'X-From-UI': true } })
			{% endif -%}
		{% else -%}
		$http.get('{{ url_for("api.server_settings", conf=conf, server=server) }}', { headers: { 'X-From-UI': true } })
		{% endif -%}
			.then(function(response) {
				data = response.data;
				$scope.bools = data.results.boolean;
				$scope.all.bools = data.boolean;
				$scope.strings = data.results.common;
				$scope.all.strings = data.string;
				$scope.integers = data.results.integer;
				$scope.all.integers = data.integer;
				$scope.multis = data.results.multi;
				$scope.all.multis = data.multi;
				$scope.server_doc = data.server_doc;
				$scope.suggest = data.suggest;
				$scope.placeholders = data.placeholders;
				$scope.defaults = data.defaults;
				$scope.includes = data.results.includes;
				$scope.includes_ori = angular.copy($scope.includes);
				$scope.includes_ext = data.results.includes_ext;
				$scope.templates = data.results.templates;
				$scope.hierarchy = data.results.hierarchy;
				$scope.refreshHierarchy();
				$scope.refreshScrollspy();
				$('#waiting-container').hide();
				$('#settings-panel').show();
			}, function(response) {
				notifAll(response.data);
				$('#waiting-container').hide();
			});
	};
	$http.get('{{ url_for("api.setting_options", server=server) }}', { headers: { 'X-From-UI': true } })
		.then(function(response) {
			$scope.revokeEnabled = response.data.is_revocation_enabled;
		});
	/* Our form is submitted asynchronously thanks to this callback */
	$scope.submit = function(e) {
		/* we disable the 'real' form submission */
		e.preventDefault();
		/* ugly hack to disable form submission when pressing the 'return' key
		 * on the select + replace switch by hidden fields so that the unchecked
		 * switch get submitted */
		sbm = true;
		_($scope.new).forEach(function(value, key) {
			/* Once we found a 'select' was active, we exit the loop as we just
			 * need one to disable form submission */
			if (value && value.selected) {
				sbm = false;
				return;
			}
		});
		/* The above forEach is a function, as we cannot exit two levels at once
		 * we need this check & return */
		if (!sbm) {
			return;
		}
		var form = $(e.target);
		if (form.attr('name') === 'setSettings') {
			/* We want to submit every displayed settings. By default unchecked
			 * checkboxes are not submitted so we use a hidden field to achieve that */
			angular.forEach($scope.bools, function(value, key) {
				form.find('#'+value.name).val(value.value);
				form.find('#'+value.name+'_view').attr('disabled', true);
			});
			angular.forEach($scope.multis, function(value, key) {
				angular.forEach(value.reset, function(val, i) {
					form.find('#'+value.name+'_reset_bui_CUSTOM-'+i).val(val);
					form.find('#'+value.name+'_reset_bui_CUSTOM_view-'+i).attr('disabled', true);
				});
			});
			$scope.invalid = {};
			/* UX tweak: disable the submit button + change text */
			submit = form.find('button[type="submit"]');
			sav = submit.text();
			submit.text('{{ _("Saving...") }}');
			submit.attr('disabled', true);
			/* submit the data */
			$.ajax({
				url: form.attr('action'),
				type: 'POST',
				data: form.serialize(),
				headers: { 'X-From-UI': true },
			})
			.fail(myFail)
			.done(function(data) {
				/* The server answered correctly but some errors may have occurred server
				 * side so we display them */
				if (data.notif) {
					$.each(data.notif, function(i, n) {
						notif(n[0], n[1]);
						$scope.invalid[n[2]] = true;
					});
				}
				$scope.setSettings.$setPristine();
				$scope.changed = false;
				$scope.getClientsList();
			}).always(function() {
				/* reset the submit button state */
				submit.text(sav);
				submit.attr('disabled', false);
			});
			/* re-enable the checkboxes */
			angular.forEach($scope.bools, function(value, key) {
				form.find('#'+value.name+'_view').attr('disabled', false);
			});
			angular.forEach($scope.multis, function(value, key) {
				angular.forEach(value.reset, function(val, i) {
					form.find('#'+value.name+'_reset_bui_CUSTOM_view-'+i).attr('disabled', false);
				});
			});
		}
	};
	$scope.refreshHierarchy = function() {
		if ($scope.hierarchy) {
			$('#tree-hierarchy').fancytree({
				extensions: ["glyph", "table"],
				glyph: {
					preset: "bootstrap3",
					map: {
						doc: "glyphicon glyphicon-file",
						docOpen: "glyphicon glyphicon-file",
						checkbox: "glyphicon glyphicon-unchecked",
						checkboxSelected: "glyphicon glyphicon-check",
						checkboxUnknown: "glyphicon glyphicon-share",
						dragHelper: "glyphicon glyphicon-play",
						dropMarker: "glyphicon glyphicon-arrow-right",
						error: "glyphicon glyphicon-warning-sign",
						expanderClosed: "glyphicon glyphicon-plus-sign",
						expanderLazy: "glyphicon glyphicon-plus-sign",
						// expanderLazy: "glyphicon glyphicon-expand",
						expanderOpen: "glyphicon glyphicon-minus-sign",
						// expanderOpen: "glyphicon glyphicon-collapse-down",
						folder: "glyphicon glyphicon-folder-close",
						folderOpen: "glyphicon glyphicon-folder-open",
						loading: "glyphicon glyphicon-refresh glyphicon-spin"
					}
				},
				source: $scope.hierarchy,
				init: function() {
					$('#tree-hierarchy').floatThead({
						position: 'auto',
						autoReflow: true,
						top: $('.navbar').height(),
					});
				},
				scrollParent: $(window),
				renderColumns: function(event, data) {
					var node = data.node;
					$tdList = $(node.tr).find(">td");

					{% if client -%}
					var URL = '{{ url_for("view.cli_settings", client=client, server=server) }}?conf='+encodeURIComponent(node.data.full);
					{% else -%}
					var URL = '{{ url_for("view.settings", server=server) }}?conf='+encodeURIComponent(node.data.full);
					{% endif -%}

					$tdList.eq(1).html('<a href="'+URL+'" class="btn btn-info btn-xs no-link pull-right"><span class="glyphicon glyphicon-pencil" aria-hidden="true">&nbsp;{{ _("Edit") }}</a>');
				},
			});
			var tree = $('#tree-hierarchy').fancytree('getTree');

			tree.getRootNode().visit(function(node) {
				node.setExpanded(true);
			});
		}
	};
	$scope.refreshScrollspy = function() {
		angular.forEach($('.bui-scrollspy > li'), function(e) {
			var ae = angular.element(e);
			var target = e.dataset.target;
			var options = {
				scope: $scope,
				target: target
			};
			if (target in $scope.spy) {
				var oldSpy = $scope.spy[target];
				oldSpy.untrackElement(options.target, ae);
		    oldSpy.destroy();
			}
			var scrollspy = $scrollspy(options);
			scrollspy.trackElement(options.target, ae);
			$scope.spy[target] = scrollspy;
		});
	};
	$scope.remove = function(key, index) {
		if (!$scope.old[key]) {
			$scope.old[key] = {};
		}
		$scope.old[key][$scope[key][index]['name']] = $scope[key][index]['value'];
		$scope[key].splice(index, 1);
		$scope.add[key] = false;
		$scope.new[key] = undefined;
		$scope.changed = true;
	};
	$scope.clickAdd = function(type) {
		if ($scope.new[type]) {
			$scope.new[type] = undefined;
		}
		$scope.add[type] = true;
		all = $scope.all[type];
		if (type === 'templates') {
			all = [];
			_($scope.all[type]).forEach(function(value, name) {
				all.push(name);
			});
		}
		keys = _.map($scope[type], 'name');
		diff = _.difference(all, keys);
		$scope.avail[type] = [];
		_(diff).forEach(function(n) {
			v = $scope.defaults[n];
			if (!v && type === 'multis') {
				v = [''];
			}
			if (!v && type === 'templates') {
				v = $scope.all[type][n];
			}
			$scope.avail[type].push({'name': n, 'value': v});
		});
	};
	$scope.undoAdd = function(type) {
		$scope.add[type] = false;
	};
	$scope.removeMulti = function(pindex, cindex) {
		$scope.multis[pindex].value.splice(cindex, 1);
		if ($scope.multis[pindex].value.length <= 0) {
			$scope.multis.splice(pindex, 1);
		}
		$scope.add.multis = false;
		$scope.new.multis = false;
		$scope.changed = true;
		$scope.refreshScrollspy();
	};
	$scope.addMulti = function(pindex) {
		$scope.multis[pindex].value.push('');
		$scope.add.multis = false;
		$scope.new.multis = false;
		$scope.changed = true;
		$scope.refreshScrollspy();
	};
	$scope.removeIncludes = function(index) {
		if (!$scope.old.includes) {
			$scope.old.includes = [];
		}
		if (!$scope.old.includes_ori) {
			$scope.old.includes_ori = [];
		}
		$scope.old.includes.push($scope.includes[index]);
		$scope.old.includes_ori.push($scope.includes_ori[index]);
		$scope.includes.splice(index, 1);
		$scope.includes_ori.splice(index, 1);
		$scope.changed = true;
		$scope.refreshScrollspy();
	};
	$scope.clickAddIncludes = function() {
		val = '';
		val2 = false;
		if ($scope.old.includes) {
			val = $scope.old.includes.pop();
		}
		if ($scope.old.includes_ori) {
			val2 = $scope.old.includes_ori.pop();
		}
		$scope.includes.push(val);
		$scope.includes_ori.push(val2);
		$scope.changed = true;
		$scope.refreshScrollspy();
	};
	$scope.select = function(selected, select, type) {
		select.search = undefined;
		if ($scope.old[type] && $scope.old[type][selected.name]) {
			selected.value = $scope.old[type][selected.name];
		}
		$scope[type].push(selected);
		console.log(selected);
		console.log($scope[type]);
		$scope.add[type] = false;
		$scope.changed = true;
	};
	/* A client has been selected, we redirect to the client config page */
	$scope.selectClient = function(selected, select) {
		select.search = undefined;
		document.location = '{{ url_for("view.cli_settings", server=server) }}?client='+selected.name;
	};
	/* A config file has been selected for edition, we redirect the client */
	$scope.editInclude = function(parent, index) {
		file = $scope.paths[parent][index];
		{% if client -%}
		document.location = '{{ url_for("view.cli_settings", client=client, server=server) }}?conf='+file;
		{% else -%}
		document.location = '{{ url_for("view.settings", server=server) }}?conf='+file;
		{% endif -%}
	};
	$scope.expandPath = function(index) {
		path = $scope.includes[index];
		{% if client -%}
		api = '{{ url_for("api.path_expander", client=client, server=server, source=conf) }}';
		{% else -%}
		api = '{{ url_for("api.path_expander", server=server, source=conf) }}';
		{% endif -%}
		$scope.inc_invalid = {};
		$http.get(
			api,
			{
				headers: { 'X-From-UI': true },
				params: { 'path': path },
			}
		).then(
			function(response) {
				data = response.data;
				/* The server answered correctly but some errors may have occurred server
				 * side so we display them */
				if (data.notif) {
					notifAll(data.notif)
					$scope.inc_invalid[index] = true;
				} else if (data.result) {
					$scope.paths[index] = [];
					$.each(data.result, function(i, p) {
						$scope.paths[index].push(p);
					});
				}
			},
			function(response) {
				errorsHandler(response.data);
			}
		);
	};
	$scope.getClientsList = function() {
		api = '{{ url_for("api.clients_list", server=server) }}';
		$http.get(
			api,
			{
				headers: { 'X-From-UI': true },
			}
		).then(
			function(response) {
				var data = response.data;
				$scope.clients = data.result;
			}
		);
	};
	$scope.getTemplatesList = function() {
		api = '{{ url_for("api.templates_list", server=server) }}';
		$http.get(
			api,
			{
				headers: { 'X-From-UI': true },
			}
		).then(
			function(response) {
				var data = response.data;
				$scope.raw.templates = data.result;
				$scope.all.templates = {};
				_(data.result).forEach(function(r) {
					$scope.all.templates[r.name] = r.value;
				});
			}
		);
	};
	$scope.deleteClient = function() {
		api = '{{ url_for("api.client_settings", client=client, server=server) }}';
		$.ajax({
			url: api,
			type: 'DELETE',
			{% if template -%}
			data: { template: true }
			{% else -%}
			data: { delcert: $('#delcert').is(':checked'), revoke: $('#revoke').is(':checked'), keepconf: $('#keepconf').is(':checked') }
			{% endif -%}
		})
		.fail(myFail)
		.done(function(data) {
			redirect = data[0][0] == NOTIF_SUCCESS;
			notifAll(data, redirect);
			if (redirect) {
				document.location = '{{ url_for("view.settings", server=server) }}';
			}
		});
	};
	$scope.createClient = function(e) {
		/* we disable the 'real' form submission */
		e.preventDefault();
		var form = $(e.target);
		$.ajax({
			url: form.attr('action'),
			type: 'PUT',
			data: form.serialize()
		})
		.fail(myFail)
		.done(function(data) {
			/* The server answered correctly but some errors may have occurred server
			 * side so we display them */
			if (data.notif) {
				notif(data.notif[0][0], data.notif[0][1]);
				if (data.notif[0][0] == NOTIF_SUCCESS) {
					$scope.getClientsList();
					notif(data.notif[1][0], data.notif[1][1], 20000);
				}
			}
		});
	};
	$scope.createTemplate = function(e) {
		/* we disable the 'real' form submission */
		e.preventDefault();
		var form = $(e.target);
		$.ajax({
			url: form.attr('action'),
			type: 'PUT',
			data: form.serialize()
		})
		.fail(myFail)
		.done(function(data) {
			/* The server answered correctly but some errors may have occurred server
			 * side so we display them */
			if (data.notif) {
				notif(data.notif[0][0], data.notif[0][1]);
				if (data.notif[0][0] == NOTIF_SUCCESS) {
					$scope.getTemplatesList();
					notif(data.notif[1][0], data.notif[1][1], 20000);
				}
			}
		});
	};
	/* These callbacks expand/reduce the input for a better readability */
	$scope.focusIn = function(ev) {
		el = $( ev.target ).parent();
		/* Hide the button */
		el.next('div').hide();
		/* Hide the legend */
		el.next('div').next('div').hide();
		/* Hide the reset button */
		el.next('div').next('div').next('div').hide();
		/* Expand the input */
		el.removeClass('col-lg-2').addClass('col-lg-9');
	};
	$scope.focusOut = function(ev) {
		el = $( ev.target ).parent();
		el.next('div').show();
		el.next('div').next('div').show();
		el.next('div').next('div').next('div').show();
		el.removeClass('col-lg-9').addClass('col-lg-2');
	};
	$scope.loadConfig();
	$scope.getClientsList();
	$scope.getTemplatesList();
}]);

{{ macros.page_length('#table-list-clients') }}
{{ macros.page_length('#table-list-templates') }}

$(document).ready(function () {
	$('#config-nav a').click(function (e) {
		e.preventDefault();
		$(this).tab('show');
	});
});
