Project

General

Profile

« Previous | Next » 

Revision 7f25f394

Added by Leos Stejskal about 3 years ago

Fixes #31240 - New Host Registration Form (#8419)

Completely in React, with router & PF4 components
Divided into two tabs: General fields & Advanced Fields
Slots for extending form fields (general / advanced)
Form can be submitted only if there are no invalid fields

Related plugin extensions:
Katello/katello#9249
theforeman/foreman_remote_execution#574

Changes:
  • Organization & Location can be changed in the Form
    changing ORG/LOC will fetch new data and reset form fields (HostGroup, OS & Smart Proxy)
    Reset of fields from plugins is handled by plugins.
  • Host group
    If selected, following fields inherit the value from selected host group: Operating System, Activation Keys & Life Cycle Environment
    Selecting HG resets OS field
  • Operating System
    When selected, show info about assigned host_init_config template.
    If template is not found, show error and block form
Setup Remote Execution & Setup Insights
  • Yes/No value in default options Inherit from Host parameter is delivered from selected organization, location host group and operating system. The chain of getting value is:
    organization -> location -> operatingsystem -> hostgroup
  • Packages (new field)
    Install packages after the host registration
  • Repository & Repository GPG (new fields)
  • Token Life Time
    Can be set to unlimited, otherwise value validated as: >= 1 && <= 999 999

View differences:

app/controllers/api/v2/registration_controller.rb
prepare_host
host_setup_insights
host_setup_remote_execution
host_setup_packages
host_setup_extension
@template = @host.initial_configuration_template
raise ActiveRecord::Rollback if @template.nil?
app/controllers/concerns/foreman/controller/registration.rb
registration_url: registration_url,
setup_insights: ActiveRecord::Type::Boolean.new.deserialize(params['setup_insights']),
setup_remote_execution: ActiveRecord::Type::Boolean.new.deserialize(params['setup_remote_execution']),
packages: params['packages'],
repo: params['repo'],
repo_gpg_key_url: params['repo_gpg_key_url'],
}
......
rex_param.save!
end
def host_setup_packages
return if params['packages'].to_s.blank?
insights_param = HostParameter.find_or_initialize_by(host: @host, name: 'host_packages', key_type: 'string')
insights_param.value = params['packages']
insights_param.save!
end
def api_authorization_token
scope = [{
controller: :registration,
app/controllers/concerns/foreman/controller/registration_commands.rb
def command
args_query = "?#{registration_args.to_query}"
"curl #{insecure} -s '#{endpoint}#{args_query if args_query != '?'}' #{command_headers} | bash"
"curl#{insecure} -s '#{endpoint}#{args_query if args_query != '?'}' #{command_headers} | bash"
end
def registration_args
......
end
def insecure
registration_params['insecure'] ? '--insecure' : ''
registration_params['insecure'] ? ' --insecure' : ''
end
def endpoint
......
end
def command_headers
hours = (registration_params['jwt_expiration'].presence || 4).to_i.hours.to_i
scope = [{ controller: :registration, actions: [:global, :host] }]
jwt = User.current.jwt_token!(expiration: hours, scope: scope)
jwt_args = {
scope: [{ controller: :registration, actions: [:global, :host] }],
}
"-H 'Authorization: Bearer #{jwt}'"
if registration_params['jwt_expiration'].present?
jwt_args[:expiration] = registration_params['jwt_expiration'].to_i.hours.to_i if registration_params['jwt_expiration'] != 'unlimited'
else
jwt_args[:expiration] = 4.hours.to_i
end
"-H 'Authorization: Bearer #{User.current.jwt_token!(**jwt_args)}'"
end
def host_config_params
organization = User.current.my_organizations.find(registration_params['organization_id']) if registration_params['organization_id'].present?
location = User.current.my_locations.find(registration_params['location_id']) if registration_params['location_id'].present?
host_group = Hostgroup.authorized(:view_hostgroups).find(registration_params['hostgroup_id']) if registration_params["hostgroup_id"].present?
operatingsystem = Operatingsystem.authorized(:view_operatingsystems).find(registration_params['operatingsystem_id']) if registration_params["operatingsystem_id"].present?
Host.new(organization: organization, location: location, hostgroup: host_group, operatingsystem: operatingsystem).params
end
end
app/controllers/registration_commands_controller.rb
class RegistrationCommandsController < ApplicationController
include Foreman::Controller::RegistrationCommands
def new
form_options
def form_data
render json: {
organizations: User.current.my_organizations.select(:id, :name),
locations: User.current.my_locations.select(:id, :name),
hostGroups: Hostgroup.authorized(:view_hostgroups).includes([:operatingsystem]),
operatingSystems: Operatingsystem.authorized(:view_operatingsystems),
smartProxies: Feature.find_by(name: 'Registration')&.smart_proxies,
configParams: host_config_params,
pluginData: plugin_data,
}
end
def operatingsystem_template
os = Operatingsystem.authorized(:view_operatingsystems).find(params[:id])
template_kind = TemplateKind.find_by(name: 'host_init_config')
template = os.os_default_templates
.find_by(template_kind: template_kind)&.provisioning_template
if template
render json: { template: { name: template.name, path: edit_provisioning_template_path(template) } }
else
render json: { template: { name: nil, os_path: edit_operatingsystem_path(os)} }
end
end
def create
form_options
@command = command
render json: { command: command }
end
private
def form_options
@host_groups = Hostgroup.authorized(:view_hostgroups).select(:id, :name)
@operating_systems = Operatingsystem.authorized(:view_operatingsystems).select(:id, :title)
@smart_proxies = Feature.find_by(name: 'Registration')&.smart_proxies || []
end
def ignored_query_args
['utf8', 'authenticity_token', 'commit', 'action', 'locale', 'controller', 'jwt_expiration', 'smart_proxy_id', 'insecure']
end
def registration_params
params
if params['registration_command']
params['registration_command'].transform_keys(&:underscore)
else
params
end
end
# Extension point for plugins
def plugin_data
{}
end
end
app/registries/foreman/access_permissions.rb
:puppetclasses => pc_ajax_actions,
:subnets => subnets_ajax_actions,
:interfaces => [:new, :random_name],
:registration_commands => [:new, :create],
:registration_commands => [:form_data, :operatingsystem_template, :create],
:"api/v2/hosts" => [:create],
:"api/v2/interfaces" => [:create],
:"api/v2/tasks" => [:index],
app/registries/menu/loader.rb
menu.item :hosts, :caption => N_('All Hosts')
menu.item :newhost, :caption => N_('Create Host'),
:url_hash => {:controller => '/hosts', :action => 'new'}
menu.item :register_hosts, :caption => N_('Register Host')
menu.item :register_hosts, :caption => N_('Register Host'),
:url => '/hosts/register',
:url_hash => { :controller => 'hosts', :action => 'create' }
if SETTINGS[:unattended]
menu.divider :caption => N_('Provisioning Setup')
menu.item :architectures, :caption => N_('Architectures')
app/views/hosts/index.html.erb
<%
register_btn = display_link_if_authorized(_("Register Host"), hash_for_register_hosts_path, :class => "btn btn-default")
register_btn = link_to(_("Register Host"), hash_for_register_hosts_path, :class => "btn btn-default") if authorized_for({contoller: 'hosts', action: 'create'})
%>
<% title_actions multiple_actions_select, csv_link, register_btn, button_group(new_link(_("Create Host"))) %>
app/views/registration_commands/_form.erb
<% title _('Register Host') %>
<%
options = [[_("Inherit from parameter"), ""], [_('Yes (enforce)'), 'true'], [_('No (enforce)'), 'false']]
%>
<%= form_tag({controller: 'registration_commands', action: 'create' }, method: :post, class: 'form-horizontal well') do %>
<div class='form-group'>
<label class='col-md-2 control-label'><%= _('Organization') %></label>
<div class='col-md-4'>
<p class="form-control-static">
<%= Organization.current&.name || _('No Organization selected') %>
<%= hidden_field_tag 'organization_id', Organization.current&.id %>
</p>
</div>
</div>
<div class='form-group'>
<label class='col-md-2 control-label'><%= _('Location') %></label>
<div class='col-md-4'>
<p class="form-control-static">
<%= Location.current&.name || _('No Location selected') %>
<%= hidden_field_tag 'location_id', Location.current&.id %>
</p>
</div>
</div>
<div class='form-group'>
<label class='col-md-2 control-label' for='hostgroup_id'>
<%= _('Host Group') %>
</label>
<div class='col-md-4'>
<%= select_tag 'hostgroup_id', options_from_collection_for_select(@host_groups, :id, :name, params[:hostgroup_id]), class: 'form-control', include_blank: '' %>
</div>
</div>
<div class='form-group'>
<label class='col-md-2 control-label' for='operatingsystem_id'>
<%= _('Operating System') %>
</label>
<div class='col-md-4'>
<%= select_tag 'operatingsystem_id', options_from_collection_for_select(@operating_systems, :id, :title, params[:operatingsystem_id]), class: 'form-control', include_blank: '' %>
</div>
</div>
<div class='form-group'>
<label class='col-md-2 control-label' for='smart_proxy'>
<%= _('Proxy') %>
<% help = _('Only Smart Proxies with enabled Registration module are available') %>
<a rel="popover" data-content="<%= help %>" data-trigger="focus" data-container="body" data-html="true" tabindex="-1">
<span class="pficon pficon-info"></span>
</a>
</label>
<div class='col-md-4'>
<%= select_tag 'smart_proxy_id', options_from_collection_for_select(@smart_proxies, :id, :name, params[:smart_proxy_id]), class: 'form-control', include_blank: '' %>
</div>
</div>
<div class='form-group'>
<label class='col-md-2 control-label' for='setup_insights'>
<% help = _('If set to "Yes", Insights client will be installed and registered on Red Hat family operating systems. It has no effect on other OS families that do not support it. The inherited value is based on the `host_registration_insights` parameter. It can be inherited e.g. from host group, operating system, organization. When overidden, the selected value will be stored on host parameter level.') %>
<%= _('Setup Insights') %>
<a rel="popover" data-content="<%= help %>" data-trigger="focus" data-container="body" data-html="true" tabindex="-1">
<span class="pficon pficon-info "></span>
</a>
</label>
<div class='col-md-4'>
<%= select_tag 'setup_insights', options_for_select(options, params[:setup_insights]), class: 'form-control' %>
</div>
</div>
<div class='form-group'>
<label class='col-md-2 control-label' for='setup_remote_execution'>
<%= _('Remote Execution') %>
<% help = _('If set to "Yes", SSH keys will be installed on the registered host. The inherited value is based on the `host_registration_remote_execution` parameter. It can be inherited e.g. from host group, operating system, organization.. When overidden, the selected value will be stored on host parameter level.') %>
<a rel="popover" data-content="<%= help %>" data-trigger="focus" data-container="body" data-html="true" tabindex="-1">
<span class="pficon pficon-info "></span>
</a>
</label>
<div class='col-md-4'>
<%= select_tag 'setup_remote_execution', options_for_select(options, params[:setup_remote_execution]), class: 'form-control' %>
</div>
</div>
<div class='form-group'>
<label class='col-md-2 control-label'>
<%= _('Token lifetime (hours)') %>
<% help = _('Expiration of the authorization token.') %>
<a rel="popover" data-content="<%= help %>" data-trigger="focus" data-container="body" data-html="true" tabindex="-1">
<span class="pficon pficon-info "></span>
</a>
</label>
<div class='col-md-4'>
<%= number_field_tag 'jwt_expiration', params[:jwt_expiration] || 4, class: 'form-control', min: 1, required: true %>
</div>
</div>
<div class='form-group'>
<label class='col-md-2 control-label'>
<%= _('Insecure') %>
<% help = _('If the target machine does not trust the Foreman SSL certificate, the initial connection could be subject to Man in the middle attack. If you accept the risk and do not require the server authenticity to be verified, you can enable insecure argument for the initial curl. Note that all subsequent communication is then properly secured, because the initial request deploys the SSL certificate for the rest of the registration process.') %>
<a rel="popover" data-content="<%= help %>" data-trigger="focus" data-container="body" data-html="true" tabindex="-1">
<span class="pficon pficon-info "></span>
</a>
</label>
<div class='col-md-4'>
<%= check_box_tag 'insecure', '', params[:insecure] %>
</div>
</div>
<% pagelets_for(:global_registration).each do |pagelet| %>
<%= render_pagelet(pagelet) %>
<% end %>
<div class='form-group '>
<%= submit_tag _('Generate command'), class: 'btn btn-primary' %>
<%= link_to _('Cancel'), hosts_path, class: 'btn btn-default' %>
</div>
<% if action_name == 'create' %>
<div class='form-group '>
<label class='col-md-2 control-label'><%= _('Command') %></label>
<div class='col-md-8'>
<pre class='ellipsis white-space-normal' id='registration_command'><%= @command %></pre>
<a class='btn btn-default' onclick="tfm.hosts.copyRegistrationCommand();" ><%= _('Copy to clipboard') %></a>
</div>
</div>
<% end %>
<% end %>
app/views/registration_commands/create.html.erb
<%= render 'form' %>
app/views/registration_commands/new.html.erb
<%= render 'form' %>
app/views/unattended/provisioning_templates/host_init_config/host_init_config_default.erb
<% end -%>
curl --silent <%= '--cacert $SSL_CA_CERT' if built_https -%> -o /dev/null --noproxy \* '<%= foreman_url('built') %>'
echo "Successfully enrolled host <%= @host.name %> with Foreman."
echo "#"
echo "# Host [<%= @host.name %>] successfully enrolled."
echo "#"
exit 0
app/views/unattended/provisioning_templates/registration/global_registration.erb
#!/bin/sh
<%
headers = ["-H 'Authorization: Bearer #{@auth_token}'"]
activation_keys = [(@hostgroup.params['kt_activation_keys'] if @hostgroup), @activation_keys].compact.join(',')
-%>
# Rendered with following template parameters:
......
<%= "\n# Setup Insights: [#{@setup_insights}]" unless @setup_insights.nil? -%>
<%= "\n# Setup Remote Execution: [#{@setup_remote_execution}]" unless @setup_remote_execution.nil? -%>
<%= "\n# Remote execution interface: [#{@remote_execution_interface}]" if @remote_execution_interface.present? -%>
<%= "\n# Packages: [#{@packages}]" if @packages.present? -%>
<%= "\n# Activation keys: [#{activation_keys}]" if activation_keys.present? -%>
if ! [ $(id -u) = 0 ]; then
......
<%= " --data 'setup_insights=#{@setup_insights}' \\\n" unless @setup_insights.nil? -%>
<%= " --data 'setup_remote_execution=#{@setup_remote_execution}' \\\n" unless @setup_remote_execution.nil? -%>
<%= " --data 'remote_execution_interface=#{@remote_execution_interface}' \\\n" if @remote_execution_interface.present? -%>
<%= " --data 'packages=#{@packages}' \\\n" if @packages.present? -%>
}
......
<%= " --data 'host[organization_id]=#{@organization.id}' \\\n" if @organization -%>
<%= " --data 'host[location_id]=#{@location.id}' \\\n" if @location -%>
<%= " --data 'host[hostgroup_id]=#{@hostgroup.id}' \\\n" if @hostgroup -%>
<%= " --data 'host[lifecycle_environment_id]=#{@lifecycle_environment_id}' \\\n" if @lifecycle_environment_id.present? -%>
<%= " --data 'setup_insights=#{@setup_insights}' \\\n" unless @setup_insights.nil? -%>
<%= " --data 'setup_remote_execution=#{@setup_remote_execution}' \\\n" unless @setup_remote_execution.nil? -%>
<%= " --data 'remote_execution_interface=#{@remote_execution_interface}' \\\n" if @remote_execution_interface.present? -%>
<%= " --data 'packages=#{@packages}' \\\n" if @packages.present? -%>
}
<% if @force -%>
yum remove -y katello-ca-consumer*
<% end -%>
CONSUMER_RPM=$(mktemp --suffix .rpm)
curl --silent --output $CONSUMER_RPM <%= subscription_manager_configuration_url(hostname: @url_host) %>
......
rm -f $CONSUMER_RPM
subscription-manager register --org='<%= @organization.label %>' --activationkey='<%= @activation_key %>' || exit 1
subscription-manager register <%= '--force' if @force %> --org='<%= @organization.label %>' --activationkey='<%= activation_keys %>' || <%= @ignore_subman_errors ? 'true' : 'exit 1' %>
register_katello_host | bash
else
register_host | bash
config/routes.rb
get 'random_name', only: :new
get 'preview_host_collection'
get 'register', to: 'registration_commands#new'
get 'register' => 'react#index'
post 'register', to: 'registration_commands#create'
get 'register/data', to: 'registration_commands#form_data'
get 'register/os/:id', to: 'registration_commands#operatingsystem_template'
end
constraints(host_id: /[^\/]+/) do
test/controllers/api/v2/registration_commands_controller_test.rb
assert_response :success
response = ActiveSupport::JSON.decode(@response.body)['registration_command']
assert_includes response, "curl -s 'http://test.host/register'"
assert_includes response, "curl -s 'http://test.host/register'"
assert_includes response, "-H 'Authorization: Bearer"
end
......
operatingsystem_id: operatingsystems(:redhat).id,
setup_insights: 'false',
setup_remote_execution: 'false',
packages: 'pkg1',
}
post :create, params: params
......
assert_includes response, "operatingsystem_id=#{operatingsystems(:redhat).id}"
assert_includes response, 'setup_insights=false'
assert_includes response, 'setup_remote_execution=false'
assert_includes response, 'packages=pkg1'
end
test 'with params ignored in url' do
test 'with params ignored in URL' do
params = {
insecure: true,
jwt_expiration: 23,
test/controllers/api/v2/registration_controller_test.rb
refute HostParameter.find_by(host: host, name: 'host_registration_remote_execution').value
end
end
context 'packages' do
test 'without param' do
params = { packages: '' }.merge(host_params)
post :host, params: params, session: set_session_user
assert_response :success
host = Host.find_by(name: params[:host][:name]).reload
assert_nil HostParameter.find_by(host: host, name: 'host_packages')
end
test 'with param' do
params = { packages: 'pkg1 pkg2' }.merge(host_params)
post :host, params: params, session: set_session_user
assert_response :success
host = Host.find_by(name: params[:host][:name]).reload
assert HostParameter.find_by(host: host, name: 'host_packages').value
end
end
end
end
test/controllers/registration_commands_controller_test.rb
require 'test_helper'
class RegistrationCommandsControllerTest < ActionController::TestCase
test 'new' do
get :new, session: set_session_user
assert_response :success
assert_template :new
end
describe 'create' do
test 'create' do
params = { organization: taxonomies(:organization1).id, location: taxonomies(:location1).id }
test 'with params' do
params = {
organizationId: taxonomies(:organization1).id,
locationId: taxonomies(:location1).id,
hostgroupId: hostgroups(:common).id,
operatingsystemId: operatingsystems(:redhat).id,
}
post :create, params: params, session: set_session_user
command = JSON.parse(@response.body)['command']
assert_includes command, 'organizationId='
assert_includes command, 'locationId='
assert_includes command, 'hostgroupId='
assert_includes command, 'operatingsystemId='
end
test 'with params ignored in URL' do
params = {
smart_proxy_id: smart_proxies(:one).id,
insecure: true,
jwt_expiration: 23,
}
post :create, params: params, session: set_session_user
command = JSON.parse(@response.body)['command']
assert_includes command, "curl --insecure -s '#{smart_proxies(:one).url}/register"
refute command.include?('smart_proxy_id')
refute command.include?('insecure=true')
refute command.include?('jwt_expiration')
end
context 'host_params' do
let(:params) { { organization: taxonomies(:organization1).id, location: taxonomies(:location1).id } }
before do
CommonParameter.where(name: 'host_registration_insights').destroy_all
CommonParameter.where(name: 'setup_remote_execution').destroy_all
end
test 'inherit value' do
post :create, params: params, session: set_session_user
refute JSON.parse(@response.body)['command'].include?('setup_insights')
refute JSON.parse(@response.body)['command'].include?('setup_remote_execution')
end
test 'yes (override)' do
post :create, params: params.merge(setup_insights: true, setup_remote_execution: true), session: set_session_user
assert JSON.parse(@response.body)['command'].include?('setup_insights=true')
assert JSON.parse(@response.body)['command'].include?('setup_remote_execution=true')
end
test 'no (override)' do
post :create, params: params.merge(setup_insights: false, setup_remote_execution: false), session: set_session_user
assert JSON.parse(@response.body)['command'].include?('setup_insights=false')
assert JSON.parse(@response.body)['command'].include?('setup_remote_execution=false')
end
end
context 'jwt' do
test 'with default expiration' do
post :create, session: set_session_user
command = JSON.parse(@response.body)['command']
parsed_token = command.scan(/(?<=Bearer )(.*)(?=.*)(?=\')/).flatten[0]
assert JwtToken.new(parsed_token).decode['exp']
end
test 'with expiration' do
post :create, params: { jwt_expiration: 23 }, session: set_session_user
command = JSON.parse(@response.body)['command']
parsed_token = command.scan(/(?<=Bearer )(.*)(?=.*)(?=\')/).flatten[0]
assert JwtToken.new(parsed_token).decode['exp']
end
test 'unlimited' do
post :create, params: { jwt_expiration: 'unlimited' }, session: set_session_user
command = JSON.parse(@response.body)['command']
parsed_token = command.scan(/(?<=Bearer )(.*)(?=.*)(?=\')/).flatten[0]
assert_response :success
assert_template :create
refute JwtToken.new(parsed_token).decode['exp']
end
end
end
end
webpack/assets/javascripts/react_app/components/common/LabelIcon/LabelIcon.test.js
import { testComponentSnapshotsWithFixtures } from '../../../common/testHelpers';
import LabelIcon from './index';
describe('LabelIcon', () => {
testComponentSnapshotsWithFixtures(LabelIcon, { 'renders': {text: 'Yay, label help!'} });
})
webpack/assets/javascripts/react_app/components/common/LabelIcon/__snapshots__/LabelIcon.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LabelIcon renders 1`] = `
<Popover
bodyContent="Yay, label help!"
>
<button
className="pf-c-form__group-label-help"
onClick={[Function]}
>
<HelpIcon
color="currentColor"
noVerticalAlign={true}
size="sm"
/>
</button>
</Popover>
`;
webpack/assets/javascripts/react_app/components/common/LabelIcon/index.js
import React from 'react';
import PropTypes from 'prop-types';
import { Popover } from '@patternfly/react-core';
import { HelpIcon } from '@patternfly/react-icons';
const LabelIcon = ({ text }) => (
<Popover bodyContent={text}>
<button
className="pf-c-form__group-label-help"
onClick={e => e.preventDefault()}
>
<HelpIcon noVerticalAlign />
</button>
</Popover>
);
LabelIcon.propTypes = {
text: PropTypes.string.isRequired,
};
export default LabelIcon;
webpack/assets/javascripts/react_app/components/componentRegistry.js
import ClipboardCopy from './common/ClipboardCopy';
import MemoryAllocationInput from './MemoryAllocationInput';
import CPUCoresInput from './CPUCoresInput';
import LabelIcon from './common/LabelIcon';
const componentRegistry = {
registry: forceSingleton('component_registry', () => ({})),
......
{ name: 'SettingUpdateModal', type: SettingUpdateModal },
{ name: 'PersonalAccessTokens', type: PersonalAccessTokens },
{ name: 'ClipboardCopy', type: ClipboardCopy },
{ name: 'LabelIcon', type: LabelIcon },
{
name: 'MemoryAllocationInput',
type: MemoryAllocationInput,
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPage.scss
.registration_commands_form {
input[type='checkbox'] {
margin: auto;
}
}
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageActions.js
import { foremanUrl } from '../../../../foreman_tools';
import { get, post } from '../../../redux/API';
import {
REGISTRATION_COMMANDS_DATA,
REGISTRATION_COMMANDS_OS_TEMPLATE,
REGISTRATION_COMMANDS,
} from '../constants';
export const dataAction = params =>
get({
key: REGISTRATION_COMMANDS_DATA,
url: foremanUrl('/hosts/register/data'),
params,
});
export const operatingSystemTemplateAction = operatingSystemId =>
get({
key: REGISTRATION_COMMANDS_OS_TEMPLATE,
url: foremanUrl(`/hosts/register/os/${operatingSystemId}`),
});
export const commandAction = params =>
post({
key: REGISTRATION_COMMANDS,
url: foremanUrl('/hosts/register'),
params,
});
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageHelpers.js
/* eslint-disable camelcase */
import React from 'react';
import { FormSelectOption } from '@patternfly/react-core';
import { foremanUrl } from '../../../../foreman_tools';
import { sprintf, translate as __ } from '../../../common/I18n';
// Form helpers
export const emptyOption = length => (
<FormSelectOption
value=""
label={length > 0 ? '' : __('Nothing to select.')}
/>
);
// OperatingSystem helpers
export const validatedOS = (operatingSystemId, template) => {
if (!operatingSystemId) {
return 'default';
}
if (template?.name) {
return 'success';
}
return 'error';
};
export const formatOSname = os => {
if (os?.minor) {
return `${os.name} ${os.major}.${os.minor}`;
}
return `${os.name} ${os.major}`;
};
export const osHelperText = (
operatingSystemId,
operatingSystems,
hostGroupId,
hostGroups,
template
) => {
if (operatingSystemId) {
return osTemplateHelperText(operatingSystemId, template);
}
if (hostGroupId) {
const osId = hostGroups.find(hg => `${hg.id}` === `${hostGroupId}`)
?.operatingsystem_id;
return (
<>
{hostGroupOSHelperText(hostGroupId, hostGroups, operatingSystems)}
<br />
{osId && osTemplateHelperText(osId, template)}
</>
);
}
return '';
};
const osTemplateHelperText = (operatingSystemId, template) => {
if (!operatingSystemId && template === undefined) {
return <>&nbsp;</>;
}
if (template?.name) {
return (
<span>
{__('Initial configuration template')}:{' '}
<a href={foremanUrl(template.path)} target="_blank" rel="noreferrer">
{template.name}
</a>
</span>
);
}
return (
<span className="has-error">
<a href={foremanUrl(template.os_path)} target="_blank" rel="noreferrer">
Operating system
</a>{' '}
{__('does not have assigned host_init_config template')}
</span>
);
};
const hostGroupOSHelperText = (hostGroupId, hostGroups, operatingSystems) => {
const osId = hostGroups.find(hg => `${hg.id}` === `${hostGroupId}`)
?.operatingsystem_id;
const hostGroupOS = operatingSystems.find(os => `${os.id}` === `${osId}`);
if (hostGroupOS) {
return sprintf('Host group OS: %s', formatOSname(hostGroupOS));
}
return __('No OS from host group');
};
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageSelectors.js
import {
REGISTRATION_COMMANDS_DATA,
REGISTRATION_COMMANDS_OS_TEMPLATE,
REGISTRATION_COMMANDS,
} from '../constants';
import {
selectAPIStatus,
selectAPIResponse,
} from '../../../redux/API/APISelectors';
// Form API Data
export const selectAPIStatusData = state =>
selectAPIStatus(state, REGISTRATION_COMMANDS_DATA);
export const selectOrganizations = state =>
selectAPIResponse(state, REGISTRATION_COMMANDS_DATA).organizations || [];
export const selectLocations = state =>
selectAPIResponse(state, REGISTRATION_COMMANDS_DATA).locations || [];
export const selectHostGroups = state =>
selectAPIResponse(state, REGISTRATION_COMMANDS_DATA).hostGroups || [];
export const selectOperatingSystems = state =>
selectAPIResponse(state, REGISTRATION_COMMANDS_DATA).operatingSystems || [];
export const selectOperatingSystemTemplate = state =>
selectAPIResponse(state, REGISTRATION_COMMANDS_OS_TEMPLATE).template;
export const selectSmartProxies = state =>
selectAPIResponse(state, REGISTRATION_COMMANDS_DATA).smartProxies || [];
export const selectConfigParams = state =>
selectAPIResponse(state, REGISTRATION_COMMANDS_DATA).configParams || {};
export const selectPluginData = state =>
selectAPIResponse(state, REGISTRATION_COMMANDS_DATA).pluginData || {};
// Generate Command
export const selectAPIStatusCommand = state =>
selectAPIStatus(state, REGISTRATION_COMMANDS);
export const selectCommand = state =>
selectAPIResponse(state, REGISTRATION_COMMANDS).command || '';
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/__snapshots__/integration.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RegistrationCommandsPage integration generate command: generated command 1`] = `
Object {
"action": Array [
Array [
Object {
"payload": Object {
"key": "REGISTRATION_COMMANDS",
"params": Object {
"hostgroupId": undefined,
"insecure": false,
"jwtExpiration": 4,
"locationId": undefined,
"operatingsystemId": undefined,
"organizationId": undefined,
"packages": "",
"repo": "",
"repoGpgKeyUrl": "",
"setupInsights": "",
"setupRemoteExecution": "",
"smartProxyId": undefined,
},
"url": "/hosts/register",
},
"type": "API_POST",
},
],
],
"state": Object {},
}
`;
exports[`RegistrationCommandsPage integration generate command: rendered 1`] = `
Object {
"action": Array [
Array [
Object {
"payload": Object {
"key": "REGISTRATION_COMMANDS_DATA",
"params": Object {
"location_id": undefined,
"organization_id": undefined,
},
"url": "/hosts/register/data",
},
"type": "API_GET",
},
],
],
"state": Object {},
}
`;
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/Actions.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import Actions from '../../components/Actions';
import { actionsComponentProps } from '../fixtures'
describe('RegistrationCommandsPage - Actions', () => {
testComponentSnapshotsWithFixtures(Actions, { 'renders': actionsComponentProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/Advanced.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import Advanced from '../../components/Advanced';
import { advancedComponentProps } from '../fixtures'
describe('RegistrationCommandsPage - Advanced', () => {
testComponentSnapshotsWithFixtures(Advanced, { 'renders': advancedComponentProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/Command.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import Command from '../../components/Command';
import { commandComponentProps } from '../fixtures'
describe('RegistrationCommandsPage - Command', () => {
testComponentSnapshotsWithFixtures(Command, { 'renders': commandComponentProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/ConfigParams.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import ConfigParams from '../../components/fields/ConfigParams';
import { configParamsProps } from '../fixtures'
describe('RegistrationCommandsPage fields - ConfigParams', () => {
testComponentSnapshotsWithFixtures(ConfigParams, { 'renders': configParamsProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/General.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import General from '../../components/General';
import { generalComponentProps } from '../fixtures'
describe('RegistrationCommandsPage - General', () => {
testComponentSnapshotsWithFixtures(General, { 'renders': generalComponentProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/HostGroup.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import HostGroup from '../../components/fields/HostGroup';
import { hostGroupProps } from '../fixtures'
describe('RegistrationCommandsPage fields - HostGroup', () => {
testComponentSnapshotsWithFixtures(HostGroup, { 'renders': hostGroupProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/Insecure.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import Insecure from '../../components/fields/Insecure';
import { insecureProps } from '../fixtures'
describe('RegistrationCommandsPage fields - Insecure', () => {
testComponentSnapshotsWithFixtures(Insecure, { 'renders': insecureProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/OperatingSystem.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import OperatingSystem from '../../components/fields/OperatingSystem';
import { osProps } from '../fixtures'
jest.mock('react-redux');
describe('RegistrationCommandsPage fields - OperatingSystem', () => {
testComponentSnapshotsWithFixtures(OperatingSystem, { 'renders': osProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/Packages.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import Packages from '../../components/fields/Packages';
import { packagesProps } from '../fixtures'
describe('RegistrationCommandsPage fields - Packages', () => {
testComponentSnapshotsWithFixtures(Packages, { 'renders': packagesProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/Repository.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import Repository from '../../components/fields/Repository';
import { repositoryProps } from '../fixtures'
describe('RegistrationCommandsPage fields - Repository', () => {
testComponentSnapshotsWithFixtures(Repository, { 'renders': repositoryProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/SmartProxy.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import SmartProxy from '../../components/fields/SmartProxy';
import { smartProxyProps } from '../fixtures'
describe('RegistrationCommandsPage fields - SmartProxy', () => {
testComponentSnapshotsWithFixtures(SmartProxy, { 'renders': smartProxyProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/Taxonomies.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import Taxonomies from '../../components/fields/Taxonomies';
import { taxonomiesProps } from '../fixtures'
describe('RegistrationCommandsPage fields - Taxonomies', () => {
testComponentSnapshotsWithFixtures(Taxonomies, { 'renders': taxonomiesProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/TokenLifeTime.test.js
import { testComponentSnapshotsWithFixtures } from '../../../../../common/testHelpers';
import TokenLifeTime from '../../components/fields/TokenLifeTime';
import { tokenLifeTimeProps } from '../fixtures'
describe('RegistrationCommandsPage fields - TokenLifeTime', () => {
testComponentSnapshotsWithFixtures(TokenLifeTime, { 'renders': tokenLifeTimeProps });
})
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/__snapshots__/Actions.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RegistrationCommandsPage - Actions renders 1`] = `
<Fragment>
<ActionGroup>
<Button
id="generate_btn"
isDisabled={false}
isLoading={false}
onClick={[Function]}
variant="primary"
>
Generate
</Button>
<Link
to="/hosts"
>
<Button
variant="link"
>
Cancel
</Button>
</Link>
</ActionGroup>
<FormGroup
fieldId="actions_help"
>
<FormHelperText
icon={
<ExclamationCircleIcon
color="currentColor"
noVerticalAlign={false}
size="sm"
/>
}
isHidden={true}
/>
</FormGroup>
</Fragment>
`;
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/__snapshots__/Advanced.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RegistrationCommandsPage - Advanced renders 1`] = `
<Fragment>
<ConfigParams
configParams={Object {}}
handleInsights={[Function]}
handleRemoteExecution={[Function]}
isLoading={false}
setupInsights=""
setupRemoteExecution=""
/>
<Packages
configParams={Object {}}
handlePackages={[Function]}
isLoading={false}
packages=""
/>
<Repository
handleRepo={[Function]}
handleRepoGpgKeyUrl={[Function]}
isLoading={false}
repo=""
repoGpgKeyUrl=""
/>
<TokenLifeTime
handleInvalidField={[Function]}
isLoading={false}
onChange={[Function]}
value=""
/>
</Fragment>
`;
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/__snapshots__/Command.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RegistrationCommandsPage - Command renders 1`] = `
<FormGroup
label="Registration command"
>
<ClipboardCopy
clickTip="Successfully copied to clipboard!"
entryDelay={100}
exitDelay={1600}
hoverTip="Copy to clipboard"
isCode={true}
isExpanded={true}
isReadOnly={true}
maxWidth="150px"
onChange={[Function]}
onCopy={[Function]}
position="top"
switchDelay={2000}
textAriaLabel="Copyable input"
toggleAriaLabel="Show content"
variant="expansion"
>
command
</ClipboardCopy>
</FormGroup>
`;
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/__snapshots__/ConfigParams.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RegistrationCommandsPage fields - ConfigParams renders 1`] = `
<Fragment>
<FormGroup
fieldId="registration_setup_remote_execution"
isRequired={true}
label="Setup REX"
labelIcon={
<LabelIcon
text="Setup remote execution. If set to \`Yes\`, SSH keys will be installed on the registered host. The inherited value is based on the \`host_registration_remote_execution\` parameter. It can be inherited e.g. from host group, operating system, organization. When overridden, the selected value will be stored on host parameter level."
/>
}
>
<FormSelect
className="without_select2"
id="registration_setup_remote_execution"
isDisabled={false}
isRequired={true}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
ouiaSafe={true}
validated="default"
value=""
>
<FormSelectOption
key="0"
label="Inherit from host parameter (no)"
value=""
/>
<FormSelectOption
key="1"
label="Yes (override)"
value={true}
/>
<FormSelectOption
key="2"
label="No (override)"
value={false}
/>
</FormSelect>
</FormGroup>
<FormGroup
fieldId="registration_setup_insights"
isRequired={true}
label="Setup Insights"
labelIcon={
<LabelIcon
text="If set to \`Yes\`, Insights client will be installed and registered on Red Hat family operating systems. It has no effect on other OS families that do not support it. The inherited value is based on the \`host_registration_insights\` parameter. It can be inherited e.g. from host group, operating system, organization. When overridden, the selected value will be stored on host parameter level."
/>
}
>
<FormSelect
className="without_select2"
id="registration_setup_insights"
isDisabled={false}
isRequired={true}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
ouiaSafe={true}
validated="default"
value=""
>
<FormSelectOption
key="0"
label="Inherit from host parameter (no)"
value=""
/>
<FormSelectOption
key="1"
label="Yes (override)"
value={true}
/>
<FormSelectOption
key="2"
label="No (override)"
value={false}
/>
</FormSelect>
</FormGroup>
</Fragment>
`;
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/__snapshots__/General.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RegistrationCommandsPage - General renders 1`] = `
<Fragment>
<Taxonomies
handleLocation={[Function]}
handleOrganization={[Function]}
isLoading={false}
locationId={0}
locations={Array []}
organizationId={0}
organizations={Array []}
/>
<HostGroup
handleHostGroup={[Function]}
hostGroupId={0}
hostGroups={Array []}
isLoading={false}
/>
<OperatingSystem
handleInvalidField={[Function]}
handleOperatingSystem={[Function]}
hostGroupId={0}
hostGroups={Array []}
isLoading={false}
operatingSystemId={0}
operatingSystemTemplate=""
operatingSystems={Array []}
/>
<SmartProxy
handleSmartProxy={[Function]}
isLoading={false}
smartProxies={Array []}
smartProxyId={0}
/>
<Insecure
handleInsecure={[Function]}
insecure={false}
isLoading={false}
/>
</Fragment>
`;
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/__snapshots__/HostGroup.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RegistrationCommandsPage fields - HostGroup renders 1`] = `
<FormGroup
fieldId="reg_host_group"
label="Host group"
>
<FormSelect
className="without_select2"
id="reg_host_group"
isDisabled={false}
isRequired={false}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
ouiaSafe={true}
validated="default"
value={0}
>
<FormSelectOption
label=""
value=""
/>
<FormSelectOption
key="0"
label="test_hg"
value={0}
/>
</FormSelect>
</FormGroup>
`;
webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/__tests__/components/__snapshots__/Insecure.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RegistrationCommandsPage fields - Insecure renders 1`] = `
<FormGroup
fieldId="reg_insecure"
>
<Checkbox
className=""
id="reg_insecure"
isChecked={false}
isDisabled={false}
isValid={true}
label={
<span>
Insecure
<LabelIcon
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff