foreman_resource_quota 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +619 -0
- data/README.md +51 -0
- data/Rakefile +49 -0
- data/app/controllers/concerns/foreman/controller/parameters/resource_quota.rb +28 -0
- data/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb +96 -0
- data/app/controllers/foreman_resource_quota/application_controller.rb +9 -0
- data/app/controllers/foreman_resource_quota/resource_quotas_controller.rb +50 -0
- data/app/helpers/foreman_resource_quota/hosts_helper.rb +18 -0
- data/app/helpers/foreman_resource_quota/resource_quota_helper.rb +107 -0
- data/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb +115 -0
- data/app/models/concerns/foreman_resource_quota/user_extensions.rb +15 -0
- data/app/models/concerns/foreman_resource_quota/usergroup_extensions.rb +14 -0
- data/app/models/foreman_resource_quota/resource_quota.rb +83 -0
- data/app/models/foreman_resource_quota/resource_quota_user.rb +10 -0
- data/app/models/foreman_resource_quota/resource_quota_usergroup.rb +10 -0
- data/app/services/foreman_resource_quota/resource_origin.rb +97 -0
- data/app/services/foreman_resource_quota/resource_origins/compute_attributes_origin.rb +64 -0
- data/app/services/foreman_resource_quota/resource_origins/compute_resource_origin.rb +82 -0
- data/app/services/foreman_resource_quota/resource_origins/facts_origin.rb +68 -0
- data/app/services/foreman_resource_quota/resource_origins/vm_attributes_origin.rb +40 -0
- data/app/views/foreman_resource_quota/api/v2/hosts/resource_quota.json.rabl +3 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/base.json.rabl +6 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/create.json.rabl +5 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/hosts.json.rabl +7 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/index.json.rabl +5 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/main.json.rabl +7 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/show.json.rabl +5 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/update.json.rabl +5 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/usergroups.json.rabl +7 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/users.json.rabl +7 -0
- data/app/views/foreman_resource_quota/api/v2/resource_quotas/utilization.json.rabl +7 -0
- data/app/views/foreman_resource_quota/api/v2/usergroups/resource_quota.json.rabl +3 -0
- data/app/views/foreman_resource_quota/api/v2/users/resource_quota.json.rabl +3 -0
- data/app/views/foreman_resource_quota/resource_quotas/_form.html.erb +21 -0
- data/app/views/foreman_resource_quota/resource_quotas/edit.html.erb +12 -0
- data/app/views/foreman_resource_quota/resource_quotas/index.html.erb +55 -0
- data/app/views/foreman_resource_quota/resource_quotas/new.html.erb +10 -0
- data/app/views/foreman_resource_quota/resource_quotas/welcome.html.erb +10 -0
- data/app/views/hosts/_form_quota_fields.html.erb +4 -0
- data/app/views/users/_form_quota_tab.html.erb +45 -0
- data/config/initializers/inflections.rb +5 -0
- data/config/routes.rb +43 -0
- data/db/migrate/20230306120001_create_resource_quotas.rb +31 -0
- data/lib/foreman_resource_quota/engine.rb +56 -0
- data/lib/foreman_resource_quota/exceptions.rb +11 -0
- data/lib/foreman_resource_quota/register.rb +106 -0
- data/lib/foreman_resource_quota/version.rb +5 -0
- data/lib/foreman_resource_quota.rb +6 -0
- data/lib/tasks/foreman_resource_quota_tasks.rake +50 -0
- data/locale/Makefile +60 -0
- data/locale/en/foreman_resource_quota.po +18 -0
- data/locale/foreman_resource_quota.pot +19 -0
- data/locale/gemspec.rb +4 -0
- data/package.json +44 -0
- data/webpack/api_helper.js +113 -0
- data/webpack/api_helper.test.js +96 -0
- data/webpack/components/CreateResourceQuotaModal.js +46 -0
- data/webpack/components/ResourceQuotaEmptyState/index.js +58 -0
- data/webpack/components/ResourceQuotaForm/ResourceQuotaForm.scss +1 -0
- data/webpack/components/ResourceQuotaForm/ResourceQuotaFormConstants.js +71 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/Properties.scss +9 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/StaticDetail.js +72 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/StatusPropertiesLabel.js +71 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/StatusPropertiesLabel.test.js +50 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/TextInputField.js +131 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/index.js +190 -0
- data/webpack/components/ResourceQuotaForm/components/QuotaState.js +157 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/Resource.scss +13 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/UnitInputField.js +224 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.js +151 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.scss +10 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/index.js +239 -0
- data/webpack/components/ResourceQuotaForm/components/Resources.js +105 -0
- data/webpack/components/ResourceQuotaForm/components/Submit.js +72 -0
- data/webpack/components/ResourceQuotaForm/index.js +185 -0
- data/webpack/components/UpdateResourceQuotaModal.js +143 -0
- data/webpack/global_index.js +15 -0
- data/webpack/global_test_setup.js +11 -0
- data/webpack/helper.js +86 -0
- data/webpack/index.js +23 -0
- data/webpack/lib/ActionableDetail.js +115 -0
- data/webpack/lib/ActionableDetail.scss +4 -0
- data/webpack/lib/EditableSwitch.js +47 -0
- data/webpack/lib/EditableTextInput/EditableTextInput.js +206 -0
- data/webpack/lib/EditableTextInput/PencilEditButton.js +27 -0
- data/webpack/lib/EditableTextInput/__tests__/editableTextInput.test.js +193 -0
- data/webpack/lib/EditableTextInput/editableTextInput.scss +38 -0
- data/webpack/lib/EditableTextInput/index.js +4 -0
- data/webpack/lib/react-testing-lib-wrapper.js +80 -0
- data/webpack/test_setup.js +17 -0
- metadata +134 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanResourceQuota
|
4
|
+
class ResourceOrigin
|
5
|
+
RESOURCE_FUNCTION_MAP = {
|
6
|
+
cpu_cores: :extract_cpu_cores,
|
7
|
+
memory_mb: :extract_memory_mb,
|
8
|
+
disk_gb: :extract_disk_gb,
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
def collect_resources!(resources_sum, missing_hosts_resources, host_objects)
|
12
|
+
return if missing_hosts_resources.empty?
|
13
|
+
|
14
|
+
relevant_hosts = load_hosts_eagerly(missing_hosts_resources, host_objects, host_attribute_eager_name)
|
15
|
+
host_values = collect_attribute_from_hosts(relevant_hosts, host_attribute_name)
|
16
|
+
sum_resources_and_delete_missing_hosts!(resources_sum, missing_hosts_resources, host_values)
|
17
|
+
end
|
18
|
+
|
19
|
+
def host_attribute_eager_name
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def host_attribute_name
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def extract_cpu_cores(param)
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def extract_memory_mb(param)
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_disk_gb(param)
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def load_hosts_eagerly(missing_hosts_resources, host_objects, eager_attribute)
|
42
|
+
relevant_hosts = Host::Managed.where(name: missing_hosts_resources.keys).includes(eager_attribute)
|
43
|
+
relevant_hosts = relevant_hosts.compact
|
44
|
+
if relevant_hosts.size < missing_hosts_resources.size # Add non-eagerly loaded host objects
|
45
|
+
relevant_hosts_names = relevant_hosts.map(&:name)
|
46
|
+
(missing_hosts_resources.keys - relevant_hosts_names).each do |missing_host_name|
|
47
|
+
relevant_hosts << host_objects[missing_host_name]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
relevant_hosts
|
51
|
+
rescue ActiveRecord::AssociationNotFoundError
|
52
|
+
# the eager_attribute could not be loaded
|
53
|
+
host_objects.values
|
54
|
+
end
|
55
|
+
|
56
|
+
def collect_attribute_from_hosts(host_list, attribute_name)
|
57
|
+
host_values = {}
|
58
|
+
host_list.each do |host|
|
59
|
+
attribute_value = host.send(attribute_name)
|
60
|
+
host_values[host.name] = attribute_value if attribute_value.present?
|
61
|
+
rescue StandardError
|
62
|
+
# skip hosts whose attribute couldn't be collected. They will be kept in the list
|
63
|
+
# of missing host resources
|
64
|
+
end
|
65
|
+
host_values
|
66
|
+
end
|
67
|
+
|
68
|
+
def sum_resources_and_delete_missing_hosts!(resources_sum, missing_hosts_resources, host_values)
|
69
|
+
host_values.each do |host_name, attribute_content|
|
70
|
+
missing_hosts_resources[host_name].reverse_each do |resource_name|
|
71
|
+
resource_value = process_resource(resource_name, attribute_content)
|
72
|
+
next unless resource_value
|
73
|
+
resources_sum[resource_name] += resource_value
|
74
|
+
missing_hosts_resources[host_name].delete(resource_name)
|
75
|
+
end
|
76
|
+
missing_hosts_resources.delete(host_name) if missing_hosts_resources[host_name].empty?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def process_resource(resource_name, attribute_content)
|
81
|
+
resource_value = method(RESOURCE_FUNCTION_MAP[resource_name]).call(attribute_content)
|
82
|
+
return nil unless resource_value
|
83
|
+
resource_value
|
84
|
+
end
|
85
|
+
|
86
|
+
# Extract encapsulated volumes attributes
|
87
|
+
# The structure of data encapslated in :volumes_attributes is inconsistent. For example,
|
88
|
+
# in an API call for host creation call this is an Array. But, through the UI this becomes a Hash.
|
89
|
+
# Parameters: A Hash containing :volumes_attributes.
|
90
|
+
# Returns: An Array of volume data (expecting an Array of Hashes)
|
91
|
+
def extract_volumes(param)
|
92
|
+
volumes = param[:volumes_attributes]
|
93
|
+
volumes = volumes.values if volumes.is_a?(Hash) # If it's a Hash, extract values
|
94
|
+
volumes
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanResourceQuota
|
4
|
+
module ResourceOrigins
|
5
|
+
class ComputeAttributesOrigin < ResourceOrigin
|
6
|
+
def host_attribute_eager_name
|
7
|
+
:compute_attributes
|
8
|
+
end
|
9
|
+
|
10
|
+
def host_attribute_name
|
11
|
+
:compute_attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
def extract_cpu_cores(param)
|
15
|
+
return nil unless param.key?(:cpus)
|
16
|
+
param[:cpus].to_i
|
17
|
+
rescue StandardError
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def extract_memory_mb(param)
|
22
|
+
case determine_memory_key(param)
|
23
|
+
when :memory
|
24
|
+
param[:memory].to_i / ResourceQuotaHelper::FACTOR_B_TO_MB
|
25
|
+
when :memory_mb
|
26
|
+
param[:memory_mb].to_i
|
27
|
+
end
|
28
|
+
rescue StandardError
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def extract_disk_gb(param)
|
33
|
+
return nil unless param.key?(:volumes_attributes)
|
34
|
+
extract_volumes(param).sum do |disk|
|
35
|
+
# key can be capactiy or size_gb
|
36
|
+
return nil unless disk.key?(:capacity) || disk.key?(:size_gb)
|
37
|
+
(disk[:size_gb] || disk[:capacity]).to_i
|
38
|
+
end
|
39
|
+
rescue StandardError
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def determine_memory_key(param)
|
46
|
+
return :memory if param.key?(:memory)
|
47
|
+
return :memory_mb if param.key?(:memory_mb)
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse_storage_string(storage_str)
|
52
|
+
return nil unless storage_str.is_a? String
|
53
|
+
case storage_str[-1].upcase
|
54
|
+
when 'G'
|
55
|
+
storage_str.to_i
|
56
|
+
when 'T'
|
57
|
+
storage_str[0..-2].to_i * ResourceQuotaHelper::FACTOR_B_TO_MB
|
58
|
+
when 'M'
|
59
|
+
(storage_str[0..-2].to_f / ResourceQuotaHelper::FACTOR_B_TO_MB).ceil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanResourceQuota
|
4
|
+
module ResourceOrigins
|
5
|
+
class ComputeResourceOrigin < ResourceOrigin
|
6
|
+
def extract_cpu_cores(param)
|
7
|
+
param.cpus
|
8
|
+
rescue StandardError
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def extract_memory_mb(param)
|
13
|
+
param.memory.to_i / ResourceQuotaHelper::FACTOR_B_TO_MB
|
14
|
+
rescue StandardError
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def extract_disk_gb(_)
|
19
|
+
# FIXME: no disk given in VM (compare fog extensions)
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def collect_resources!(resources_sum, missing_hosts_resources, _host_objects)
|
24
|
+
compute_resource_to_hosts = group_hosts_by_compute_resource(missing_hosts_resources.keys)
|
25
|
+
|
26
|
+
compute_resource_to_hosts.each do |compute_resource_id, hosts|
|
27
|
+
next if compute_resource_id == :nil_compute_resource
|
28
|
+
|
29
|
+
host_vms, vm_id_attr = filter_vms_by_hosts(hosts, compute_resource_id)
|
30
|
+
next if host_vms.empty?
|
31
|
+
|
32
|
+
hosts.each do |host|
|
33
|
+
vm = host_vms.find { |obj| obj.send(vm_id_attr) == host.uuid }
|
34
|
+
next unless vm
|
35
|
+
process_host_vm!(resources_sum, missing_hosts_resources, host.name, vm)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Groups hosts with the same compute resource.
|
41
|
+
# Parameters: An array of host names.
|
42
|
+
# Returns: A hash where keys represent compute resource IDs and values are the list of host objects.
|
43
|
+
def group_hosts_by_compute_resource(host_names)
|
44
|
+
Host::Managed.where(name: host_names).includes(:compute_resource).group_by do |host|
|
45
|
+
host.compute_resource&.id || :nil_compute_resource
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Filters VMs of a compute resource, given a list of hosts.
|
50
|
+
# Parameters:
|
51
|
+
# - hosts: An array of host objects.
|
52
|
+
# - compute_resource_id: ID of the compute resource.
|
53
|
+
# Returns:
|
54
|
+
# - filtered_vms: An array of filtered virtual machine objects.
|
55
|
+
# - id_attr: Attribute used for identifying virtual machines (either :vmid or :id).
|
56
|
+
def filter_vms_by_hosts(hosts, compute_resource_id)
|
57
|
+
host_uuids = hosts.map(&:uuid)
|
58
|
+
vms = ComputeResource.find_by(id: compute_resource_id).vms.all
|
59
|
+
id_attr = vms[0].respond_to?(:vmid) ? :vmid : :id
|
60
|
+
filtered_vms = vms.select { |obj| host_uuids.include?(obj.send(id_attr)) } # reduce from all vms
|
61
|
+
[filtered_vms, id_attr]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Processes a host's virtual machines and updates resource allocation.
|
65
|
+
# Parameters:
|
66
|
+
# - resources_sum: Hash containing total resources sum.
|
67
|
+
# - missing_hosts_resources: Hash containing missing resources per host.
|
68
|
+
# - host_name: Name of the host.
|
69
|
+
# - vm: Compute resource VM object of given host.
|
70
|
+
# Returns: None.
|
71
|
+
def process_host_vm!(resources_sum, missing_hosts_resources, host_name, host_vm)
|
72
|
+
missing_hosts_resources[host_name].reverse_each do |resource_name|
|
73
|
+
resource_value = process_resource(resource_name, host_vm)
|
74
|
+
next unless resource_value
|
75
|
+
resources_sum[resource_name] += resource_value
|
76
|
+
missing_hosts_resources[host_name].delete(resource_name)
|
77
|
+
end
|
78
|
+
missing_hosts_resources.delete(host_name) if missing_hosts_resources[host_name].empty?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanResourceQuota
|
4
|
+
module ResourceOrigins
|
5
|
+
class FactsOrigin < ResourceOrigin
|
6
|
+
FACTS_KEYS_CPU_CORES = [
|
7
|
+
'ansible_processor_cores',
|
8
|
+
'facter_processors::cores',
|
9
|
+
'proc_cpuinfo::common::cpu_cores',
|
10
|
+
'processors::cores',
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
FACTS_KEYS_MEMORY_B = [
|
14
|
+
'facter_memory::system::total_bytes',
|
15
|
+
'memory::system::total_bytes',
|
16
|
+
'memory::memtotal',
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
FACTS_REGEX_DISK_B = [
|
20
|
+
/^disks::(\w+)::size_bytes$/,
|
21
|
+
/^facter_disks::(\w+)::size_bytes$/,
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
def host_attribute_eager_name
|
25
|
+
:fact_values
|
26
|
+
end
|
27
|
+
|
28
|
+
def host_attribute_name
|
29
|
+
:facts
|
30
|
+
end
|
31
|
+
|
32
|
+
def extract_cpu_cores(param)
|
33
|
+
common_keys = param.keys & FACTS_KEYS_CPU_CORES
|
34
|
+
return param[common_keys.first].to_i if common_keys.any?
|
35
|
+
nil
|
36
|
+
rescue StandardError
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_memory_mb(param)
|
41
|
+
common_keys = param.keys & FACTS_KEYS_MEMORY_B
|
42
|
+
return (param[common_keys.first].to_i / ResourceQuotaHelper::FACTOR_B_TO_MB).to_i if common_keys.any?
|
43
|
+
nil
|
44
|
+
rescue StandardError
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_disk_gb(param)
|
49
|
+
total_gb = nil
|
50
|
+
|
51
|
+
FACTS_REGEX_DISK_B.each do |regex|
|
52
|
+
relevant_keys = param.keys.grep(regex)
|
53
|
+
next unless relevant_keys.any?
|
54
|
+
|
55
|
+
total_gb = sum_disk_space(param, relevant_keys)
|
56
|
+
end
|
57
|
+
|
58
|
+
total_gb&.to_i
|
59
|
+
rescue StandardError
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def sum_disk_space(facts, keys)
|
64
|
+
keys.map { |key| facts[key].to_i }.sum / ResourceQuotaHelper::FACTOR_B_TO_GB unless keys.empty?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ForemanResourceQuota
|
4
|
+
module ResourceOrigins
|
5
|
+
class VMAttributesOrigin < ResourceOrigin
|
6
|
+
def host_attribute_eager_name
|
7
|
+
:compute_resource
|
8
|
+
end
|
9
|
+
|
10
|
+
def host_attribute_name
|
11
|
+
:vm_compute_attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
def extract_cpu_cores(param)
|
15
|
+
return nil unless param.key?(:cpus)
|
16
|
+
param[:cpus]
|
17
|
+
rescue StandardError
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def extract_memory_mb(param)
|
22
|
+
return nil unless param.key?(:memory_mb)
|
23
|
+
param[:memory_mb]
|
24
|
+
rescue StandardError
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def extract_disk_gb(param)
|
29
|
+
return nil unless param.key?(:volumes_attributes)
|
30
|
+
extract_volumes(param).sum do |disk|
|
31
|
+
# key can be capactiy or size_gb
|
32
|
+
return nil unless disk.key?(:capacity) || disk.key?(:size_gb)
|
33
|
+
(disk[:size_gb] || disk[:capacity]).to_i
|
34
|
+
end
|
35
|
+
rescue StandardError
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<%
|
2
|
+
if @resource_quota.new_record?
|
3
|
+
react_data = {
|
4
|
+
"isNewQuota": true,
|
5
|
+
}
|
6
|
+
else
|
7
|
+
react_data = {
|
8
|
+
"isNewQuota": false,
|
9
|
+
"initialProperties": {
|
10
|
+
"id": @resource_quota.id,
|
11
|
+
"name": @resource_quota.name,
|
12
|
+
"description": @resource_quota.description,
|
13
|
+
"cpu_cores": @resource_quota.cpu_cores,
|
14
|
+
"memory_mb": @resource_quota.memory_mb,
|
15
|
+
"disk_gb": @resource_quota.disk_gb,
|
16
|
+
},
|
17
|
+
}
|
18
|
+
end
|
19
|
+
%>
|
20
|
+
|
21
|
+
<%= react_component('ResourceQuotaForm', react_data) %>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<% title(_('Edit Resource Quota %s') % @resource_quota) %>
|
2
|
+
<%= breadcrumbs(resource_url: api_resource_quotas_path,
|
3
|
+
switcher_item_url: edit_resource_quota_path(':id')) %>
|
4
|
+
|
5
|
+
<% content_for(:javascripts) do %>
|
6
|
+
<%= webpacked_plugins_js_for :foreman_resource_quota %>
|
7
|
+
<% end %>
|
8
|
+
<% content_for(:stylesheets) do %>
|
9
|
+
<%= webpacked_plugins_css_for :foreman_resource_quota %>
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<%= render :partial => 'form' %>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<% content_for(:javascripts) do %>
|
2
|
+
<%= webpacked_plugins_js_for :foreman_resource_quota %>
|
3
|
+
<% end %>
|
4
|
+
<% content_for(:stylesheets) do %>
|
5
|
+
<%= webpacked_plugins_css_for :foreman_resource_quota %>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<% title _('Resource quotas') %>
|
9
|
+
|
10
|
+
<%= title_actions react_component('CreateResourceQuotaModal') %>
|
11
|
+
|
12
|
+
<table class="<%= table_css_classes 'table-bordered table-striped' %>">
|
13
|
+
<thead>
|
14
|
+
<tr>
|
15
|
+
<th><%= sort :name, :as => s_('Resource Quota|Name') %></th>
|
16
|
+
<th class="col-md-6"><%= _('Description') %></th>
|
17
|
+
<th class="col-md-1"><%= _('CPU cores') %></th>
|
18
|
+
<th class="col-md-1"><%= _('Memory (MB)') %></th>
|
19
|
+
<th class="col-md-1"><%= _('Disk space (GB)') %></th>
|
20
|
+
<th><%= _('Actions') %></th>
|
21
|
+
</tr>
|
22
|
+
</thead>
|
23
|
+
<tbody>
|
24
|
+
<% @resource_quotas.each do |quota|
|
25
|
+
react_data = {
|
26
|
+
"isNewQuota": false,
|
27
|
+
"initialProperties": {
|
28
|
+
"id": quota.id,
|
29
|
+
"name": quota.name,
|
30
|
+
"description": quota.description,
|
31
|
+
"cpu_cores": quota.cpu_cores,
|
32
|
+
"memory_mb": quota.memory_mb,
|
33
|
+
"disk_gb": quota.disk_gb,
|
34
|
+
},
|
35
|
+
}
|
36
|
+
%>
|
37
|
+
<tr>
|
38
|
+
<td class="ellipsis">
|
39
|
+
<%= react_component('UpdateResourceQuotaModal', react_data) %>
|
40
|
+
</td>
|
41
|
+
<td><%= h(quota.description) %></td>
|
42
|
+
<td><%= h(quota.cpu_cores) %></td>
|
43
|
+
<td><%= h(quota.memory_mb) %></td>
|
44
|
+
<td><%= h(quota.disk_gb) %></td>
|
45
|
+
<td>
|
46
|
+
<%= action_buttons(
|
47
|
+
display_delete_if_authorized(hash_for_foreman_resource_quota_resource_quota_path(id: quota), data: { confirm: _("Delete %s?") % quota.name})
|
48
|
+
) %>
|
49
|
+
</td>
|
50
|
+
</tr>
|
51
|
+
<% end %>
|
52
|
+
</tbody>
|
53
|
+
</table>
|
54
|
+
|
55
|
+
<%= will_paginate_with_info @resource_quotas %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<% title _('New Resource Quota') %>
|
2
|
+
|
3
|
+
<% content_for(:javascripts) do %>
|
4
|
+
<%= webpacked_plugins_js_for :foreman_resource_quota %>
|
5
|
+
<% end %>
|
6
|
+
<% content_for(:stylesheets) do %>
|
7
|
+
<%= webpacked_plugins_css_for :foreman_resource_quota %>
|
8
|
+
<% end %>
|
9
|
+
|
10
|
+
<%= render :partial => 'form' %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<% content_for(:javascripts) do %>
|
2
|
+
<%= webpacked_plugins_js_for :foreman_resource_quota %>
|
3
|
+
<% end %>
|
4
|
+
<% content_for(:stylesheets) do %>
|
5
|
+
<%= webpacked_plugins_css_for :foreman_resource_quota %>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<% content_for(:title, _("Resource Quotas")) %>
|
9
|
+
|
10
|
+
<%= react_component('ResourceQuotaEmptyState') %>
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<% resource_type ||= pagelet.opts[:resource_type] %>
|
2
|
+
<% if resource_type == :user %>
|
3
|
+
<% resource = @user %>
|
4
|
+
<% elsif resource_type == :usergroup %>
|
5
|
+
<% resource = @usergroup %>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<% if resource_type == :user %>
|
9
|
+
<%= checkbox_f form, :"resource_quota_is_optional", :label => _("Optional Assignment"),
|
10
|
+
:label_help => _("It is optional for a user to assign a quota when creating new hosts") %>
|
11
|
+
<% end %>
|
12
|
+
|
13
|
+
<%= multiple_checkboxes(form, :resource_quotas, resource, ForemanResourceQuota::ResourceQuota, :label => _("Resource Quotas")) %>
|
14
|
+
|
15
|
+
<% if resource_type == :user %>
|
16
|
+
<% usergroups = @user.cached_usergroups.includes(:resource_quotas).distinct %>
|
17
|
+
<% if usergroups.any? %>
|
18
|
+
<div class="form-group" id="inherited-quotas">
|
19
|
+
<label class="col-md-2 control-label" for="quotas"><%= _("Quotas from user groups") %></label>
|
20
|
+
<div class="col-md-5">
|
21
|
+
<div class="dropdown">
|
22
|
+
<button class="btn btn-default dropdown-toggle" type="button" id="usergroupsDropdownMenuBtn" data-toggle="dropdown">
|
23
|
+
<%= usergroups.first %>
|
24
|
+
<span class="caret"></span>
|
25
|
+
</button>
|
26
|
+
<ul class="dropdown-menu" quota="menu" aria-labelledby="usergroupsDropdownMenuBtn">
|
27
|
+
<% usergroups.each do |usergroup| %>
|
28
|
+
<li quota="presentation"><a quota="menuitem" tabindex="-1" data-id="<%=usergroup.id %>"><%= usergroup %></a></li>
|
29
|
+
<% end %>
|
30
|
+
</ul>
|
31
|
+
</div>
|
32
|
+
<ul class="list-group" id='quotas_tab'>
|
33
|
+
<% usergroups.each do |usergroup| %>
|
34
|
+
<% unless usergroup.resource_quotas.map(&:name).any? %>
|
35
|
+
<li data-id="<%= usergroup.id %>" class="list-group-item"><%= _('This group has no quotas') %></li>
|
36
|
+
<%end %>
|
37
|
+
<% usergroup.resource_quotas.map(&:name).each do |quota_name| %>
|
38
|
+
<li data-id="<%= usergroup.id %>" class="list-group-item"><%= quota_name %></li>
|
39
|
+
<% end %>
|
40
|
+
<% end %>
|
41
|
+
</ul>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
<% end %>
|
45
|
+
<% end %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop: disable Metrics/BlockLength
|
4
|
+
Rails.application.routes.draw do
|
5
|
+
scope :foreman_resource_quota, path: 'foreman_resource_quota' do
|
6
|
+
resources :resource_quotas, except: %i[show], controller: 'foreman_resource_quota/resource_quotas' do
|
7
|
+
collection do
|
8
|
+
get 'auto_complete_search'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
namespace :foreman_resource_quota do
|
14
|
+
resources :resource_quotas, except: %i[show] do
|
15
|
+
collection do
|
16
|
+
get 'help', action: :welcome
|
17
|
+
get 'auto_complete_search'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# API routes
|
22
|
+
namespace :api, defaults: { format: 'json' } do
|
23
|
+
scope '(:apiv)',
|
24
|
+
module: :v2,
|
25
|
+
defaults: { apiv: 'v2' },
|
26
|
+
apiv: /v1|v2/,
|
27
|
+
constraints: ApiConstraints.new(version: 2, default: true) do
|
28
|
+
resources :resource_quotas, except: %i[new edit] do
|
29
|
+
collection do
|
30
|
+
get 'auto_complete_search'
|
31
|
+
end
|
32
|
+
constraints(id: %r{[^/]+}) do
|
33
|
+
get 'utilization'
|
34
|
+
get 'hosts'
|
35
|
+
get 'users'
|
36
|
+
get 'usergroups'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
# rubocop: enable Metrics/BlockLength
|