// // Copyright (C) 2015 FreeIPA Contributors see COPYING for license // define([ 'dojo/_base/lang', 'dojo/_base/declare', 'dojo/Evented', 'dojo/Stateful', 'dojo/Deferred', 'dojo/on', 'dojo/promise/all', 'dojo/when', './builder', './ipa', './jquery', './menu', './metadata', './phases', './reg', './rpc', './text', './details', './facet', './field', './facets/ActionMixin', './facets/HeaderMixin', './facets/Facet', './topology_graph', './navigation', './widget', // plain imports './search', './entity'], function(lang, declare, Evented, Stateful, Deferred, on, all, when, builder, IPA, $, menu, metadata_provider, phases, reg, rpc, text, mod_details, mod_facet, mod_field, ActionMixin, HeaderMixin, Facet, topology_graph, navigation, widget_mod) { /** * Topology module * @class * @singleton */ var topology = IPA.topology = { required_domain_level: 1, search_facet_group: { name: 'search', label: '@i18n:tabs.topology', facets: { suffix_search: 'topologysuffix_search', server_search: 'server_search', server_role_search: 'server_role_search', domainlevel: 'domainlevel_details', topologygraph: 'topology-graph', location_search: 'location_search' } } }; var make_suffix_spec = function() { return { name: 'topologysuffix', enable_test: function() { return true; }, facet_groups: [ 'segments', 'settings' ], facets: [ { $type: 'search', disable_facet_tabs: false, tabs_in_sidebar: true, no_update: true, tab_label: '@mo:topologysuffix.label', facet_groups: [topology.search_facet_group], facet_group: 'search', columns: [ 'cn', 'iparepltopoconfroot' ] }, { $type: 'nested_search', facet_group: 'segments', nested_entity: 'topologysegment', search_all_entries: true, label: '@mo:topologysegment.label', tab_label: '@mo:topologysegment.label', name: 'topologysegment', columns: [ 'cn', 'iparepltoposegmentleftnode', 'iparepltoposegmentrightnode', 'iparepltoposegmentdirection' ] }, { $type: 'details', no_update: true, sections: [ { name: 'details', fields: [ 'cn', { name: 'iparepltopoconfroot', read_only: true } ] } ] } ] };}; var make_segment_spec = function() { return { name: 'topologysegment', containing_entity: 'topologysuffix', enable_test: function() { return true; }, facets: [ { $type: 'details', disable_breadcrumb: false, sections: [ { name: 'details', label: '@i18n:objects.topology.segment_details', fields: [ 'cn', { $type: 'entity_select', name: 'iparepltoposegmentleftnode', other_entity: 'server', other_field: 'cn', z_index: 2 }, { $type: 'entity_select', name: 'iparepltoposegmentrightnode', other_entity: 'server', other_field: 'cn', z_index: 1 }, 'iparepltoposegmentdirection' ] }, { name: 'replication_config', label: '@i18n:objects.topology.replication_config', fields: [ { $type: 'radio', name: 'nsds5replicaenabled', options: ['on', 'off'], default_value: 'on', read_only: true }, 'nsds5replicatimeout', 'nsds5replicastripattrs', 'nsds5replicatedattributelist', 'nsds5replicatedattributelisttotal' ] } ] } ], adder_dialog: { fields: [ { name: 'cn', required: false }, { $type: 'entity_select', name: 'iparepltoposegmentleftnode', other_entity: 'server', other_field: 'cn', z_index: 2 }, { $type: 'entity_select', name: 'iparepltoposegmentrightnode', other_entity: 'server', other_field: 'cn', z_index: 1 } ] } };}; var make_server_spec = function() { return { name: 'server', policies: [ { $factory: IPA.facet_update_policy, source_facet: 'details', dest_entity: 'location', dest_facet: 'details' } ], facets: [ { $type: 'search', $factory: topology.servers_search_facet, no_update: true, disable_facet_tabs: false, tabs_in_sidebar: true, tab_label: '@mo:server.label', always_request_members: true, facet_groups: [topology.search_facet_group], facet_group: 'search', columns: [ 'cn', 'ipamindomainlevel', 'ipamaxdomainlevel', 'iparepltopomanagedsuffix_topologysuffix' ] }, { $type: 'details', $factory: topology.servers_facet, disable_facet_tabs: true, sections: [ { name: 'details', fields: [ { name: 'cn', read_only: true }, { name: 'ipamindomainlevel', read_only: true }, { name: 'ipamaxdomainlevel', read_only: true }, { $type: 'multivalued', name: 'iparepltopomanagedsuffix_topologysuffix', read_only: true }, { $type: 'entity_select', name: 'ipalocation_location', other_entity: 'location', other_field: 'idnsname', flags: ['w_if_no_aci'] }, { name: 'ipaserviceweight', placeholder: '100', flags: ['w_if_no_aci'] }, { $type: 'association_table', other_entity: 'server_role', name: 'enabled_role_servrole', footer: false, read_only: true, selectable: false } ] } ], actions: [ { $factory: IPA.delete_action, name: 'server_del', method: 'del', label: '@i18n:objects.servers.remove_server', needs_confirm: true, confirm_msg: '@i18n:objects.servers.remove_server_msg', confirm_dialog: { $factory: IPA.confirm_dialog, title: '@i18n:objects.servers.remove_server', ok_label: '@i18n:buttons.remove', ok_button_class: 'btn btn-danger' } } ], control_buttons_right: [ { name: 'server_del', label: '@i18n:objects.servers.remove_server', icon: 'fa-exclamation-circle', button_class: 'btn btn-danger' } ] } ] };}; var make_serverroles_spec = function() { return { name: 'server_role', facet_groups: [ 'role_servers' ], facets: [ { $factory: topology.serverroles_search_facet, $type: 'search', primary_key_name: 'role_servrole', search_all_entries: true, disable_search_field: true, no_update: true, selectable: false, disable_facet_tabs: false, tabs_in_sidebar: true, tab_label: '@i18n:objects.server_role.label', label: '@i18n:objects.server_role.label', facet_groups: [topology.search_facet_group], facet_group: 'search', columns: [ 'role_servrole', 'status' ] }, { $factory: topology.serverroles_nested_search_facet, $type: 'nested_search', primary_key_name: 'server_server', search_all_entries: true, disable_search_field: true, no_update: true, selectable: false, nested_entity: 'server_role', facet_group: 'role_servers', label: '@i18n:objects.server_role.label_singular', columns: [ { name: 'server_server', target_entity: 'server', target_facet: 'details', link: true }, 'status' ] } ] };}; var make_domainlevel_spec = function() { return { name: 'domainlevel', facets: [ { $type: 'details', get_all_attrs: false, check_rights: false, no_update: true, refresh_command_name: 'get', disable_facet_tabs: false, tabs_in_sidebar: true, tab_label: '@mo:domainlevel.label', facet_groups: [topology.search_facet_group], facet_group: 'search', sections: [ { name: 'details', fields: [ { name: 'ipadomainlevel', read_only: true, adapter: topology.domainlevel_adapter } ] } ], actions: ['domainlevel_set'], control_buttons: [{ label: '@i18n:objects.domainlevel.set', name: 'domainlevel_set' }] } ] };}; topology.domainlevel_adapter = declare([mod_field.Adapter], { load: function(data) { return [this.get_record(data)]; } }); var make_location_spec = function() { return { name: 'location', policies: [ { $factory: IPA.facet_update_policy, source_facet: 'details', dest_entity: 'server', dest_facet: 'details' }, { $factory: IPA.facet_update_policy, source_facet: 'details', dest_entity: 'location', dest_facet: 'search' } ], facets: [ { $type: 'search', disable_facet_tabs: false, tabs_in_sidebar: true, tab_label: '@mo:location.label', facet_groups: [topology.search_facet_group], facet_group: 'search', columns: [ 'idnsname', 'description' ] }, { $type: 'details', disable_facet_tabs: true, section_layout_class: 'col-sm-12', sections: [ { name: 'general', label: '@i18n:details.general', fields: [ 'idnsname', { $type: 'textarea', name: 'description' } ] }, { $factory: IPA.section, name: 'servers', label: '@mo:server.label', fields: [ { $type: 'location_association_table', adapter: topology.location_adapter, other_entity: 'server', footer: false, name: 'cn', columns: [ { name: 'cn', link: true }, { name: 'ipaserviceweight' }, { name: 'service_relative_weight' } ] } ] } ] } ], adder_dialog: { fields: [ { $type: 'text', name: 'idnsname', required: true }, 'description' ] } };}; topology.location_adapter = declare([mod_field.Adapter], { load: function(data) { var result = this.get_record(data); if (result.servers_server === undefined) return []; result = result.servers_server; this.result_name = 'servers'; var servers = this.get_record(data); this.result_name = 'result'; var output = []; for (var i=0, l=result.length; i'; if (ca_counter === 1) { message += '
  • CA
  • \n'; } if (kra_counter === 1) { message += '
  • KRA
  • \n'; } message += ''; var dialog = IPA.dialog({ name: 'svc_warning', title: '@i18n:objects.servers.svc_warning_title', sections: [ { show_header: false, layout: { $factory: widget_mod.fluid_layout, widget_cls: "col-sm-12 controls", label_cls: "hide" }, fields: [ { field: false, $type: 'html', html: message } ] } ] }); dialog.create_button({ name: 'ok', label: '@i18n:buttons.ok', click: function() { dialog.close(); } }); dialog.open(); }; var batch = that.table_facet_create_get_records_command(pkeys, on_success_extended, on_error); return batch; }; return that; }; topology.servers_facet = function(spec, no_init) { spec = spec || {}; var that = IPA.details_facet(spec, no_init); /** * Creates buttons on the right side of facet. */ that.create_controls = function() { that.create_control_buttons(that.controls_left); that.create_action_dropdown(that.controls_left); that.control_buttons = that.control_buttons_right; that.create_control_buttons(that.controls_right); }; /** * Inits right facet buttons. */ that.init_servers_facet = function() { that.init_details_facet(); var buttons_spec = { $factory: IPA.control_buttons_widget, name: 'control-buttons', css_class: 'control-buttons', buttons: spec.control_buttons_right }; that.control_buttons_right = IPA.build(buttons_spec); that.control_buttons_right.init(that); }; if (!no_init) that.init_servers_facet(); return that; }; topology.serverroles_search_facet = function(spec) { spec = spec || {}; var that = IPA.search_facet(spec); that.filter_records = function(records_map, pkey, record) { var stored_record = records_map.get(pkey); if (!stored_record) return true; // set priority to all possible values. Value with higher priority // will overwrite a value with lower prio. var priority_map = { absent: 0, configured: 1, enabled: 2 }; var priority_old = priority_map[stored_record.status]; var priority_new = priority_map[record.status]; return priority_new >= priority_old; }; that.get_refresh_command_args = function() { // We need to call find command with no argument. return; }; return that; }; topology.serverroles_nested_search_facet = function(spec) { spec = spec || {}; spec.disable_facet_tabs = true; var that = IPA.nested_search_facet(spec); that.get_refresh_command_args = function() { // We need to call find command with no argument. return; }; that.get_refresh_command_options = function() { return { 'role_servrole': that.get_pkey() }; }; that.on_column_link_click = function(value, entity) { var pkeys = [value]; navigation.show_entity('server', that.details_facet_name, pkeys); return false; }; that.filter_records = function(records_map, pkey, record) { // Return false when the status is 'absent' return record.status !== 'absent'; }; return that; }; topology.location_server_adder_dialog = function(spec) { spec = spec || {}; var that = IPA.entity_adder_dialog(spec); that.init = function() { that.added.attach(function() { that.facet.refresh(); that.facet.on_update.notify(); }); }; that.create_add_command = function(record) { var pkey = that.facet.get_pkey(); var command = that.entity_adder_dialog_create_add_command(record); command.set_option('ipalocation_location', pkey); return command; }; that.init(); return that; }; topology.location_association_table_widget = function(spec) { spec = spec || {}; spec.name = spec.name || 'servers'; var that = IPA.association_table_widget(spec); that.create_add_dialog = function() { var entity_label = that.entity.metadata.label_singular; var pkey = that.facet.get_pkey(); var other_entity_label = that.other_entity.metadata.label_singular; var title = that.add_title; title = title.replace('${entity}', entity_label); title = title.replace('${primary_key}', pkey); title = title.replace('${other_entity}', other_entity_label); return topology.location_server_adder_dialog({ title: title, entity: that.other_entity.name, method: 'mod', options: { 'ipalocation_location': pkey }, sections: [ { fields: [ { $type: 'entity_select', name: 'cn', required: true, other_entity: that.other_entity.name, other_field: 'cn', filter_options: { 'not_in_location': pkey } }, { $type: 'text', name: 'ipaserviceweight' } ] } ] }); }; that.remove = function(values, on_success, on_error) { var pkey = ''; var batch = rpc.batch_command({ on_success: function(data) { that.remove_button.set_enabled(false); that.facet.refresh(); var count = data.result.count; var msg = text.get('@i18n:association.removed').replace('${count}', count); IPA.notify_success(msg); }, on_error: on_error }); var command, value; for(var i = 0, l=values.length; i= topology.required_domain_level) { that.state.push('managed-topology'); } that.notify_on_change(old_state); }; return that; }; topology.topology_graph_facet_spec = { name: 'topology-graph', 'class': 'topology-graph container-fluid', label: 'Topology Graph', tab_label: 'Topology Graph', facet_groups: [topology.search_facet_group], facet_group: 'search', actions: ['refresh', 'segment_add', 'segment_del'], control_buttons: [ { name: 'refresh', label: '@i18n:buttons.refresh', icon: 'fa-refresh' }, { name: 'segment_add', label: '@i18n:buttons.add', icon: 'fa-plus' }, { name: 'segment_del', label: '@i18n:buttons.remove', icon: 'fa-trash-o' } ], widgets: [ { $type: 'activity', name: 'activity', text: 'Working', visible: false }, { $type: 'topology-graph', name: 'topology-graph' } ], state: { evaluators: [ topology.managed_topology_evaluator ] } }; /** * Facet containing topology graph * * @class */ topology.TopologyGraphFacet = declare([Facet, ActionMixin, HeaderMixin], { init: function(spec) { this.inherited(arguments); var graph = this.get_widget('topology-graph'); var listener = this.resize_listener.bind(this, graph); on(this, 'show', function(args) { var size = this.calculate_canvas_size(); graph.update(size); $(window).on('resize', null, listener); }.bind(this)); on(graph, 'link-selected', function(data) { this.set_selected_link(data.link); }.bind(this)); on(graph, 'add-agreement', function(link) { var dialog = topology.create_add_dialog(); dialog.open(); dialog.prefill_dialog(link); }); on(this, 'hide', function () { $(window).off('resize', null, listener); }); }, resize_listener: function(graph) { var size = this.calculate_canvas_size(); graph.resize(size.height, size.width); }, calculate_canvas_size: function() { var space = parseInt(this.content.css('paddingLeft'), 10); var height = $(window).height() - this.content.offset().top - space; var width = this.content.width(); return { height: height, width: width }; }, set_selected_link: function(link) { this.selected_link = link; this.action_state.put('selected_link', link ? ['link_selected'] : []); }, refresh: function() { var graph = this.get_widget('topology-graph'); var size = this.calculate_canvas_size(); graph.update(size); } }); /** * Creates segment adder dialog according to spec. * * @param spec {Object} */ topology.create_add_dialog = function(spec) { spec = spec || {}; spec.entity = spec.entity || 'topologysegment'; var entity = reg.entity.get('topologysegment'); var title = text.get('@i18n:dialogs.add_title'); var label = entity.metadata.label_singular; spec.title = title.replace('${entity}', label); spec.fields = spec.fields || [ { name: 'cn', placeholder: '@i18n:objects.topology.autogenerated', required: false }, { $type: 'entity_select', name: 'suffix', label: '@mo:topologysuffix.label_singular', other_entity: 'topologysuffix', other_field: 'cn', z_index: 3, required: true }, { $type: 'entity_select', name: 'iparepltoposegmentleftnode', other_entity: 'server', other_field: 'cn', z_index: 2 }, { $type: 'entity_select', name: 'iparepltoposegmentrightnode', other_entity: 'server', other_field: 'cn', z_index: 1 } ]; var that = IPA.entity_adder_dialog(spec); that.init = function() { that.added.attach(that.on_success); }; that.show_edit_page = function(entity, result) { var suffix = this.fields.get_field('suffix').save()[0]; var cn = result.cn[0]; navigation.show_entity(entity.name, 'default', [suffix, cn]); }; that.create_add_command = function(record) { var args = [this.fields.get_field('suffix').save()[0]]; var cn = this.fields.get_field('cn').save()[0]; if (cn) args.push(cn); var options = { 'iparepltoposegmentleftnode': this.fields.get_field('iparepltoposegmentleftnode').save()[0], 'iparepltoposegmentrightnode': this.fields.get_field('iparepltoposegmentrightnode').save()[0] }; var command = rpc.command({ entity: this.entity.name, method: this.method, retry: this.retry, args: args, options: options }); return command; }; that.on_success = function(data) { that.facet.refresh(); }; that.prefill_dialog = function(link) { that.get_field('iparepltoposegmentleftnode').set_value([link.source.id]); that.get_field('iparepltoposegmentrightnode').set_value([link.target.id]); that.get_field('suffix').set_value([link.suffix]); }; that.init(); return that; }; /** * Shows topology segment adder dialog with suffix select * * @class topology.add_segment_action * @extends IPA.action */ topology.add_segment_action = function(spec) { spec = spec || {}; spec.name = spec.name || 'segment_add'; spec.method = spec.method || 'add'; spec.enable_cond = spec.enable_cond || ['managed-topology']; var that = IPA.action(spec); that.execute_action = function(facet, on_success, on_error) { var dialog = topology.create_add_dialog(); dialog.open(); }; return that; }; /** * Deletes selected segment * * Facet must have 'selected_link' option. * * Facet should reflect the selection in action state: "link_selected" * * @class topology.del_segment_action * @extends IPA.delete_action */ topology.del_segment_action = function(spec) { spec = spec || {}; spec.name = spec.name || 'segment_del'; spec.enable_cond = spec.enable_cond || ['link_selected']; var that = IPA.delete_action(spec); that.execute_action = function(facet, on_success, on_error) { that.facet = facet; var args = [ facet.selected_link.suffix.cn[0], facet.selected_link.segment.cn[0] ]; rpc.command({ entity: 'topologysegment', method: that.method, args: args, options: that.options, on_success: that.get_on_success(facet, on_success), on_error: that.get_on_error(facet, on_error) }).execute(); }; that.get_confirm_message = function(facet) { var pkey = facet.selected_link.segment.cn[0]; var msg = that.confirm_msg.replace('${object}', pkey); return msg; }; that.on_success = function(facet, data, text_status, xhr) { IPA.notify_success(data.result.summary); that.facet.refresh(); that.facet.set_selected_link(null); }; that.on_error = function(facet, xhr, text_status, error_thrown) { }; return that; }; /** * Graph widget encapsulates and supply data to graph component * * Graph is show only with domain level 1. * * @class */ topology.TopologyGraphWidget = declare([Stateful, Evented], { graph: null, // nodes container_node: null, el: null, disabled_view_el: null, topology_view_el: null, current_view_el: null, visualization_cnt_el: null, _get_servers: function() { var deferred = new Deferred(); var s_promise = rpc.command({ entity: 'server', method: 'find', options: { sizelimit: 0 } }).execute(); when(s_promise, function(results) { // suffixes load success var servers = results.data.result.result; deferred.resolve(servers); }.bind(this), function(results) { deferred.reject({ message: 'unable to load servers', results: results }); }); return deferred.promise; }, _get_suffixes: function() { var deferred = new Deferred(); function get_suffixes() { return rpc.command({ entity: 'topologysuffix', method: 'find', options: { sizelimit: 0 } }).execute(); } function get_segments(suffix_name) { return rpc.command({ entity: 'topologysegment', method: 'find', args: [suffix_name], options: { sizelimit: 0 } }).execute(); } var suff_promise = get_suffixes(); when(suff_promise, function(results) { // suffixes load success var suffixes = results.data.result.result; var segment_promises = []; for (var i=0,l=suffixes.length; i b.suffix.cn[0]; }); } for (i=0,l=servers.length; i', { 'class': this.css_class }); this._update_view(); if (this.container_node) { this.el.appendTo(this.container_node); } return this.el; }, _render_disabled_view: function() { if (this.disabled_view_el) return this.disabled_view_el; this.disabled_view_el = $('
    ', { 'class': 'disabled-view' }); var msg = text.get('@i18n:objects.topology.insufficient_domain_level'); msg = msg.replace('${domainlevel}', topology.required_domain_level); $('
    ') .append( $('

    ', { text: msg }) ) .appendTo(this.disabled_view_el); return this.disabled_view_el; }, _render_topology_view: function() { if (this.topology_view_el) return this.topology_view_el; this.topology_view_el = $('

    ', { 'class': 'topology-view' }); this.visualization_cnt_el = $('
    ', { 'class': 'visualization' }). appendTo(this.topology_view_el); return this.topology_view_el; }, resize: function(height, width) { if (this.graph) { this.graph.resize(height, width); } }, _init_widgets: function() { }, constructor: function(spec) { lang.mixin(this, spec); this._init_widgets(); } }); /** * Topology suffix entity specification object * @member topology */ topology.suffix_spec = make_suffix_spec(); /** * Topology segment entity specification object * @member topology */ topology.segment_spec = make_segment_spec(); /** * IPA server entity specification object * @member topology */ topology.server_spec = make_server_spec(); /** * IPA server roles entity specification object * @member topology */ topology.serverroles_spec = make_serverroles_spec(); /** * Domain Level entity specification object * @member topology */ topology.domainlevel_spec = make_domainlevel_spec(); topology.location_spec = make_location_spec(); /** * Register entity * @member topology */ topology.register = function() { var e = reg.entity; var a = reg.action; var fa = reg.facet; var w = reg.widget; e.register({type: 'topologysuffix', spec: topology.suffix_spec}); e.register({type: 'topologysegment', spec: topology.segment_spec}); e.register({type: 'server', spec: topology.server_spec}); e.register({type: 'server_role', spec: topology.serverroles_spec}); e.register({type: 'domainlevel', spec: topology.domainlevel_spec}); e.register({type: 'location', spec: topology.location_spec}); a.register('domainlevel_set', topology.domainlevel_set_action); a.register('segment_add', topology.add_segment_action); a.register('segment_del', topology.del_segment_action); w.register('topology-graph', topology.TopologyGraphWidget); w.register('location_association_table', topology.location_association_table_widget); fa.register({ type: 'topology-graph', ctor: topology.TopologyGraphFacet, spec: topology.topology_graph_facet_spec }); }; phases.on('registration', topology.register); phases.on('post-metadata', topology.domainlevel_metadata); return topology; });