diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js index c7af191..249da95 100644 --- a/public_html/js/kolab_admin.js +++ b/public_html/js/kolab_admin.js @@ -1,3639 +1,3640 @@ /* +--------------------------------------------------------------------------+ | This file is part of the Kolab Web Admin Panel | | | | Copyright (C) 2011-2014, Kolab Systems AG | | | | This program is free software: you can redistribute it and/or modify | | it under the terms of the GNU Affero General Public License as published | | by the Free Software Foundation, either version 3 of the License, or | | (at your option) any later version. | | | | This program is distributed in the hope that it will be useful, | | but WITHOUT ANY WARRANTY; without even the implied warranty of | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | GNU Affero General Public License for more details. | | | | You should have received a copy of the GNU Affero General Public License | | along with this program. If not, see | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ function kolab_admin() { var self = this; this.env = {}; this.translations = {}; this.request_timeout = 300; this.message_time = 3000; this.events = {}; this.attr_default_rx = /^(text|select|imap_acl)/; // set jQuery ajax options $.ajaxSetup({ cache: false, error: function(request, status, err) { self.http_error(request, status, err); }, beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', self.env.token); } }); /*********************************************************/ /********* basic utilities *********/ /*********************************************************/ // set environment variable(s) this.set_env = function(p, value) { if (p != null && typeof p === 'object' && !value) for (var n in p) this.env[n] = p[n]; else this.env[p] = value; }; // add a localized label(s) to the client environment this.tdef = function(p, value) { if (typeof p == 'string') this.translations[p] = value; else if (typeof p == 'object') $.extend(this.translations, p); }; // return a localized string this.t = function(label) { if (this.translations[label]) return this.translations[label]; else return label; }; // print a message into browser console this.log = function(msg) { if (window.console && console.log) console.log(msg); }; // execute a specific command on the web client this.command = function(command, props, obj) { if (obj && obj.blur) obj.blur(); if (this.busy) return false; this.set_busy(true, 'loading'); var ret = undefined, func = command.replace(/[^a-z]/g, '_'), task = command.replace(/\.[a-z-_]+$/g, ''); if (this[func] && typeof this[func] === 'function') { ret = this[func](props); } else { this.http_post(command, props); } // update menu state $('li', $('#navigation')).removeClass('active'); $('li.'+task, ('#navigation')).addClass('active'); if (ret === false) this.set_busy(false); return ret === false ? false : obj ? false : true; }; this.set_busy = function(a, message) { if (a && message) { var msg = this.t(message); if (msg == message) msg = 'Loading...'; this.display_message(msg, 'loading'); } else if (!a) { this.hide_message('loading'); } this.busy = a; // if (this.gui_objects.editform) // this.lock_form(this.gui_objects.editform, a); // clear pending timer if (this.request_timer) clearTimeout(this.request_timer); // set timer for requests if (a && this.env.request_timeout) this.request_timer = window.setTimeout(function() { self.request_timed_out(); }, this.request_timeout * 1000); }; // called when a request timed out this.request_timed_out = function() { this.set_busy(false); this.display_message('Request timed out!', 'error'); }; // Add variable to GET string, replace old value if exists this.add_url = function(url, name, value) { value = urlencode(value); if (/(\?.*)$/.test(url)) { var urldata = RegExp.$1, datax = RegExp('((\\?|&)'+RegExp.escape(name)+'=[^&]*)'); if (datax.test(urldata)) urldata = urldata.replace(datax, RegExp.$2 + name + '=' + value); else urldata += '&' + name + '=' + value return url.replace(/(\?.*)$/, urldata); } else return url + '?' + name + '=' + value; }; this.trigger_event = function(event, data) { if (this.events[event]) for (var i in this.events[event]) this.events[event][i](data); }; this.add_event_listener = function(event, func) { if (!this.events[event]) this.events[event] = []; this.events[event].push(func); }; /*********************************************************/ /********* GUI functionality *********/ /*********************************************************/ // write to the document/window title this.set_pagetitle = function(title) { if (title && document.title) document.title = title; }; // display a system message (types: loading, notice, error) this.display_message = function(msg, type, timeout) { var obj; if (!type) type = 'notice'; if (msg) msg = this.t(msg); if (type == 'loading') { timeout = this.request_timeout * 1000; if (!msg) msg = this.t('loading'); } else if (!timeout) timeout = this.message_time * (type == 'error' || type == 'warning' ? 2 : 1); obj = $('
'); if (type != 'loading') { msg = '
' + msg + '
'; obj.addClass(type).click(function() { return self.hide_message(); }); } if (timeout > 0) window.setTimeout(function() { self.hide_message(type, type != 'loading'); }, timeout); if (type == 'loading') this.hide_message(type); obj.attr('id', type == 'loading' ? 'loading' : 'message') .appendTo('body').html(msg).show(); }; // make a message to disapear this.hide_message = function(type, fade) { if (type == 'loading') $('#loading').remove(); else $('#message').fadeOut('normal', function() { $(this).remove(); }); }; this.set_watermark = function(id) { if (this.env.watermark) $('#'+id).html(this.env.watermark); } // modal dialog popup this.modal_dialog = function(content, buttons, opts) { var settings = {btns: {}}, body = $(''), head, foot, footer = []; // title bar if (opts && opts.title) $('') .append($('').text(opts.title)) .appendTo(body); // dialog content if (typeof content != 'object') content = $('
').html(content); content.addClass('modal_msg').appendTo(body); // buttons $.each(buttons, function(i, v) { var n = i.replace(/[^a-z0-9_]/ig, ''); settings.btns[n] = v; footer.push({name: n, label: self.t(i)}); }); // if (!settings.btns.cancel && (!opts || !opts.no_cancel)) // settings.btns.cancel = function() { this.hide(); }; if (footer.length) { foot = $(''); $.each(footer, function() { $('
').addClass('modal_btn_' + this.name).text(this.label).appendTo(foot); }); body.append(foot); } // configure and display dialog body.wModal(settings).wModal('show'); }; this.tree_list_init = function() { $('table.list.tree span.expando').click(function() { var tr = $(this).parents('table.list.tree tr'), expanded = tr.hasClass('expanded'), level = tr.data('level') || 0, row = tr[0], found = false; tr[expanded ? 'removeClass' : 'addClass']('expanded'); $('tr', tr.parent()).each(function() { if (this === row) { found = true; return; } if (!found) return; var r = $(this), l = r.data('level') || 0; if (l <= level) return false; if (!expanded && l == level+1) r.show(); else if (expanded && l > level) r.hide().removeClass('expanded'); }); return false; }); }; // position and display popup this.popup_show = function(e, popup) { var popup = $(popup), pos = this.mouse_pos(e), win = $(window), w = popup.width(), h = popup.height(), left = pos.left - w, top = pos.top; if (top + h > win.height()) top -= h; if (left + w > win.width()) left -= w; popup.css({left: left + 'px', top: top + 'px'}).show(); e.stopPropagation(); }; /********************************************************/ /********* Remote request methods *********/ /********************************************************/ // compose a valid url with the given parameters this.url = function(action, query) { var k, param = {}, querystring = typeof query === 'string' ? '&' + query : ''; if (typeof action !== 'string') query = action; else if (!query || typeof query !== 'object') query = {}; // overwrite task name if (action) { if (action.match(/^([a-z]+)/i)) query.task = RegExp.$1; if (action.match(/[^a-z0-9-_]([a-z0-9-_]+)$/i)) query.action = RegExp.$1; } // remove undefined values for (k in query) { if (query[k] !== undefined && query[k] !== null) param[k] = query[k]; } return '?' + $.param(param) + querystring; }; // send a http POST request to the server this.http_post = function(action, postdata) { var url = this.url(action); if (postdata && typeof postdata === 'object') postdata.remote = 1; else { if (!postdata) postdata = ''; postdata += '&remote=1'; } this.set_request_time(); return $.ajax({ type: 'POST', url: url, data: postdata, dataType: 'json', success: function(response) { kadm.http_response(response, action); }, error: function(o, status, err) { kadm.http_error(o, status, err); } }); }; // send a http POST request to the API service this.api_post = function(action, postdata, func) { var url = 'api/' + action; if (!func) func = 'api_response'; this.set_request_time(); return $.ajax({ type: 'POST', url: url, data: JSON.stringify(postdata), dataType: 'json', contentType: 'application/json; charset=utf-8', success: function(response) { kadm[func](response); }, error: function(o, status, err) { kadm.http_error(o, status, err); } }); }; // handle HTTP response this.http_response = function(response, action) { var i; if (!response) return; // set env vars if (response.env) this.set_env(response.env); // we have translation labels to add if (typeof response.labels === 'object') this.tdef(response.labels); // HTML page elements if (response.objects) for (i in response.objects) $('#'+i).html(response.objects[i]); this.update_request_time(); this.set_busy(false); // if we get javascript code from server -> execute it if (response.exec) eval(response.exec); response.action = action; this.trigger_event('http-response', response); }; // handle HTTP request errors this.http_error = function(request, status, err) { var errmsg = request.statusText; this.set_busy(false); request.abort(); if (request.status && errmsg) this.display_message(this.t('servererror') + ' (' + errmsg + ')', 'error'); }; this.api_response = function(response) { this.update_request_time(); this.set_busy(false); if (!response || response.status != 'OK') { // Logout on invalid-session error if (response && response.code == 403) this.main_logout(); else this.display_message(response && response.reason ? response.reason : this.t('servererror'), 'error'); return false; } return true; }; /*********************************************************/ /********* keyboard autocomplete methods *********/ /*********************************************************/ this.ac_init = function(obj, props) { if (props && props.form) { if (i = $('[name="type_id"]', props.form).val()) props.type_id = i; if (i = $('[name="object_type"]', props.form).val()) props.object_type = i; if (i = $('[name="id"]', props.form).val()) props.id = i; delete props['form']; } obj.keydown(function(e) { return kadm.ac_keydown(e, props); }) .attr('autocomplete', 'off'); }; // handler for keyboard events on autocomplete-fields this.ac_keydown = function(e, props) { if (this.ac_timer) clearTimeout(this.ac_timer); var highlight, key = e.which; switch (key) { case 38: // arrow up case 40: // arrow down if (!this.ac_visible()) break; var dir = key == 38 ? 1 : 0; highlight = $('.selected', this.ac_pane).get(0); if (!highlight) highlight = this.ac_pane.__ul.firstChild; if (highlight) this.ac_select(dir ? highlight.previousSibling : highlight.nextSibling); return e.stopPropagation(); case 9: // tab if (e.shiftKey || !this.ac_visible()) { this.ac_stop(); return; } case 13: // enter if (!this.ac_visible()) return false; // insert selected item and hide selection pane this.ac_insert(this.ac_selected); this.ac_stop(); - return e.stopPropagation(); + e.stopPropagation(); + return false; case 27: // escape this.ac_stop(); return; case 37: // left case 39: // right if (!e.shiftKey) return; } // start timer this.ac_timer = window.setTimeout(function() { kadm.ac_start(props); }, 500); this.ac_input = e.target; return true; }; this.ac_visible = function() { return (this.ac_selected !== null && this.ac_selected !== undefined && this.ac_value); }; this.ac_select = function(node) { if (!node) return; var current = $('.selected', this.ac_pane); if (current.length) current.removeClass('selected'); $(node).addClass('selected'); this.ac_selected = node._id; }; // autocomplete search processor this.ac_start = function(props) { var q = this.ac_input ? this.ac_input.value : null, min = this.env.autocomplete_min_length, old_value = this.ac_value, ac = this.ac_data; if (q === null) return; // trim query string q = $.trim(q); // Don't (re-)search if the last results are still active if (q == old_value) return; // Stop and destroy last search this.ac_stop(); if (q.length && q.length < min) { this.display_message(this.t('search.acchars').replace('$min', min), 'notice', 2000); return; } this.ac_value = q; // ...string is empty if (!q.length) return; // ...new search value contains old one, but the old result was empty if (old_value && old_value.length && q.indexOf(old_value) == 0 && this.ac_result && !this.ac_result.length) return; var i, xhr, data = props, action = props && props.action ? props.action : 'form_value.list_options'; this.ac_oninsert = props.oninsert; data.search = q; delete data['action']; delete data['insert_func']; this.display_message(this.t('search.loading'), 'loading'); xhr = this.api_post(action, data, 'ac_result'); this.ac_data = xhr; }; this.ac_result = function(response) { // search stopped in meantime? if (!this.ac_value) return; if (!this.api_response(response)) return; // ignore this outdated search response if (this.ac_input && response.result.search != this.ac_value) return; // display search results var i, ul, li, text, result = response.result.list, pos = $(this.ac_input).offset(), value = this.ac_value, rx = new RegExp('(' + RegExp.escape(value) + ')', 'ig'); // create results pane if not present if (!this.ac_pane) { ul = $('
    '); this.ac_pane = $('
    ').attr('id', 'autocompletepane') .css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body); this.ac_pane.__ul = ul[0]; } ul = this.ac_pane.__ul; // reset content ul.innerHTML = ''; // move the results pane right under the input box this.ac_pane.css({left: (pos.left - 1)+'px', top: (pos.top + this.ac_input.offsetHeight - 1)+'px', display: 'none'}); // add each result line to the list for (i in result) { text = result[i]; li = document.createElement('LI'); li.innerHTML = text.replace(rx, '##$1%%').replace(//g, '>').replace(/##([^%]+)%%/g, '$1'); li.onmouseover = function() { kadm.ac_select(this); }; li.onmouseup = function() { kadm.ac_click(this) }; li._id = i; ul.appendChild(li); } if (ul.childNodes.length) { this.ac_pane.show(); // select the first li = $('li:first', ul); li.addClass('selected'); this.ac_selected = li.get(0)._id; } this.env.ac_result = result; }; this.ac_click = function(node) { if (this.ac_input) this.ac_input.focus(); this.ac_insert(node._id); this.ac_stop(); }; this.ac_insert = function(id) { var val = this.env.ac_result[id]; if (typeof this.ac_oninsert == 'function') this.ac_oninsert(id, val); else $(this.ac_input).val(val); }; this.ac_blur = function() { if (this.ac_timer) clearTimeout(this.ac_timer); this.ac_input = null; this.ac_stop(); }; this.ac_stop = function() { this.ac_selected = null; this.ac_value = ''; if (this.ac_pane) this.ac_pane.hide(); this.ac_destroy(); }; // Clears autocomplete data/requests this.ac_destroy = function() { if (this.ac_data) this.ac_data.abort(); this.ac_data = null; this.ac_info = null; }; /*********************************************************/ /********* Forms widgets *********/ /*********************************************************/ // Form initialization this.form_init = function(id) { var form = $('#'+id), aci_fields = $('textarea[data-type="aci"]', form); this.aci = {}; this.acl = {}; this.trigger_event('form-load', id); // replace some textarea fields with pretty/smart input lists $('textarea[data-type="list"]', form) .each(function() { kadm.form_list_element_wrapper(this); }); // create smart select fields $('input[data-type="select"]', form) .each(function() { kadm.form_select_element_wrapper(this); }); // create LDAP URL fields $('input[data-type="ldap_url"]:not(:disabled):not([readonly])', form) .each(function() { kadm.form_url_element_wrapper(this); }); // create IMAP ACL fields $('textarea[data-type="acl"]', form) .each(function() { kadm.form_acl_element_wrapper(this); }); // create ACI fields aci_fields.each(function() { kadm.form_aci_element_wrapper(this); }); if (aci_fields.length) this.form_aci_init(); }; // Form serialization this.form_serialize = function(data) { var form = $(data.id); // smart list fields $('textarea[data-type="list"]:not(:disabled)', form).each(function() { var i, v, value = [], re = RegExp('^' + RegExp.escape(this.name) + '\[[0-9-]+\]$'); for (i in data.json) { if (i.match(re)) { if (v = $('input[name="'+i+'"]', form).val()) value.push(v); delete data.json[i]; } } // autocompletion lists data is stored in env variable if (kadm.env.assoc_fields[this.name]) { value = []; for (i in kadm.env.assoc_fields[this.name]) value.push(i); } data.json[this.name] = value; }); // smart selects $('input[data-type="select"]', form).each(function() { delete data.json[this.name]; }); // LDAP URL fields $('input[data-type="ldap_url"]:not(:disabled):not([readonly])', form).each(function() { data.json = kadm.form_url_element_submit(this.name, data.json, form); }); // IMAP ACL fields $('textarea[data-type="acl"]:not(:disabled):not([readonly])', form).each(function() { data.json = kadm.form_acl_element_submit(this.name, data.json, form); }); // ACI fields $('textarea[data-type="aci"]:not(:disabled):not([readonly])', form).each(function() { data.json = kadm.form_aci_element_submit(this.name, data.json, form); }); // text-separated fields (convert text into array) $('textarea[data-type="separated"]:not(:disabled)', form).each(function() { data.json[this.name] = data.json[this.name] ? data.json[this.name].split(/[\r\n]+/) : ''; }); // quota inputs $('input[data-type="quota"]', form).each(function() { var unit = $('select[name="' + this.name + '-unit"]').val(); if (unit && this.value) data.json[this.name] = this.value + unit; delete data.json[this.name + '-unit']; }); // "boolean" checkbox inputs $('input[type="checkbox"][value="TRUE"]', form).each(function() { data.json[this.name] = this.checked ? 'TRUE' : 'FALSE'; }); return data; }; // Form element update handler this.form_element_update = function(data) { var elem = $('[name="'+data.name+'"]'); if (!elem.length) return; if (elem.attr('data-type') == 'list') { // remove old wrapper $('span[class="listarea"]', elem.parent()).remove(); // insert new list element this.form_list_element_wrapper(elem.get(0)); } }; // Replaces form element with smart list element this.form_list_element_wrapper = function(form_element) { var i = 0, j = 0, list = [], elem, e = $(form_element), form = form_element.form, disabled = e.attr('disabled'), readonly = e.attr('readonly'), autocomplete = e.attr('data-autocomplete'), maxlength = e.attr('data-maxlength'), maxcount = e.attr('data-maxcount'), area = $(''); e.hide(); if (autocomplete) list = this.env.assoc_fields ? this.env.assoc_fields[form_element.name] : []; else if (form_element.value) list = form_element.value.split("\n"); // Need at least one element if (!autocomplete || disabled || readonly) { $.each(list, function() { i++; }); if (!i) list = ['']; } // Create simple list for readonly/disabled if (disabled || readonly) { area.addClass('readonly'); // add simple input rows $.each(list, function(i, v) { var elem = $(''); elem.attr({ value: v, disabled: disabled, readonly: readonly, name: form_element.name + '[' + (j++) + ']' }) elem = $('').append(elem); elem.appendTo(area); }); } // extended widget with add/remove buttons and/or autocompletion else { // add autocompletion input if (autocomplete) { elem = this.form_list_element(form, { maxlength: maxlength, autocomplete: autocomplete, element: e }, -1); // Initialize autocompletion this.ac_init(elem, { form: form, attribute: form_element.name, oninsert: this.form_element_oninsert }); // when max=1 we use only one input if (maxcount == 1) { $.each(list, function(i, v) { $('input', elem).val(v); return false; }); list = []; } elem.appendTo(area); area.addClass('autocomplete'); } // add input rows $.each(list, function(i, v) { var elem = kadm.form_list_element(form, { value: v, key: i, maxlength: maxlength, autocomplete: autocomplete, element: e }, j++); elem.appendTo(area); }); } area.appendTo(form_element.parentNode); }; // Creates smart list element this.form_list_element = function(form, data, idx) { var content, elem, input, key = data.key, orig = data.element ac = data.autocomplete; data.name = (orig ? orig.attr('name') : data.name) + '[' + idx + ']'; data.readonly = (ac && idx >= 0); // remove internal attributes delete data['element']; delete data['autocomplete']; delete data['key']; // build element content content = '' + (!ac ? '' : ac && idx == -1 ? '' : '') + (!ac || idx >= 0 ? '' : '') + ''; elem = $(content); input = $('input', elem); // Set INPUT attributes input.attr(data); if (data.readonly) input.addClass('readonly'); if (ac) input.addClass('autocomplete'); // attach element creation event if (!ac) $('span[class="add"]', elem).click(function() { var name = data.name.replace(/\[[0-9]+\]$/, ''), span = $(this.parentNode.parentNode), maxcount = $('textarea[name="'+name+'"]').attr('data-maxcount'); // check element count limit if (maxcount && maxcount <= span.parent().children().length) { alert(kadm.t('form.maxcount.exceeded')); return; } var dt = (new Date()).getTime(), elem = kadm.form_list_element(form, {name: name}, dt); kadm.ac_stop(); span.after(elem); $('input', elem).focus(); }); // attach element deletion event if (!ac || idx >= 0) $('span[class="reset"]', elem).click(function() { var span = $(this.parentNode.parentNode), name = data.name.replace(/\[[0-9]+\]$/, ''), l = $('input[name^="' + name + '["]', form), key = $(this).data('key'); if (l.length > 1 || $('input[name="' + name + '"]', form).attr('data-autocomplete')) span.remove(); else $('input', span).val('').focus(); // delete key from internal field representation if (key !== undefined && kadm.env.assoc_fields[name]) delete kadm.env.assoc_fields[name][key]; kadm.ac_stop(); }).data('key', key); return elem; }; this.form_element_oninsert = function(key, val) { var elem, input = $(this.ac_input).get(0), dt = (new Date()).getTime(), span = $(input.parentNode), name = input.name.replace(/\[-1\]$/, ''), af = kadm.env.assoc_fields, maxcount = $('textarea[name="'+name+'"]').attr('data-maxcount'); if (maxcount == 1) { $(input).val(val); af[name] = {}; af[name][key] = val; return; } // reset autocomplete input input.value = ''; // check element count limit if (maxcount && maxcount <= span.parent().children().length - 1) { alert(kadm.t('form.maxcount.exceeded')); return; } // check if element doesn't exist on the list already if (!af[name]) af[name] = {}; if (af[name][key]) return; // add element elem = kadm.form_list_element(input.form, { name: name, autocomplete: true, value: val }, dt); span.after(elem); // update field variable af[name][key] = val; }; // Replaces form element with smart select element this.form_select_element_wrapper = function(form_element) { var e = $(form_element), form = form_element.form, name = form_element.name, elem = $('#selectlabel_' + name), area = $(''), content = $(''), list = this.env.assoc_fields ? this.env.assoc_fields[form_element.name] : []; if (elem.length) { $('#selectarea_' + name).remove(); $('#selectcontent_' + name).remove(); } else { elem = $('') .css({cursor: 'pointer'}) .click(function(e) { var popup = $('span.listarea', this.parentNode); kadm.popup_show(e, popup); $('input', popup).val('').focus(); $('span.listcontent > span.listelement', popup).removeClass('selected').show(); }) .appendTo(form_element.parentNode); } elem.text(e.val()); if (list.length <= 1) return; if (form_element.type != 'hidden') e.hide(); elem = this.form_list_element(form, { autocomplete: true, element: e }, -1); elem.appendTo(area); content.appendTo(area); area.hide().appendTo(form_element.parentNode); // popup events $('input', area) .click(function(e) { // stop click on the popup e.stopPropagation(); }) .keypress(function(e) { // prevent form submission with Enter key if (e.which == 13) e.preventDefault(); }) .keydown(function(e) { // block Up/Down arrow keys, // in Firefox Up arrow moves cursor left if (e.which == 38 || e.which == 40) e.preventDefault(); }) .keyup(function(e) { // filtering var s = this.value, options = $('span.listcontent > span.listelement', area); // Enter key if (e.which == 13) { options.filter('.selected').click() return; } // Escape else if (e.which == 27) { area.hide(); this.value = s = ''; } // UP/Down arrows else if (e.which == 38 || e.which == 40) { options = options.not(':hidden'); if (options.length <= 1) return; var focused, selected = options.filter('.selected'), index = options.index(selected); if (e.which == 40) { if (!(focused = options.get(index+1))) focused = options.get(index-1); } else { if (!(focused = options.get(index-1))) focused = options.get(index+1); } if (focused) { focused = $(focused); selected.removeClass('selected'); focused.addClass('selected'); var parent = focused.parent(), parent_height = parent.height(), parent_top = parent.get(0).scrollTop, top = focused.offset().top - parent.offset().top, height = focused.height(); if (top < 0) parent.get(0).scrollTop = 0; else if (top >= parent_height) parent.get(0).scrollTop = top - parent_height + height + parent_top; } return; } if (!s) { options.show().removeClass('selected'); return; } options.each(function() { var o = $(this), v = o.data('value'); o[v.indexOf(s) != -1 ? 'show' : 'hide']().removeClass('selected'); }); options = options.not(':hidden'); if (options.length == 1) options.addClass('selected'); }); // add option rows $.each(list, function(i, v) { var elem = kadm.form_select_option_element(form, {value: v, key: v, element: e}); elem.appendTo(content); }); }; // Creates option element for smart select this.form_select_option_element = function(form, data) { // build element content var elem = $('') .data('value', data.key).text(data.value) .click(function(e) { var val = $(this).data('value'), elem = $(data.element), old_val = elem.val(); $('span.link', elem.parent()).text(val); elem.val(val); if (val != old_val) elem.change(); }); return elem; }; // initialize ACI fields in form this.form_aci_init = function() { // get list of ldap attributes for ACI form if (!this.ldap_attributes) { this.api_post('form_value.select_options', {attributes: ['attribute']}, 'form_aci_init_response'); } }; this.form_aci_init_response = function(response) { if (!this.api_response(response)) return; this.ldap_attributes = response.result.attribute ? response.result.attribute.list : []; }; // Replaces form element with ACI element this.form_aci_element_wrapper = function(form_element) { var i, e = $(form_element), form = form_element.form, name = form_element.name, div = $('
    '), select = $(''), table = $('
    '), buttons = [ $('').attr({value: this.t('aci.new')}), $('').attr({value: this.t('aci.edit'), disabled: true}), $('').attr({value: this.t('aci.remove'), disabled: true}) ], aci = this.parse_aci(e.val()) || []; this.aci[name] = aci; e.hide(); // this.log(e.val()); // this.log(aci); $.each(aci, function(i, entry) { $('').val(i).text(entry.name).appendTo(select) .on('dblclick', function () { self.form_aci_dialog(name, this.value); }); }); select.attr('id', 'aci'+name).on('change', function() { var selected = $(this).val() || []; buttons[1].prop('disabled', selected.length != 1); buttons[2].prop('disabled', selected.length == 0); }); // click on 'new' and 'edit' button buttons[0].on('click', function() { self.form_aci_dialog(name); }); buttons[1].on('click', function() { var selected = select.val(); self.form_aci_dialog(name, selected && selected.length ? selected[0] : null); }); // click on 'remove' button buttons[2].on('click', function() { $.each(select.val() || [], function(i, v) { self.aci[name][v] = null; $('option[value="' + v + '"]', select).remove(); }); buttons[1].prop('disabled', true); buttons[2].prop('disabled', true); }); $('.buttons', table).append(buttons); $('.list', table).append(select); div.append(table) $(form_element).parent().append(div); }; // updates form data with ACI (on form submit) this.form_aci_element_submit = function(name, data, form) { data[name] = this.build_aci(this.aci[name]); return data; }; // display aci dialog this.form_aci_dialog = function(name, id) { var aci = id ? this.aci[name][id] : {}; this.aci_dialog_aci = aci; this.aci_dialog_name = name; this.aci_dialog_id = id; this.modal_dialog(this.form_aci_dialog_content(aci), this.form_aci_dialog_buttons()); window.setTimeout(function() { $('#aci-name').focus(); }, 100); }; // return aci dialog buttons this.form_aci_dialog_buttons = function() { var buttons = { 'button.ok': function() { if (self.form_aci_dialog_submit()) { this.hide(); $('#aci-dialog').remove(); } }, 'button.cancel': function() { this.hide(); $('#aci-dialog').remove(); }, }; return buttons; }; // build and return aci dialog content this.form_aci_dialog_content = function(aci) { var dialog = $('#aci-dialog'); if (!dialog.length) { var i, tabs = [ $('
    ').attr('id', 'aci-tab-users') .append($('').text(this.t('aci.users'))) .append(this.form_aci_dialog_tab_users()), $('
    ').attr('id', 'aci-tab-rights') .append($('').text(this.t('aci.rights'))) .append(this.form_aci_dialog_tab_rights()), $('
    ').attr('id', 'aci-tab-targets') .append($('').text(this.t('aci.targets'))) .append(this.form_aci_dialog_tab_targets()) ]; dialog = $('
    ' + '
    ') .hide().appendTo('body'); dialog.children('label').text(this.t('aci.aciname')); $('#aci-form').append(tabs); this.trigger_event('form-load', 'aci-form'); } // reset form elements this.form_aci_dialog_reset(aci); return dialog.show(); }; this.form_aci_dialog_reset = function(aci) { var users = $('#aci-users').html(''), rights = aci.perms ? aci.perms[0].rights : [], inputs = $('#aci-rights input').prop('checked', false), target = $('#aci-targets-target').val(''), target_filter = $('#aci-targets-filter').val(''), target_operator = $('#aci-targets input[name="attr-operator"]'), rule = aci.perms ? aci.perms[0].type : 'userdn'; $.each(rights, function(i, v) { $('#aci-rights-' + v).click(); }); $('#aci-name').val(aci.name); $('#aci-rights-type').val(aci.perms ? aci.perms[0].type : ''); $('#aci-users-button-remove').prop('disabled', true); target_operator.filter('[value="="]').prop('checked', true); target_operator.filter('[value="!="]').prop('checked', false); $.each(aci.perms ? aci.perms : [], function(i, perm) { $.each(perm.rules || [], function(n, rule) { // these permission rules we do not support here if (!/^(userdn|groupdn|roledn)$/i.test(rule.keyword) || rule.operator != '=') return; $.each(rule.expression || [], function(x, v) { if (v.substr(0, 8) == 'ldap:///') { v = v.substr(8); } var t = v; if (t == 'all' || t == 'self' || t == 'anyone' || t == 'parent') t = self.t('aci.ldap-' + t); else if (/^cn=([^,]+)/.test(t)) t = RegExp.$1; // @TODO: resolve users DN with user names $('').attr({value: rule.keyword + ':' + v}).text(t).appendTo(users); }); }); }); $.each(aci.targets || [], function(i, v) { switch (v.type) { case 'targetfilter': target_filter.val(v.expression); break; case 'targetattr': if (v.expression[0] == '*') $('#aci-targets-attr option').prop('selected', true); else $('#aci-targets-attr').val(v.expression); target_operator.filter('[value="="]').prop('checked', v.operator == '='); target_operator.filter('[value="!="]').prop('checked', v.operator == '!='); break; case 'target': target.val(v.expression); break; } }); }; // submits aci dialog, updates aci definition in form this.form_aci_dialog_submit = function() { var val, rules = [], rights = [], name_input = $('#aci-name'), name = name_input.val(), rights_type = $('#aci-rights-type').val(), aci_list = $('#aci' + this.aci_dialog_name), exists = false, aci = {perms: [], targets: [], version: '3.0', name: name}; // sanity checks if (!name) { alert(this.t('aci.error.noname')); name_input.focus(); return false; } $.each(this.aci[self.aci_dialog_name] || [], function(i, v) { if (v && v.name == name && (!self.aci_dialog_id || self.aci_dialog_id != i)) { exists = true; return false; } }); if (exists) { alert(this.t('aci.error.exists')); name_input.focus(); return false; } // permissions $('#aci-users option').each(function() { var keyword, value = this.value; /^([a-z]+):/.test(value); keyword = RegExp.$1; value = value.substr(keyword.length + 1); rules.push({ join: 'or', operator: '=', keyword: keyword, expression: ['ldap:///' + value] }); }); if (!rules.length) { alert(this.t('aci.error.nousers')); return false; } $('#aci-rights input').each(function() { if (this.checked) { if (this.value == 'all') { rights = ['all']; return false; } rights.push(this.value); } }); if (!rights.length) { rights = ['all']; rights_type = rights_type == 'allow' ? 'deny' : 'allow'; } aci.perms.push({ rights: rights, type: rights_type, rules: rules }); // targets if ((v = $('#aci-targets-attr').val() || []).length) aci.targets.push({ type: 'targetattr', expression: v.length == $('#aci-targets-attr option').length ? ['*'] : v, operator: $('#aci-targets input[name="attr-operator"][value="!="]').is(':checked') ? '!=' : '=', }); if (v = $('#aci-targets-target').val()) aci.targets.push({ type: 'target', expression: v, operator: '=' }); if (v = $('#aci-targets-filter').val()) aci.targets.push({ type: 'targetfilter', expression: v, operator: '=' // @TODO, }); // this.log(aci); // this.log(this.build_aci([aci])); if (this.aci_dialog_id) { this.aci[this.aci_dialog_name][this.aci_dialog_id] = aci; $('option[value="' + this.aci_dialog_id + '"]', aci_list).text(aci.name); } else { this.aci[this.aci_dialog_name].push(aci); $('').val(this.aci[this.aci_dialog_name].length-1) .text(aci.name) .appendTo(aci_list) .on('dblclick', function () { self.form_aci_dialog(self.aci_dialog_name, this.value); }); } return true; }; // tab Users in aci dialog this.form_aci_dialog_tab_users = function() { var select = $(''), table = $('
    '), buttons = [ $('').attr({value: this.t('aci.new')}), $('').attr({value: this.t('aci.remove'), disabled: true}) ]; select.on('change', function() { var selected = $(this).val() || []; buttons[1].attr('disabled', selected.length == 0); }); // click on 'new' button buttons[0].on('click', function() { self.form_aci_user_dialog(); }); // click on 'remove' button buttons[1].on('click', function() { $.each(select.val() || [], function(i, v) { $('option[value="' + v + '"]', select).remove(); }); $(this).prop('disabled', true); }); $('.buttons', table).append(buttons); $('.list', table).append(select); return table; }; // tab Rights in aci dialog this.form_aci_dialog_tab_rights = function() { var div = $('
    '), select = $(''), types = ['allow', 'deny'], rights = ['read', 'compare', 'search', 'write', 'selfwrite', 'delete', 'add', 'proxy', 'all'], inputs = []; $.each(rights, function(i, v) { var input = $('').attr({value: v, id: 'aci-rights-' + v}); inputs.push($('').text(self.t('aci.' + v)).prepend(input)); if (v == 'all') input.on('change', function() { var list = $('input:not(#aci-rights-all)', div); if (this.checked) list.prop({checked: true, disabled: true}); else list.prop({disabled: false}); }); }); $.each(types, function(i, v) { $('').attr({value: v}).text(self.t('aci.' + v)).appendTo(select); }); return div.append(select).append(inputs); }; // tab Targets in aci dialog this.form_aci_dialog_tab_targets = function() { var opts = [], content = $('
    '), target = $(''), filter = $(''), button = $('').val(this.t('aci.thisentry')) .on('click', function() { target.val('ldap:///' + self.env.entrydn); }), select = $(''), radio = [ $('