katello 4.12.0.rc3 → 4.12.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of katello might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +0 -1
- data/app/assets/javascripts/katello/sync_management/sync_management.js +1 -0
- data/app/controllers/katello/api/v2/products_bulk_actions_controller.rb +1 -1
- data/app/helpers/katello/concerns/dashboard_helper_extensions.rb +0 -10
- data/app/helpers/katello/hosts_and_hostgroups_helper.rb +14 -2
- data/app/helpers/katello/katello_urls_helper.rb +26 -1
- data/app/lib/katello/errors.rb +4 -0
- data/app/lib/katello/resources/cdn.rb +4 -13
- data/app/models/katello/candlepin/repository_mapper.rb +1 -1
- data/app/models/katello/glue/pulp/repos.rb +0 -6
- data/app/models/katello/host_collection.rb +12 -3
- data/app/models/katello/repository.rb +6 -0
- data/app/services/katello/pulp3/alternate_content_source.rb +2 -2
- data/app/views/katello/api/v2/hosts/host_collections.json.rabl +5 -1
- data/app/views/katello/hosts/_errata_counts.html.erb +1 -1
- data/app/views/overrides/activation_keys/_host_tab_pane.html.erb +1 -29
- data/lib/katello/plugin.rb +1 -8
- data/lib/katello/version.rb +1 -1
- data/webpack/components/ActivationKeysSearch/ActivationKeysSearch.test.js +28 -0
- data/webpack/components/ActivationKeysSearch/index.js +222 -0
- data/webpack/components/extensions/HostDetails/Tabs/PackagesTab/PackagesTab.js +1 -1
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/packageInstallModal.test.js +1 -0
- data/webpack/components/extensions/HostDetails/Tabs/__tests__/packagesTab.test.js +1 -0
- data/webpack/global_index.js +10 -0
- data/webpack/scenes/ContentViews/Details/ComponentContentViews/ContentViewComponents.js +6 -3
- data/webpack/scenes/ContentViews/Publish/CVPublishForm.js +1 -1
- data/webpack/scenes/ContentViews/Publish/__tests__/publishContentView.test.js +30 -0
- data/webpack/scenes/Hosts/ChangeContentSource/components/ContentSourceForm.js +1 -1
- metadata +7 -6
- data/app/assets/javascripts/katello/hosts/activation_key_edit.js +0 -167
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c78bf13db890a8782d152850ca4e2b26425a3d181a1e02e9f4ecf779785e3585
|
4
|
+
data.tar.gz: 983b9a631aee789c6468d9dc3cdae527e1c5d42dd8b02281be7cfd41e751d5f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ebb29c3055d54b87bdc077765fba457f7e790f6789267c69dd36709d308b74fe6979b32ace066aa9840d20ec94e06b203cade51fe59b68abc321c3c0f1a8d00
|
7
|
+
data.tar.gz: 85471a1295e226c3a03a4daab47c6f4ec7ff5707068ca67878c8fe67993c138c2a77f0ab8a751fa47fb057f9150b64764ce7847e3988d11d305101e6005584bc
|
data/README.md
CHANGED
@@ -74,7 +74,6 @@ See the [annotation docs](./test/scenarios/annotations/README.md) for more infor
|
|
74
74
|
* Archived mailing lists:
|
75
75
|
* [Foreman User Mailing List](https://groups.google.com/forum/?fromgroups#!forum/foreman-users)
|
76
76
|
* [Foreman Developer mailing list](https://groups.google.com/forum/?fromgroups#!forum/foreman-dev)
|
77
|
-
* [IRC Freenode](https://theforeman.org/support.html#IRClivechat): #theforeman-dev, #theforeman
|
78
77
|
|
79
78
|
## Documentation
|
80
79
|
|
@@ -55,7 +55,7 @@ module Katello
|
|
55
55
|
repairable_products = @products.syncable
|
56
56
|
repairable_roots = RootRepository.where(:product_id => repairable_products).has_url.select { |r| r.library_instance }.uniq
|
57
57
|
|
58
|
-
repairable_repositories = Katello::Repository.where(:root_id => repairable_roots)
|
58
|
+
repairable_repositories = Katello::Repository.library.where(:root_id => repairable_roots)
|
59
59
|
task = async_task(::Actions::BulkAction,
|
60
60
|
::Actions::Katello::Repository::VerifyChecksum,
|
61
61
|
repairable_repositories)
|
@@ -8,16 +8,6 @@ module Katello
|
|
8
8
|
def total_host_count
|
9
9
|
host_query.size
|
10
10
|
end
|
11
|
-
|
12
|
-
def removed_widgets
|
13
|
-
widgets = super
|
14
|
-
|
15
|
-
if Organization.current&.simple_content_access?
|
16
|
-
widgets.reject! { |widget| ::Widget.singleton_class::SUBSCRIPTION_TEMPLATES.include? widget[:template] }
|
17
|
-
end
|
18
|
-
|
19
|
-
widgets
|
20
|
-
end
|
21
11
|
end
|
22
12
|
end
|
23
13
|
end
|
@@ -72,6 +72,10 @@ module Katello
|
|
72
72
|
def fetch_lifecycle_environment(host_or_hostgroup, options = {})
|
73
73
|
return host_or_hostgroup.single_lifecycle_environment if host_or_hostgroup.try(:single_lifecycle_environment)
|
74
74
|
return host_or_hostgroup.lifecycle_environment if host_or_hostgroup.try(:lifecycle_environment)
|
75
|
+
if host_or_hostgroup.is_a?(::Hostgroup) && host_or_hostgroup.content_facet.present?
|
76
|
+
# to handle cloned hostgroups that are new records
|
77
|
+
return host_or_hostgroup.content_facet.lifecycle_environment
|
78
|
+
end
|
75
79
|
selected_host_group = options.fetch(:selected_host_group, nil)
|
76
80
|
return selected_host_group.lifecycle_environment if selected_host_group.present?
|
77
81
|
end
|
@@ -79,12 +83,20 @@ module Katello
|
|
79
83
|
def fetch_content_view(host_or_hostgroup, options = {})
|
80
84
|
return host_or_hostgroup.single_content_view if host_or_hostgroup.try(:single_content_view)
|
81
85
|
return host_or_hostgroup.content_view if host_or_hostgroup.try(:content_view)
|
86
|
+
if host_or_hostgroup.is_a?(::Hostgroup) && host_or_hostgroup.content_facet.present?
|
87
|
+
# to handle cloned hostgroups that are new records
|
88
|
+
return host_or_hostgroup.content_facet.content_view
|
89
|
+
end
|
82
90
|
selected_host_group = options.fetch(:selected_host_group, nil)
|
83
91
|
return selected_host_group.content_view if selected_host_group.present?
|
84
92
|
end
|
85
93
|
|
86
|
-
def fetch_content_source(
|
87
|
-
return
|
94
|
+
def fetch_content_source(host_or_hostgroup, options = {})
|
95
|
+
return host_or_hostgroup.content_source if host_or_hostgroup.content_source_id&.present? && host_or_hostgroup.persisted?
|
96
|
+
if host_or_hostgroup.is_a?(::Hostgroup) && host_or_hostgroup.content_facet.present?
|
97
|
+
# to handle cloned hostgroups that are new records
|
98
|
+
return host_or_hostgroup.content_facet.content_source
|
99
|
+
end
|
88
100
|
selected_host_group = options.fetch(:selected_host_group, nil)
|
89
101
|
return selected_host_group.content_source if selected_host_group.present?
|
90
102
|
end
|
@@ -37,11 +37,36 @@ module Katello
|
|
37
37
|
"#{prefix}/pub/#{config}"
|
38
38
|
end
|
39
39
|
|
40
|
-
apipie :method, 'Generates an absolute path to the file' do
|
40
|
+
apipie :method, 'Generates an absolute path to the file for liveimg kickstart templates' do
|
41
41
|
required :content_path, String, desc: "Relative path to the file or it's name"
|
42
42
|
optional :schema, String, desc: 'Optional URL schema for the content source', default: 'http'
|
43
43
|
optional :content_type, String, desc: 'Content type', default: 'repos'
|
44
44
|
returns String, desc: 'Absolute path to a file'
|
45
45
|
end
|
46
|
+
def repository_url(content_path, _content_type = nil, schema = 'http')
|
47
|
+
return content_path if content_path =~ %r|^([\w\-\+]+)://|
|
48
|
+
url = if @host.content_source
|
49
|
+
"#{schema}://#{@host.content_source.hostname}"
|
50
|
+
else
|
51
|
+
foreman_settings_url(schema)
|
52
|
+
end
|
53
|
+
content_path = content_path.sub(%r|^/|, '')
|
54
|
+
|
55
|
+
lifecycle_environment = nil
|
56
|
+
if @host.default_environment?
|
57
|
+
# Don't update the content_path if the host is using the default org view / library
|
58
|
+
lifecycle_environment = ::Katello::KTEnvironment.library.find_by(organization_id: @host.organization_id)
|
59
|
+
elsif !@host.single_content_view_environment?
|
60
|
+
fail ::Katello::Errors::MultiEnvironmentNotSupportedError,
|
61
|
+
"Host #{@host.name} must be subscribed to only a single content view & environment or subscribe to the default organization content view for liveimg provisioning."
|
62
|
+
else
|
63
|
+
lifecycle_environment = @host.single_lifecycle_environment
|
64
|
+
content_path = [@host.single_content_view.label, content_path].join('/')
|
65
|
+
end
|
66
|
+
|
67
|
+
path = ::Katello::Repository.repo_path_from_content_path(
|
68
|
+
lifecycle_environment, content_path)
|
69
|
+
"#{url}/pulp/content/#{path}"
|
70
|
+
end
|
46
71
|
end
|
47
72
|
end
|
data/app/lib/katello/errors.rb
CHANGED
@@ -10,6 +10,10 @@ module Katello
|
|
10
10
|
|
11
11
|
class RegistrationError < StandardError; end
|
12
12
|
|
13
|
+
class InvalidRepositoryTypeError < StandardError; end
|
14
|
+
|
15
|
+
class MultiEnvironmentNotSupportedError < StandardError; end
|
16
|
+
|
13
17
|
# unauthorized access
|
14
18
|
class SecurityViolation < StandardError; end
|
15
19
|
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module Katello
|
2
2
|
module Resources
|
3
3
|
module CDN
|
4
|
-
SUPPORTED_SSL_VERSIONS = ['SSLv23', 'TLSv1'].freeze
|
5
|
-
|
6
4
|
class Utils
|
7
5
|
# takes releasever from contentUrl (e.g. 6Server, 6.0, 6.1)
|
8
6
|
# returns hash e.g. {:major => 6, :minor => "6.1"}
|
@@ -24,11 +22,6 @@ module Katello
|
|
24
22
|
end
|
25
23
|
|
26
24
|
def initialize(url, options = {})
|
27
|
-
@ssl_version = Setting[:cdn_ssl_version]
|
28
|
-
if @ssl_version && !SUPPORTED_SSL_VERSIONS.include?(@ssl_version)
|
29
|
-
fail("Invalid SSL version specified. Check the 'CDN SSL Version' setting")
|
30
|
-
end
|
31
|
-
|
32
25
|
options.reverse_merge!(:verify_ssl => 9)
|
33
26
|
options.assert_valid_keys(:ssl_client_key,
|
34
27
|
:ssl_client_cert,
|
@@ -106,12 +99,10 @@ module Katello
|
|
106
99
|
net.cert_store = @cert_store
|
107
100
|
end
|
108
101
|
|
109
|
-
# NOTE: This
|
110
|
-
#
|
111
|
-
#
|
112
|
-
|
113
|
-
# "OpenSSL::SSL::SSLContext::METHODS"
|
114
|
-
net.ssl_version = @ssl_version
|
102
|
+
# NOTE: This is only here due to https://github.com/ruby/openssl/issues/709, otherwise the
|
103
|
+
# system-wide crypto policy could be used. Enforcing TLS version >= 1.2 will prevent using
|
104
|
+
# very old infrastructure for now, but that was considered better than having an insecure default.
|
105
|
+
net.min_version = OpenSSL::SSL::TLS1_2_VERSION
|
115
106
|
|
116
107
|
if (@options[:verify_ssl] == false) || (@options[:verify_ssl] == OpenSSL::SSL::VERIFY_NONE)
|
117
108
|
net.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
@@ -74,7 +74,7 @@ module Katello
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def relative_path
|
77
|
-
::Katello::
|
77
|
+
::Katello::Repository.repo_path_from_content_path(product.organization.library, path)
|
78
78
|
end
|
79
79
|
|
80
80
|
def prune_substitutions(subs, url)
|
@@ -6,12 +6,6 @@ module Katello
|
|
6
6
|
base.send :include, InstanceMethods
|
7
7
|
end
|
8
8
|
|
9
|
-
def self.repo_path_from_content_path(environment, content_path)
|
10
|
-
path = content_path.sub(%r|^/|, '')
|
11
|
-
path_prefix = [environment.organization.label, environment.label].join('/')
|
12
|
-
"#{path_prefix}/#{path}"
|
13
|
-
end
|
14
|
-
|
15
9
|
module InstanceMethods
|
16
10
|
def distributions(env)
|
17
11
|
to_ret = []
|
@@ -29,7 +29,10 @@ module Katello
|
|
29
29
|
scoped_search :relation => :hosts, :on => :name, :rename => :host, :complete_value => true
|
30
30
|
|
31
31
|
def max_hosts_check
|
32
|
-
|
32
|
+
# NOTE: max_hosts_check and max_hosts_no_exceeded use size() instead of count() because
|
33
|
+
# the host list exists as an array rather than a DB query when run as a validation.
|
34
|
+
host_count = hosts.size
|
35
|
+
if !unlimited_hosts && (host_count > 0 && (host_count.to_i > max_hosts.to_i)) && max_hosts_changed?
|
33
36
|
errors.add :max_host, N_("may not be less than the number of hosts associated with the host collection.")
|
34
37
|
end
|
35
38
|
end
|
@@ -60,8 +63,14 @@ module Katello
|
|
60
63
|
type ? query.of_type(type) : query
|
61
64
|
end
|
62
65
|
|
63
|
-
def
|
64
|
-
|
66
|
+
def cache_key
|
67
|
+
"#{self.class.name}/#{self.id}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def total_hosts(cached: false)
|
71
|
+
Rails.cache.fetch("#{cache_key}/total_hosts", expires_in: 1.minute, force: !cached) do
|
72
|
+
hosts.count
|
73
|
+
end
|
65
74
|
end
|
66
75
|
|
67
76
|
# Retrieve the list of accessible host collections in the organization specified, returning
|
@@ -201,6 +201,12 @@ module Katello
|
|
201
201
|
joins(:root).where("#{Katello::RootRepository.table_name}.product_id" => products)
|
202
202
|
end
|
203
203
|
|
204
|
+
def self.repo_path_from_content_path(environment, content_path)
|
205
|
+
path = content_path.sub(%r|^/|, '')
|
206
|
+
path_prefix = [environment.organization.label, environment.label].join('/')
|
207
|
+
"#{path_prefix}/#{path}"
|
208
|
+
end
|
209
|
+
|
204
210
|
def to_label
|
205
211
|
name
|
206
212
|
end
|
@@ -108,7 +108,7 @@ module Katello
|
|
108
108
|
if acs.content_type == ::Katello::Repository::FILE_TYPE && acs.subpaths.present?
|
109
109
|
paths = insert_pulp_manifest!(paths)
|
110
110
|
end
|
111
|
-
response = api.alternate_content_source_api.create(name: generate_backend_object_name, paths: paths,
|
111
|
+
response = api.alternate_content_source_api.create(name: generate_backend_object_name, paths: paths.sort,
|
112
112
|
remote: smart_proxy_acs.remote_href)
|
113
113
|
smart_proxy_acs.update!(alternate_content_source_href: response.pulp_href)
|
114
114
|
return response
|
@@ -125,7 +125,7 @@ module Katello
|
|
125
125
|
if acs.content_type == ::Katello::Repository::FILE_TYPE && acs.subpaths.present?
|
126
126
|
paths = insert_pulp_manifest!(paths)
|
127
127
|
end
|
128
|
-
api.alternate_content_source_api.update(href, name: generate_backend_object_name, paths: paths, remote: smart_proxy_acs.remote_href)
|
128
|
+
api.alternate_content_source_api.update(href, name: generate_backend_object_name, paths: paths.sort, remote: smart_proxy_acs.remote_href)
|
129
129
|
end
|
130
130
|
|
131
131
|
def delete_alternate_content_source
|
@@ -1,3 +1,7 @@
|
|
1
1
|
child :host_collections => :host_collections do
|
2
|
-
attributes :id, :name, :description, :max_hosts, :unlimited_hosts
|
2
|
+
attributes :id, :name, :description, :max_hosts, :unlimited_hosts
|
3
|
+
|
4
|
+
node :total_hosts do |host_collection|
|
5
|
+
host_collection.total_hosts(cached: true)
|
6
|
+
end
|
3
7
|
end
|
@@ -43,7 +43,7 @@
|
|
43
43
|
<% end %>
|
44
44
|
<% if host.operatingsystem_name&.match(/Debian|Ubuntu/) %>
|
45
45
|
<% if Setting["host_details_ui"] %>
|
46
|
-
<a href="/new/hosts/<%= host.name %>#/Content/
|
46
|
+
<a href="/new/hosts/<%= host.name %>#/Content/debs?status=Upgradable">
|
47
47
|
<% else %>
|
48
48
|
<a href="/content_hosts/<%= host.id %>/debs/applicable">
|
49
49
|
<% end %>
|
@@ -1,31 +1,3 @@
|
|
1
|
-
|
2
|
-
<%= javascript "katello/hosts/activation_key_edit" %>
|
3
|
-
|
4
|
-
|
5
1
|
<div class="tab-pane" id="activation_keys">
|
6
|
-
|
7
|
-
<%= _("There was a problem retrieving Activation Key data from the server.") %>
|
8
|
-
</div>
|
9
|
-
|
10
|
-
<%= field(f, _("Activation Keys"),
|
11
|
-
:help_inline => _("The value will be available in templates as @host.params['#{kt_ak_label}']")) do
|
12
|
-
react_component('TypeAheadSelect', { id: 'kt_activation_keys', multiple: true, allowNew: true })
|
13
|
-
end %>
|
14
|
-
|
15
|
-
<div class="alert alert-info">
|
16
|
-
<p><b><%= _('Subscriptions information based on selected activation keys:') %></b></p>
|
17
|
-
<ul id="ak-subscriptions-info"></ul>
|
18
|
-
|
19
|
-
<div id="ak-subscriptions-spinner" style="display: none">
|
20
|
-
<%= image_tag "spinner.gif" %>
|
21
|
-
</div>
|
22
|
-
|
23
|
-
<p><%= _('Activation keys and subscriptions can be managed') %>
|
24
|
-
<b><a href="/activation_keys" target="_blank"> <%= _('here.') %></a></b>
|
25
|
-
</p>
|
26
|
-
<p translate>
|
27
|
-
Activation keys may be used during <a href="/hosts/register">system registration.</a>
|
28
|
-
</p>
|
29
|
-
<p><a href="" id="ak_refresh_subscriptions"><%= _('Reload data') %></a></p>
|
30
|
-
</div>
|
2
|
+
<%= react_component('ActivationKeysSearch')%>
|
31
3
|
</div>
|
data/lib/katello/plugin.rb
CHANGED
@@ -373,13 +373,6 @@ Foreman::Plugin.register :katello do
|
|
373
373
|
collection: proc { http_proxy_select },
|
374
374
|
include_blank: N_("no global default")
|
375
375
|
|
376
|
-
setting 'cdn_ssl_version',
|
377
|
-
type: :string,
|
378
|
-
default: nil,
|
379
|
-
full_name: N_('CDN SSL version'),
|
380
|
-
description: N_("SSL version used to communicate with the CDN"),
|
381
|
-
collection: proc { hashify_parameters(Katello::Resources::CDN::SUPPORTED_SSL_VERSIONS) }
|
382
|
-
|
383
376
|
setting 'katello_default_provision',
|
384
377
|
type: :string,
|
385
378
|
default: 'Kickstart default',
|
@@ -727,7 +720,7 @@ Foreman::Plugin.register :katello do
|
|
727
720
|
], 'Role granting permission to import content views in an organization'
|
728
721
|
|
729
722
|
role 'Content Exporter', [
|
730
|
-
:export_content, :view_products, :view_content_views, :view_organizations
|
723
|
+
:export_content, :view_products, :view_content_views, :create_content_views, :view_organizations
|
731
724
|
], 'Role granting permission to export content views in an organization'
|
732
725
|
|
733
726
|
def find_katello_assets(args = {})
|
data/lib/katello/version.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { renderWithRedux } from 'react-testing-lib-wrapper';
|
3
|
+
import ActivationKeysSearch from './index';
|
4
|
+
|
5
|
+
describe('ActivationKeysSearch', () => {
|
6
|
+
const mockQuerySelector = jest.spyOn(document, 'querySelector');
|
7
|
+
mockQuerySelector.mockImplementation((selector) => {
|
8
|
+
if (selector === '#hostgroup_lifecycle_environment_id') {
|
9
|
+
return {
|
10
|
+
options: [{}],
|
11
|
+
selectedIndex: 0,
|
12
|
+
value: '1',
|
13
|
+
};
|
14
|
+
}
|
15
|
+
if (selector === '#hostgroup_content_view_id') {
|
16
|
+
return {
|
17
|
+
options: [{}],
|
18
|
+
selectedIndex: 0,
|
19
|
+
value: '2 ',
|
20
|
+
};
|
21
|
+
}
|
22
|
+
return null;
|
23
|
+
});
|
24
|
+
it('renders without crashing', () => {
|
25
|
+
const { getByText } = renderWithRedux(<ActivationKeysSearch />, {});
|
26
|
+
expect(getByText('Activation Key information')).toBeInTheDocument();
|
27
|
+
});
|
28
|
+
});
|
@@ -0,0 +1,222 @@
|
|
1
|
+
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
2
|
+
import { useDispatch, useSelector } from 'react-redux';
|
3
|
+
import {
|
4
|
+
Form,
|
5
|
+
FormGroup,
|
6
|
+
Spinner,
|
7
|
+
EmptyState,
|
8
|
+
Title,
|
9
|
+
Button,
|
10
|
+
Select,
|
11
|
+
SelectOption,
|
12
|
+
SelectVariant,
|
13
|
+
Alert,
|
14
|
+
} from '@patternfly/react-core';
|
15
|
+
import { FormattedMessage } from 'react-intl';
|
16
|
+
import $ from 'jquery';
|
17
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
18
|
+
import { get } from 'foremanReact/redux/API';
|
19
|
+
import { foremanUrl } from 'foremanReact/common/helpers';
|
20
|
+
import { STATUS } from 'foremanReact/constants';
|
21
|
+
import { selectAPIStatus } from 'foremanReact/redux/API/APISelectors';
|
22
|
+
|
23
|
+
const getSelectedEnvId = () => {
|
24
|
+
const selectElement = document.querySelector('#hostgroup_lifecycle_environment_id');
|
25
|
+
const selectedOption = selectElement.options[selectElement.selectedIndex];
|
26
|
+
let dataId = selectedOption?.getAttribute?.('data-id');
|
27
|
+
if (!dataId) {
|
28
|
+
dataId = selectElement.value;
|
29
|
+
}
|
30
|
+
return dataId;
|
31
|
+
};
|
32
|
+
const getSelectedContentViewId = () => {
|
33
|
+
const selectElement = document.querySelector('#hostgroup_content_view_id');
|
34
|
+
const selectedOption = selectElement.options[selectElement.selectedIndex];
|
35
|
+
let dataId = selectedOption?.getAttribute?.('data-id');
|
36
|
+
if (!dataId) {
|
37
|
+
dataId = selectElement.value;
|
38
|
+
}
|
39
|
+
return dataId;
|
40
|
+
};
|
41
|
+
const ActivationKeysSearch = () => {
|
42
|
+
const ACTIVATION_KEYS = 'ACTIVATION_KEYS';
|
43
|
+
const KT_AK_LABEL = 'kt_activation_keys';
|
44
|
+
const [selectedEnvId, setSelectedEnvId] = useState(getSelectedEnvId());
|
45
|
+
const [selectedContentViewId, setSelectedContentViewId] = useState(getSelectedContentViewId());
|
46
|
+
const isLoading =
|
47
|
+
useSelector(state => selectAPIStatus(state, ACTIVATION_KEYS)) === STATUS.PENDING;
|
48
|
+
const [activationKeys, setActivationKeys] = useState([]);
|
49
|
+
const [selectedKeys, setSelectedKeys] = useState([]);
|
50
|
+
const [isOpen, setIsOpen] = useState(false);
|
51
|
+
const dispatch = useDispatch();
|
52
|
+
|
53
|
+
const ktLoadActivationKeys = useCallback(() => {
|
54
|
+
if (selectedEnvId && selectedContentViewId) {
|
55
|
+
dispatch(get({
|
56
|
+
key: ACTIVATION_KEYS,
|
57
|
+
url: foremanUrl(`/katello/api/v2/environments/${selectedEnvId}/activation_keys`),
|
58
|
+
params: { content_view_id: selectedContentViewId },
|
59
|
+
handleSuccess: ({ data }) => {
|
60
|
+
setActivationKeys(data.results);
|
61
|
+
},
|
62
|
+
errorToast: () =>
|
63
|
+
__('There was a problem retrieving Activation Key data from the server.'),
|
64
|
+
}));
|
65
|
+
}
|
66
|
+
}, [dispatch, selectedEnvId, selectedContentViewId, setActivationKeys]);
|
67
|
+
const paramContainer = useMemo(() => {
|
68
|
+
let ret;
|
69
|
+
const inputs = document.querySelectorAll("div#parameters .fields input[type='text']");
|
70
|
+
inputs.forEach((input) => {
|
71
|
+
if (input.value === KT_AK_LABEL) {
|
72
|
+
ret = input.closest('.fields');
|
73
|
+
}
|
74
|
+
});
|
75
|
+
return ret;
|
76
|
+
}, []);
|
77
|
+
|
78
|
+
useEffect(() => {
|
79
|
+
$('#hostgroup_lifecycle_environment_id').on('change', () => setSelectedEnvId(getSelectedEnvId)); // cant use eventlistener on select2
|
80
|
+
$('#hostgroup_content_view_id').on('change', () =>
|
81
|
+
setSelectedContentViewId(getSelectedContentViewId())); // cant use eventlistener on select2
|
82
|
+
if (selectedEnvId && selectedContentViewId) {
|
83
|
+
ktLoadActivationKeys();
|
84
|
+
}
|
85
|
+
|
86
|
+
const ktHideParams = () => {
|
87
|
+
if (paramContainer) {
|
88
|
+
paramContainer.style.display = 'none';
|
89
|
+
}
|
90
|
+
};
|
91
|
+
|
92
|
+
const ktAkGetKeysFromParam = () => {
|
93
|
+
let keys = [];
|
94
|
+
if (paramContainer) {
|
95
|
+
const textarea = paramContainer.querySelector('textarea');
|
96
|
+
if (textarea) {
|
97
|
+
keys = textarea.value.split(',').map(key => key.trim());
|
98
|
+
}
|
99
|
+
}
|
100
|
+
return keys;
|
101
|
+
};
|
102
|
+
ktHideParams();
|
103
|
+
setSelectedKeys(ktAkGetKeysFromParam());
|
104
|
+
}, [ktLoadActivationKeys, paramContainer, selectedContentViewId, selectedEnvId]);
|
105
|
+
|
106
|
+
useEffect(() => {
|
107
|
+
function ktSetParam() {
|
108
|
+
let paramContainerCopy = paramContainer;
|
109
|
+
if (selectedKeys.length > 0) {
|
110
|
+
const value = selectedKeys.map(key => key.trim()).join(',');
|
111
|
+
if (!paramContainerCopy) {
|
112
|
+
// we create the param for kt_activation_keys
|
113
|
+
const addParameterButton = document.querySelector('#parameters .btn-primary');
|
114
|
+
addParameterButton.click();
|
115
|
+
const directionOfAddedItems = addParameterButton.getAttribute('direction');
|
116
|
+
const paramContainers = document.querySelectorAll('#parameters .fields');
|
117
|
+
if (directionOfAddedItems === 'append') {
|
118
|
+
paramContainerCopy = paramContainers[paramContainers.length - 1];
|
119
|
+
} else {
|
120
|
+
[paramContainerCopy] = paramContainers;
|
121
|
+
}
|
122
|
+
paramContainerCopy.querySelector("input[name*='name']").value = KT_AK_LABEL;
|
123
|
+
}
|
124
|
+
paramContainerCopy.querySelector('textarea').value = value;
|
125
|
+
paramContainerCopy.querySelector("input[type='hidden']").value = 0;
|
126
|
+
} else if (paramContainerCopy) {
|
127
|
+
// we remove the param by setting destroy to 1
|
128
|
+
paramContainerCopy.querySelector("input[type='hidden']").value = 1;
|
129
|
+
}
|
130
|
+
}
|
131
|
+
ktSetParam();
|
132
|
+
}, [paramContainer, selectedKeys]);
|
133
|
+
|
134
|
+
if (!(selectedEnvId && selectedContentViewId)) {
|
135
|
+
return (
|
136
|
+
<EmptyState>
|
137
|
+
<Title headingLevel="h4" size="lg" ouiaId="ak-empty-state-title">
|
138
|
+
{__('Please select a lifecycle environment and content view to view activation keys.')}
|
139
|
+
</Title>
|
140
|
+
</EmptyState>
|
141
|
+
);
|
142
|
+
}
|
143
|
+
|
144
|
+
const onSelect = (event, selection) => {
|
145
|
+
setIsOpen(false);
|
146
|
+
if (selectedKeys.includes(selection)) {
|
147
|
+
setSelectedKeys(prevState => prevState.filter(item => item !== selection));
|
148
|
+
} else {
|
149
|
+
setSelectedKeys(prevState => [...prevState, selection]);
|
150
|
+
}
|
151
|
+
};
|
152
|
+
const isEmptyResults = activationKeys.length === 0;
|
153
|
+
return (
|
154
|
+
<div>
|
155
|
+
<Form isHorizontal>
|
156
|
+
<FormGroup label={__('Activation Keys')}>
|
157
|
+
<Select
|
158
|
+
ouiaId="ak-select"
|
159
|
+
variant={SelectVariant.typeaheadMulti}
|
160
|
+
onToggle={setIsOpen}
|
161
|
+
onSelect={onSelect}
|
162
|
+
selections={selectedKeys}
|
163
|
+
isOpen={isOpen}
|
164
|
+
isCreatable
|
165
|
+
shouldResetOnSelect
|
166
|
+
isDisabled={isLoading || isEmptyResults}
|
167
|
+
placeholderText={
|
168
|
+
isEmptyResults
|
169
|
+
? __('The selected lifecycle environment contains no activation keys')
|
170
|
+
: null
|
171
|
+
}
|
172
|
+
>
|
173
|
+
{activationKeys.map(({ id, name }) => (
|
174
|
+
<SelectOption key={id} value={name} />
|
175
|
+
))}
|
176
|
+
</Select>
|
177
|
+
</FormGroup>
|
178
|
+
<Alert title={__('Activation Key information')} variant="info" ouiaId="ak-info">
|
179
|
+
<p>{__("The value will be available in templates as @host.params['kt_activation_keys']")}</p>
|
180
|
+
<p>
|
181
|
+
<FormattedMessage
|
182
|
+
id="ak-link-manage"
|
183
|
+
defaultMessage={__('Activation keys can be managed {here}.')}
|
184
|
+
values={{
|
185
|
+
here: (
|
186
|
+
<b>
|
187
|
+
<a href="/activation_keys" target="_blank">
|
188
|
+
{__('here')}
|
189
|
+
</a>
|
190
|
+
</b>
|
191
|
+
),
|
192
|
+
}}
|
193
|
+
/>
|
194
|
+
</p>
|
195
|
+
<p>
|
196
|
+
<FormattedMessage
|
197
|
+
id="ak-subscriptions-info"
|
198
|
+
defaultMessage={__('Activation keys may be used during {system_registration}.')}
|
199
|
+
values={{
|
200
|
+
system_registration: <a href="/hosts/register">{__('system registration')}</a>,
|
201
|
+
}}
|
202
|
+
/>
|
203
|
+
</p>
|
204
|
+
<p>
|
205
|
+
<Button
|
206
|
+
id="ak_refresh_subscriptions"
|
207
|
+
variant="link"
|
208
|
+
onClick={ktLoadActivationKeys}
|
209
|
+
ouiaId="ak-refresh-button"
|
210
|
+
isInline
|
211
|
+
>
|
212
|
+
{__('Reload data')}
|
213
|
+
</Button>
|
214
|
+
</p>
|
215
|
+
</Alert>
|
216
|
+
</Form>
|
217
|
+
{isLoading && <Spinner id="ak-subscriptions-spinner" />}
|
218
|
+
</div>
|
219
|
+
);
|
220
|
+
};
|
221
|
+
|
222
|
+
export default ActivationKeysSearch;
|
data/webpack/global_index.js
CHANGED
@@ -2,6 +2,7 @@ import React from 'react';
|
|
2
2
|
import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
|
3
3
|
import { registerReducer } from 'foremanReact/common/MountingService';
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
import componentRegistry from 'foremanReact/components/componentRegistry';
|
5
6
|
|
6
7
|
import SystemStatuses from './components/extensions/about';
|
7
8
|
import {
|
@@ -30,6 +31,9 @@ import HostsIndexActionsBar from './components/extensions/Hosts/ActionsBar';
|
|
30
31
|
import RecentCommunicationCardExtensions from './components/extensions/HostDetails/DetailsTabCards/RecentCommunicationCardExtensions';
|
31
32
|
import SystemPurposeCard from './components/extensions/HostDetails/Cards/SystemPurposeCard/SystemPurposeCard';
|
32
33
|
|
34
|
+
|
35
|
+
import ActivationKeysSearch from './components/ActivationKeysSearch';
|
36
|
+
|
33
37
|
registerReducer('katelloExtends', extendReducer);
|
34
38
|
registerReducer('katello', rootReducer);
|
35
39
|
|
@@ -80,3 +84,9 @@ addGlobalFill(
|
|
80
84
|
);
|
81
85
|
|
82
86
|
addGlobalFill('host-tab-details-cards', 'HW properties', <HwPropertiesCard key="hw-properties" />, 200);
|
87
|
+
|
88
|
+
componentRegistry.register({
|
89
|
+
name: 'ActivationKeysSearch',
|
90
|
+
type: ActivationKeysSearch,
|
91
|
+
});
|
92
|
+
|
@@ -5,7 +5,6 @@ import {
|
|
5
5
|
Bullseye, Split, SplitItem, Button, ActionList,
|
6
6
|
ActionListItem, Dropdown, DropdownItem, KebabToggle,
|
7
7
|
} from '@patternfly/react-core';
|
8
|
-
import { Link } from 'react-router-dom';
|
9
8
|
import { TableVariant, fitContent, TableText } from '@patternfly/react-table';
|
10
9
|
import { PencilAltIcon } from '@patternfly/react-icons';
|
11
10
|
import { STATUS } from 'foremanReact/constants';
|
@@ -136,7 +135,7 @@ const ContentViewComponents = ({ cvId, details }) => {
|
|
136
135
|
id: componentCvId, content_view: cv, content_view_version: cvVersion,
|
137
136
|
latest, component_content_view_versions: componentCvVersions,
|
138
137
|
} = componentCV;
|
139
|
-
const { environments, repositories } = cvVersion || {};
|
138
|
+
const { environments, repositories, id: cvVersionId } = cvVersion || {};
|
140
139
|
const {
|
141
140
|
id,
|
142
141
|
name,
|
@@ -171,7 +170,11 @@ const ContentViewComponents = ({ cvId, details }) => {
|
|
171
170
|
</Split>),
|
172
171
|
},
|
173
172
|
{ title: environments ? <ComponentEnvironments {...{ environments }} /> : <InactiveText text={__('Not yet published')} /> },
|
174
|
-
{
|
173
|
+
{
|
174
|
+
title: cvVersionId ?
|
175
|
+
<a href={urlBuilder(`content_views/${id}#/versions/${cvVersionId}/repositories`, '')}>{repositories ? repositories.length : 0}</a> :
|
176
|
+
0,
|
177
|
+
},
|
175
178
|
{
|
176
179
|
title: <AddedStatusLabel added={!!componentCvId} />,
|
177
180
|
},
|
@@ -66,7 +66,7 @@ const CVPublishForm = ({
|
|
66
66
|
</Alert>)
|
67
67
|
}
|
68
68
|
{!duplicateReposAlertDismissed && composite &&
|
69
|
-
(duplicateRepos !== null
|
69
|
+
(duplicateRepos !== null && duplicateRepos.length > 0) &&
|
70
70
|
(
|
71
71
|
<Alert
|
72
72
|
ouiaId="duplicate-repos-alert"
|
@@ -1,6 +1,9 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import * as reactRedux from 'react-redux';
|
3
3
|
import { renderWithRedux, patientlyWaitFor, fireEvent, act } from 'react-testing-lib-wrapper';
|
4
|
+
import { screen } from '@testing-library/react';
|
5
|
+
import '@testing-library/jest-dom';
|
6
|
+
|
4
7
|
import { nockInstance, assertNockRequest } from '../../../../test-utils/nockWrapper';
|
5
8
|
import api from '../../../../services/api';
|
6
9
|
import PublishContentViewWizard from '../PublishContentViewWizard';
|
@@ -80,6 +83,33 @@ test('Can show wizard with duplicate repository warning for composite CV', async
|
|
80
83
|
assertNockRequest(filterScope, done);
|
81
84
|
});
|
82
85
|
|
86
|
+
test('Can show wizard without duplicate repository warning for composite CV', async (done) => {
|
87
|
+
const cvCompositeDetailsData = cvDetailData;
|
88
|
+
cvCompositeDetailsData.composite = true;
|
89
|
+
cvCompositeDetailsData.duplicate_repositories_to_publish = [];
|
90
|
+
const scope = nockInstance
|
91
|
+
.get(environmentPathsPath)
|
92
|
+
.query(true)
|
93
|
+
.reply(200, environmentPathsData);
|
94
|
+
const filterScope = nockInstance
|
95
|
+
.get(cvFiltersPath)
|
96
|
+
.reply(200, contentViewFilterData);
|
97
|
+
|
98
|
+
const { getByText } = renderWithRedux(<PublishContentViewWizard
|
99
|
+
details={cvCompositeDetailsData}
|
100
|
+
show
|
101
|
+
onClose={() => { }}
|
102
|
+
/>);
|
103
|
+
|
104
|
+
await patientlyWaitFor(() => {
|
105
|
+
expect(getByText('Publish new version - 6.0')).toBeInTheDocument();
|
106
|
+
expect(screen.queryByText('Repositories common to the selected content view versions will merge, resulting in a composite content view that is a union of all content from each of the content view versions.')).not.toBeInTheDocument();
|
107
|
+
});
|
108
|
+
|
109
|
+
assertNockRequest(scope);
|
110
|
+
assertNockRequest(filterScope, done);
|
111
|
+
});
|
112
|
+
|
83
113
|
test('Can show Wizard and show environment paths', async (done) => {
|
84
114
|
const scope = nockInstance
|
85
115
|
.get(environmentPathsPath)
|
@@ -114,7 +114,7 @@ const ContentSourceForm = ({
|
|
114
114
|
const hostCount = contentHosts.length;
|
115
115
|
|
116
116
|
const handleCSSelect = (_event, selection) => {
|
117
|
-
handleContentSource(selection);
|
117
|
+
handleContentSource(typeof selection === 'number' ? selection.toString() : selection);
|
118
118
|
setCSSelectOpen(false);
|
119
119
|
};
|
120
120
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: katello
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.12.
|
4
|
+
version: 4.12.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- N/A
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -535,7 +535,6 @@ files:
|
|
535
535
|
- app/assets/javascripts/katello/common/spin.min.js
|
536
536
|
- app/assets/javascripts/katello/common/vendor.js
|
537
537
|
- app/assets/javascripts/katello/containers/container.js
|
538
|
-
- app/assets/javascripts/katello/hosts/activation_key_edit.js
|
539
538
|
- app/assets/javascripts/katello/hosts/host_and_hostgroup_edit.js
|
540
539
|
- app/assets/javascripts/katello/html5/excanvas.js
|
541
540
|
- app/assets/javascripts/katello/html5/html5.js
|
@@ -4515,6 +4514,8 @@ files:
|
|
4515
4514
|
- webpack/__mocks__/react-intl/locale-data/en.js
|
4516
4515
|
- webpack/common_index.js
|
4517
4516
|
- webpack/components/ActionableDetail.js
|
4517
|
+
- webpack/components/ActivationKeysSearch/ActivationKeysSearch.test.js
|
4518
|
+
- webpack/components/ActivationKeysSearch/index.js
|
4518
4519
|
- webpack/components/AddedStatusLabel.js
|
4519
4520
|
- webpack/components/Bookmark/AddBookmarkModal.js
|
4520
4521
|
- webpack/components/Bookmark/Bookmark.scss
|
@@ -5398,11 +5399,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
5398
5399
|
version: '4'
|
5399
5400
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
5400
5401
|
requirements:
|
5401
|
-
- - "
|
5402
|
+
- - ">="
|
5402
5403
|
- !ruby/object:Gem::Version
|
5403
|
-
version:
|
5404
|
+
version: '0'
|
5404
5405
|
requirements: []
|
5405
|
-
rubygems_version: 3.
|
5406
|
+
rubygems_version: 3.5.9
|
5406
5407
|
signing_key:
|
5407
5408
|
specification_version: 4
|
5408
5409
|
summary: Content and Subscription Management plugin for Foreman
|
@@ -1,167 +0,0 @@
|
|
1
|
-
var KT = KT ? KT : {};
|
2
|
-
KT.hosts = KT.hosts || {};
|
3
|
-
|
4
|
-
KT.KT_AK_LABEL = 'kt_activation_keys';
|
5
|
-
KT.hosts.availableActivationKeys = {};
|
6
|
-
|
7
|
-
function ktLoadActivationKeys() {
|
8
|
-
if(getSelectedEnvId() && getSelectedContentViewId()) {
|
9
|
-
ktAkTab().show();
|
10
|
-
} else {
|
11
|
-
ktAkTab().hide();
|
12
|
-
return; //no Katello-specific env selected
|
13
|
-
}
|
14
|
-
|
15
|
-
$("#ak-load-error").hide();
|
16
|
-
$("#ak-subscriptions-info").hide();
|
17
|
-
$("#ak-subscriptions-spinner").show();
|
18
|
-
|
19
|
-
// Retrieve the activation keys associated with the current
|
20
|
-
// environment & content view.
|
21
|
-
$.ajax({
|
22
|
-
type: 'get',
|
23
|
-
url: tfm.tools.foremanUrl('/katello/api/v2/environments/' + getSelectedEnvId() + '/activation_keys'),
|
24
|
-
data: {'content_view_id': getSelectedContentViewId()},
|
25
|
-
success: function(response) {
|
26
|
-
KT.hosts.availableActivationKeys = {};
|
27
|
-
$.each(response['results'], function (i, key) {
|
28
|
-
KT.hosts.availableActivationKeys[key.name] = [];
|
29
|
-
});
|
30
|
-
tfm.typeAheadSelect.updateOptions(Object.keys(KT.hosts.availableActivationKeys), KT.KT_AK_LABEL);
|
31
|
-
},
|
32
|
-
error: function() {
|
33
|
-
$("#ak-load-error").show();
|
34
|
-
},
|
35
|
-
});
|
36
|
-
}
|
37
|
-
|
38
|
-
function ktFindParamContainer(name){
|
39
|
-
var ret;
|
40
|
-
$("div#parameters .fields input[ type = 'text']").each(function () {
|
41
|
-
var element = $(this);
|
42
|
-
if(element.val() == name) {
|
43
|
-
ret = element.closest('.fields');
|
44
|
-
return false;
|
45
|
-
}
|
46
|
-
return true;
|
47
|
-
});
|
48
|
-
return ret;
|
49
|
-
}
|
50
|
-
|
51
|
-
function ktHideParams() {
|
52
|
-
var param = ktFindParamContainer(KT.KT_AK_LABEL);
|
53
|
-
if(param) {
|
54
|
-
param.hide();
|
55
|
-
}
|
56
|
-
}
|
57
|
-
|
58
|
-
function getSelectedEnvId() {
|
59
|
-
var dataId = $("#hostgroup_lifecycle_environment_id > option:selected").data("id");
|
60
|
-
if (dataId === undefined) {
|
61
|
-
dataId = $("#hostgroup_lifecycle_environment_id").val();
|
62
|
-
}
|
63
|
-
return dataId;
|
64
|
-
}
|
65
|
-
|
66
|
-
function getSelectedContentViewId() {
|
67
|
-
var dataId = $("#hostgroup_content_view_id > option:selected").data("id");
|
68
|
-
if (dataId === undefined) {
|
69
|
-
dataId = $("#hostgroup_content_view_id").val();
|
70
|
-
}
|
71
|
-
return dataId;
|
72
|
-
}
|
73
|
-
|
74
|
-
function ktSetParam(name, value) {
|
75
|
-
var paramContainer = ktFindParamContainer(name);
|
76
|
-
if(value) {
|
77
|
-
if(! paramContainer) { // we create the param for kt_activation_keys
|
78
|
-
var addParameterButton = $('#parameters').find('.btn-primary');
|
79
|
-
addParameterButton.click();
|
80
|
-
var directionOfAddedItems = addParameterButton.attr('direction');
|
81
|
-
var paramContainer = $('#parameters').find('.fields');
|
82
|
-
if(directionOfAddedItems === 'append'){
|
83
|
-
paramContainer = paramContainer.last();
|
84
|
-
} else {
|
85
|
-
paramContainer = paramContainer.first();
|
86
|
-
}
|
87
|
-
paramContainer.find("input[name*='name']").val(name);
|
88
|
-
}
|
89
|
-
paramContainer.find("textarea").val(value);
|
90
|
-
paramContainer.find("input[ type = 'hidden' ]").val(0);
|
91
|
-
} else if(paramContainer) {
|
92
|
-
// we remove the param by setting destroy to 1
|
93
|
-
paramContainer.find("input[ type = 'hidden' ]").val(1);
|
94
|
-
}
|
95
|
-
}
|
96
|
-
|
97
|
-
function ktAkGetKeysFromParam() {
|
98
|
-
var paramContainer = ktFindParamContainer(KT.KT_AK_LABEL);
|
99
|
-
var keys = [];
|
100
|
-
if(paramContainer) {
|
101
|
-
keys = paramContainer.find("textarea").val().split(',').map(function(key) {
|
102
|
-
return key.trim();
|
103
|
-
});
|
104
|
-
}
|
105
|
-
return keys;
|
106
|
-
}
|
107
|
-
|
108
|
-
function ktAkUpdateSubscriptionsInfo(selectedKeys) {
|
109
|
-
var subsInfo = $("ul#ak-subscriptions-info");
|
110
|
-
subsInfo.empty();
|
111
|
-
$.each(selectedKeys, function(i, key) {
|
112
|
-
if(KT.hosts.availableActivationKeys[key]) {
|
113
|
-
// hack to make it working with deface
|
114
|
-
var ul = "<ul>", ul_end = "</ul>", li = "<li>", li_end = "</li>";
|
115
|
-
content = li + key + ul;
|
116
|
-
if(!KT.hosts.availableActivationKeys[key].length == 0) {
|
117
|
-
content += li;
|
118
|
-
content += KT.hosts.availableActivationKeys[key].join(li_end + li);
|
119
|
-
content += li_end;
|
120
|
-
}
|
121
|
-
content += ul_end + li_end;
|
122
|
-
subsInfo.append(content);
|
123
|
-
}
|
124
|
-
});
|
125
|
-
$("#ak-subscriptions-info").show();
|
126
|
-
$("#ak-subscriptions-spinner").hide();
|
127
|
-
}
|
128
|
-
|
129
|
-
function ktAkTab() {
|
130
|
-
return $('li#activation_keys_tab');
|
131
|
-
}
|
132
|
-
|
133
|
-
function ktOnLoad() {
|
134
|
-
tfm.store.observeStore('typeAheadSelect', function(items, unsubscribe) {
|
135
|
-
if (items.kt_activation_keys) { // Wait until after initialization to subscribe to store changes
|
136
|
-
unsubscribe();
|
137
|
-
|
138
|
-
tfm.typeAheadSelect.updateSelected(ktAkGetKeysFromParam(), KT.KT_AK_LABEL);
|
139
|
-
|
140
|
-
tfm.store.observeStore('typeAheadSelect', function(items) {
|
141
|
-
if (items.kt_activation_keys) {
|
142
|
-
var selected = items.kt_activation_keys.selected || [];
|
143
|
-
|
144
|
-
ktAkUpdateSubscriptionsInfo(selected);
|
145
|
-
ktSetParam(KT.KT_AK_LABEL, selected.map(function(key) {
|
146
|
-
return key.trim();
|
147
|
-
}).join(','));
|
148
|
-
}
|
149
|
-
});
|
150
|
-
}
|
151
|
-
});
|
152
|
-
|
153
|
-
ktHideParams();
|
154
|
-
ktLoadActivationKeys();
|
155
|
-
}
|
156
|
-
|
157
|
-
$(document).on('ContentLoad', function(){
|
158
|
-
ktOnLoad();
|
159
|
-
|
160
|
-
$("#hostgroup_lifecycle_environment_id").change(ktLoadActivationKeys);
|
161
|
-
$("#hostgroup_content_view_id").change(ktLoadActivationKeys);
|
162
|
-
|
163
|
-
$("#ak_refresh_subscriptions").click(function () {
|
164
|
-
ktLoadActivationKeys();
|
165
|
-
return false;
|
166
|
-
});
|
167
|
-
});
|