foreman_resource_quota 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb +1 -1
  4. data/app/controllers/foreman_resource_quota/concerns/api/v2/hosts_controller_extensions.rb +21 -0
  5. data/app/helpers/foreman_resource_quota/hosts_helper.rb +18 -7
  6. data/app/lib/foreman_resource_quota/exceptions.rb +1 -0
  7. data/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb +9 -10
  8. data/app/models/concerns/foreman_resource_quota/user_extensions.rb +27 -0
  9. data/app/models/concerns/foreman_resource_quota/usergroup_extensions.rb +18 -0
  10. data/app/models/foreman_resource_quota/resource_quota.rb +23 -0
  11. data/app/views/foreman_resource_quota/resource_quotas/index.html.erb +9 -1
  12. data/app/views/hosts/_form_quota_fields.html.erb +14 -2
  13. data/app/views/users/_form_quota_tab.html.erb +2 -2
  14. data/db/migrate/20240611141939_drop_missing_hosts.rb +9 -2
  15. data/db/migrate/20250410082728_add_unassigned_flag_to_resource_quota.rb +7 -0
  16. data/db/seeds.d/030-unassigned_quota.rb +36 -0
  17. data/lib/foreman_resource_quota/register.rb +8 -0
  18. data/lib/foreman_resource_quota/version.rb +1 -1
  19. data/lib/tasks/foreman_resource_quota_tasks.rake +4 -5
  20. data/package.json +4 -5
  21. data/webpack/components/CreateResourceQuotaModal.js +1 -0
  22. data/webpack/components/ResourceQuotaEmptyState/__test__/__snapshots__/ResourceQuotaEmptyState.test.js.snap +3 -0
  23. data/webpack/components/ResourceQuotaEmptyState/index.js +1 -0
  24. data/webpack/components/ResourceQuotaForm/ResourceQuotaFormConstants.js +1 -0
  25. data/webpack/components/ResourceQuotaForm/components/Properties/StaticDetail.js +1 -0
  26. data/webpack/components/ResourceQuotaForm/components/Properties/TextInputField.js +4 -1
  27. data/webpack/components/ResourceQuotaForm/components/Properties/index.js +48 -21
  28. data/webpack/components/ResourceQuotaForm/components/Resource/UnitInputField.js +5 -1
  29. data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.js +13 -12
  30. data/webpack/components/ResourceQuotaForm/components/Resource/__test__/__snapshots__/UnitInputField.test.js.snap +14 -3
  31. data/webpack/components/ResourceQuotaForm/components/Resource/index.js +4 -0
  32. data/webpack/components/ResourceQuotaForm/components/Submit.js +2 -1
  33. data/webpack/components/ResourceQuotaForm/index.js +33 -19
  34. data/webpack/components/UpdateResourceQuotaModal.js +7 -1
  35. metadata +5 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf7fe8ad234e899887460b9e87041b4e2d05f6eaad6540a9567110326dc6ec7b
4
- data.tar.gz: 100ee9af0fd01a0f7d026cafaae6ef1faf9b1e2897952bdb935ae896ed948dca
3
+ metadata.gz: e395e2dfdbaf6a690ee47ed3885daa6d1bf02d66fa9ab4ddc9616214913e1bbe
4
+ data.tar.gz: c6804f4c3ac2245a891f54ddbec65b5736983466c05e18fedc3d0b6ff926c932
5
5
  SHA512:
6
- metadata.gz: 8511f2e3460efa522ced8b3759555755fc2e4bd44613f68d74266a103a32bc54beafe75982ad58d9263264fd1c77102b44f0eb9564abdf4c972813f7ecbd4135
7
- data.tar.gz: 5bd4034854f07d0cc7600864c84904a9295c49f4f0142880c6c5c37855d9fd3b77834dad065fdcf1ffc481a7a2dc7a40c5724e8861896352e349c72440f0c2e6
6
+ metadata.gz: 54de1fc3feda746af897c9161a3079f12e6e0be2ad9ef372ab39a9987cb86866fc7216bd5df13bcc477beb8ec8587be81bd95f858e65e05f034a22d78e88fa93
7
+ data.tar.gz: '09bf9301c5b261f83e5ca90d78ae0d38b1eff943c0c680d5c8fbd6eeca080f18c8e744f29a58f753b42f35c5c1bd52f04f0a1a562384ebdfa07dd96796cc31f0'
data/README.md CHANGED
@@ -14,20 +14,20 @@ For more information, see [Limiting host resources](https://docs.theforeman.org/
14
14
 
15
15
  | Foreman Version | Plugin Version |
16
16
  | --------------- | -------------- |
17
- | 3.11 | 0.3.1 |
17
+ | 3.15 | 0.5.0 |
18
+ | 3.14 | 0.4.0 |
19
+ | 3.13 | ~> 0.3.0 |
18
20
  | 3.5 | 0.0.1 |
19
21
 
20
22
  ## Usage
21
23
 
22
- _TODO_ Still under development: Official documentation will be added soon.
23
-
24
24
  When several users share a compute resource or infrastructure, there is a concern that some users could use more than its fair share of resources. Resource Quotas are a tool for administrators to address this concern. They limit access to the shared resource in order to guarantee a fair collaboration.
25
25
 
26
26
  In the context of Foreman, multiple users or groups usually share a fixed number of resources (limitation of compute resources like CPU cores, memory, and disk space). As of now, a user cannot be limited when allocating resources. They can create hosts with as many resources as they want. This could lead to over-usage or unequal balancing of resources under the users.
27
27
 
28
28
  This plugin introduces the configuration of Resource Quotas. A quota limits specific resources and can be applied to a user or a user group. If a user belongs to a user group, the group’s quota is automatically applied to the user as well. When deploying a new host, a user has to choose a Resource Quota that the host counts to.
29
29
 
30
- A user is hindered from deploying new hosts, if the new host would exceed the corresponding quota limits. In case, a user belongs to multiple user group with quota, the user can determine which quota new hosts belong to.
30
+ A user is hindered from deploying new hosts, if the new host would exceed the corresponding quota limits. In case, a user belongs to multiple user group with quota, the user can determine which quota new hosts belong to.
31
31
 
32
32
 
33
33
  ## Contributing
@@ -42,7 +42,7 @@ Fork and send a Pull Request. Thanks!
42
42
 
43
43
  ## Copyright
44
44
 
45
- Copyright (c) 2023 ATIX AG - https://atix.de
45
+ Copyright (c) 2025 ATIX AG - https://atix.de
46
46
 
47
47
  This program is free software: you can redistribute it and/or modify
48
48
  it under the terms of the GNU General Public License as published by
@@ -40,7 +40,7 @@ module ForemanResourceQuota
40
40
  end
41
41
 
42
42
  api :GET, '/resource_quotas/:id/missing_hosts',
43
- N_('Show resources could not be determined when calculating utilization')
43
+ N_("Show hosts' resources that could not be determined when calculating the quota utilization")
44
44
  param :id, :identifier, required: true
45
45
  def missing_hosts
46
46
  process_response @resource_quota
@@ -5,7 +5,14 @@ module ForemanResourceQuota
5
5
  module Api
6
6
  module V2
7
7
  module HostsControllerExtensions
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ before_action :check_if_quota_is_set, only: %i[create update]
12
+ end
13
+
8
14
  extend ::Apipie::DSL::Concern
15
+
9
16
  update_api(:create, :update) do
10
17
  param :host, Hash do
11
18
  param :resource_quota_id, :number, required: false,
@@ -13,6 +20,20 @@ module ForemanResourceQuota
13
20
  This field is required if the setting `resource_quota_optional_assignment` is set to false.')
14
21
  end
15
22
  end
23
+
24
+ private
25
+
26
+ def check_if_quota_is_set # rubocop:disable Metrics/AbcSize
27
+ return if User.current.quota_assignment_optional?
28
+ quota = if User.current&.admin?
29
+ ResourceQuota.where(id: params['host']['resource_quota_id']).first
30
+ else
31
+ User.current.resource_quotas.where(id: params['host']['resource_quota_id']).first
32
+ end
33
+ return unless quota.nil? || quota.unassigned?
34
+ render_error :custom_error, status: :unprocessable_entity,
35
+ locals: { message: 'No valid resource quota provided' }
36
+ end
16
37
  end
17
38
  end
18
39
  end
@@ -2,17 +2,28 @@
2
2
 
3
3
  module ForemanResourceQuota
4
4
  module HostsHelper
5
- def resource_quota_select(form, user_quotas)
6
- blank_opt = { include_blank: true }
7
- select_items = user_quotas.order(:name)
5
+ def resource_quota_select(form, user_quotas, selected, assignment_optional, host_quota)
6
+ select_opts = { include_blank: false,
7
+ selected: selected }
8
+ html_opts = { label: _('Resource Quota'),
9
+ required: !assignment_optional,
10
+ help_inline: if assignment_optional
11
+ _('Define the Resource Quota this host counts to.')
12
+ elsif !selected.nil? && (host_quota.nil? ||
13
+ host_quota == ForemanResourceQuota::ResourceQuota.unassigned.id)
14
+ format(_("Quota required! Choosing '%s' by default, change here if needed!"),
15
+ user_quotas.find(selected))
16
+ else
17
+ _('Resource quota assignment required!')
18
+ end }
19
+
8
20
  select_f form,
9
21
  :resource_quota_id,
10
- select_items,
22
+ user_quotas,
11
23
  :id,
12
24
  :to_label,
13
- blank_opt,
14
- label: _('Resource Quota'),
15
- help_inline: _('Define the Resource Quota this host counts to.')
25
+ select_opts,
26
+ html_opts
16
27
  end
17
28
  end
18
29
  end
@@ -8,5 +8,6 @@ module ForemanResourceQuota
8
8
  class HostResourcesException < ResourceQuotaException; end
9
9
  class ResourceQuotaUtilizationException < ResourceQuotaException; end
10
10
  class HostNotFoundException < ResourceQuotaException; end
11
+ class UnassignedQuotaDeletionException < ResourceQuotaException; end
11
12
  end
12
13
  end
@@ -128,22 +128,21 @@ module ForemanResourceQuota
128
128
  "while processing host '#{name}'. The Resource Quota utilization values might be inconsistent.")
129
129
  end
130
130
 
131
- def early_return?(quota)
132
- if quota.nil?
133
- return true if quota_assigment_optional?
134
- raise HostResourceQuotaEmptyException, 'must be given.'
131
+ def early_return?(quota) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
132
+ if quota.nil? || quota.unassigned?
133
+ self.resource_quota = ResourceQuota.unassigned
134
+ return true if owner.quota_assignment_optional?
135
+ if ResourceQuota.assignable.empty? || (!owner.admin? && owner.resource_quotas.assignable.empty?)
136
+ raise HostResourceQuotaEmptyException,
137
+ 'must be given.'
138
+ end
139
+ return true
135
140
  end
136
141
  return true if quota.active_resources.empty?
137
142
  return true if Setting[:resource_quota_global_no_action] # quota is assigned, but not supposed to be checked
138
143
  false
139
144
  end
140
145
 
141
- def quota_assigment_optional?
142
- return true if Setting[:resource_quota_optional_assignment]
143
- return true if owner.respond_to?(:resource_quota_is_optional) && owner.resource_quota_is_optional
144
- false
145
- end
146
-
147
146
  def save_host_resources
148
147
  host_resources.save
149
148
  end
@@ -4,12 +4,39 @@ module ForemanResourceQuota
4
4
  module UserExtensions
5
5
  extend ActiveSupport::Concern
6
6
  included do
7
+ after_create :set_unassigned_quota
8
+
7
9
  has_many :resource_quotas_users, class_name: 'ForemanResourceQuota::ResourceQuotaUser', dependent: :destroy,
8
10
  inverse_of: :user
9
11
  has_many :resource_quotas, class_name: 'ForemanResourceQuota::ResourceQuota', through: :resource_quotas_users
10
12
  attribute :resource_quota_is_optional, :boolean, default: false
11
13
 
12
14
  scoped_search relation: :resource_quotas, on: :name, complete_value: true, rename: :resource_quota
15
+
16
+ def assignable_resource_quotas
17
+ resource_quotas.assignable
18
+ end
19
+
20
+ def quota_assignment_optional?
21
+ Setting['resource_quota_optional_assignment'] || resource_quota_is_optional
22
+ end
23
+
24
+ def show_unassigned_hosts_warning?
25
+ return false if Setting['resource_quota_optional_assignment']
26
+ (admin? &&
27
+ !ForemanResourceQuota::ResourceQuota.unassigned.hosts.empty?) ||
28
+ (!admin? &&
29
+ !resource_quota_is_optional &&
30
+ !ForemanResourceQuota::ResourceQuota.unassigned.hosts.where(owner: self).empty?)
31
+ end
32
+
33
+ private
34
+
35
+ def set_unassigned_quota
36
+ return if resource_quotas.include?(ForemanResourceQuota::ResourceQuota.unassigned)
37
+
38
+ resource_quotas << ForemanResourceQuota::ResourceQuota.unassigned
39
+ end
13
40
  end
14
41
  end
15
42
  end
@@ -4,11 +4,29 @@ module ForemanResourceQuota
4
4
  module UsergroupExtensions
5
5
  extend ActiveSupport::Concern
6
6
  included do
7
+ after_create :set_unassigned_quota
8
+
7
9
  has_many :resource_quotas_usergroups, class_name: 'ForemanResourceQuota::ResourceQuotaUsergroup',
8
10
  dependent: :destroy, inverse_of: :usergroup
9
11
  has_many :resource_quotas, class_name: 'ForemanResourceQuota::ResourceQuota', through: :resource_quotas_usergroups
10
12
 
11
13
  scoped_search relation: :resource_quotas, on: :name, complete_value: true, rename: :resource_quota
14
+
15
+ def assignable_resource_quotas
16
+ resource_quotas.assignable
17
+ end
18
+
19
+ def quota_assignment_optional?
20
+ Setting['resource_quota_optional_assignment']
21
+ end
22
+
23
+ private
24
+
25
+ def set_unassigned_quota
26
+ return if resource_quotas.include?(ForemanResourceQuota::ResourceQuota.unassigned)
27
+
28
+ resource_quotas << ForemanResourceQuota::ResourceQuota.unassigned
29
+ end
12
30
  end
13
31
  end
14
32
  end
@@ -23,13 +23,26 @@ module ForemanResourceQuota
23
23
 
24
24
  validates :name, presence: true, uniqueness: true
25
25
 
26
+ before_destroy :do_not_destroy_unassigned_quota, prepend: true
27
+ before_destroy :assign_unassigned_quota, prepend: true
28
+
26
29
  scoped_search on: :name, complete_value: true
27
30
  scoped_search on: :id, complete_enabled: false, only_explicit: true, validator: ScopedSearch::Validators::INTEGER
28
31
 
32
+ scope :assignable, -> { where(unassigned: false) }
33
+
34
+ def self.unassigned
35
+ find_by(unassigned: true)
36
+ end
37
+
29
38
  def self.permission_name
30
39
  'resource_quotas'
31
40
  end
32
41
 
42
+ def unassigned?
43
+ unassigned
44
+ end
45
+
33
46
  def number_of_hosts
34
47
  hosts_resources.size
35
48
  end
@@ -151,6 +164,16 @@ module ForemanResourceQuota
151
164
 
152
165
  private
153
166
 
167
+ def do_not_destroy_unassigned_quota
168
+ return unless id == ResourceQuota.unassigned.id
169
+ raise UnassignedQuotaDeletionException,
170
+ "You cannot delete the 'Unassigned' quota."
171
+ end
172
+
173
+ def assign_unassigned_quota
174
+ hosts.update(resource_quota: ResourceQuota.unassigned)
175
+ end
176
+
154
177
  # Wrap into a function for easier testing
155
178
  def call_utilization_helper(quota_hosts)
156
179
  utilization_from_resource_origins(active_resources, quota_hosts)
@@ -6,6 +6,9 @@
6
6
  <% end %>
7
7
 
8
8
  <% title _('Resource Quotas') %>
9
+ <% if User.current.show_unassigned_hosts_warning? %>
10
+ <%= alert :class => 'alert-warning', :header => _('You have unassigned hosts!'), text: "The setting 'resource_quota_optional_assignment' is set to 'No' but there are hosts without quota assignment. Please check your hosts' quota assignments! " %>
11
+ <% end %>
9
12
 
10
13
  <%= title_actions react_component('CreateResourceQuotaModal') %>
11
14
 
@@ -22,8 +25,10 @@
22
25
  </thead>
23
26
  <tbody>
24
27
  <% @resource_quotas.each do |quota|
28
+ showAssignmentWarning = quota.unassigned? && User.current.show_unassigned_hosts_warning?
25
29
  react_data = {
26
30
  "isNewQuota": false,
31
+ "showAssignmentWarning": showAssignmentWarning,
27
32
  "initialProperties": {
28
33
  "id": quota.id,
29
34
  "name": quota.name,
@@ -31,6 +36,7 @@
31
36
  "cpu_cores": quota.cpu_cores,
32
37
  "memory_mb": quota.memory_mb,
33
38
  "disk_gb": quota.disk_gb,
39
+ "unassigned": quota.unassigned?,
34
40
  },
35
41
  }
36
42
  %>
@@ -43,9 +49,11 @@
43
49
  <td><%= h(quota.memory_mb) %></td>
44
50
  <td><%= h(quota.disk_gb) %></td>
45
51
  <td>
52
+ <% unless quota.unassigned? %>
46
53
  <%= action_buttons(
47
54
  display_delete_if_authorized(hash_for_foreman_resource_quota_resource_quota_path(id: quota), data: { confirm: _("Delete %s?") % quota.name})
48
- ) %>
55
+ ) %>
56
+ <% end %>
49
57
  </td>
50
58
  </tr>
51
59
  <% end %>
@@ -1,4 +1,16 @@
1
1
  <%
2
- user_quotas = User.current&.admin? ? ForemanResourceQuota::ResourceQuota.all : User.current.resource_quotas
2
+ user_quotas = User.current&.admin? ? ForemanResourceQuota::ResourceQuota.all : User.current.resource_quotas.distinct
3
+ user_quotas = user_quotas.assignable if !User.current.quota_assignment_optional?
4
+ user_quotas = user_quotas.order(:name)
5
+ selected = @host.resource_quota_id
6
+ # show Unassigned as default when assignment is optional
7
+ # show first selectable quota when assignment is mandatory
8
+ if selected.nil? || selected == ForemanResourceQuota::ResourceQuota.unassigned.id
9
+ if User.current.quota_assignment_optional?
10
+ selected = ForemanResourceQuota::ResourceQuota.unassigned.id
11
+ else
12
+ selected = user_quotas&.first&.id
13
+ end
14
+ end
3
15
  %>
4
- <%= resource_quota_select form, user_quotas %>
16
+ <%= resource_quota_select form, user_quotas, selected, User.current.quota_assignment_optional?, @host.resource_quota_id %>
@@ -10,7 +10,7 @@
10
10
  :label_help => _("It is optional for a user to assign a quota when creating new hosts") %>
11
11
  <% end %>
12
12
 
13
- <%= multiple_checkboxes(form, :resource_quotas, resource, ForemanResourceQuota::ResourceQuota, :label => _("Resource Quotas")) %>
13
+ <%= multiple_checkboxes(form, :resource_quotas, resource, ForemanResourceQuota::ResourceQuota.assignable, :label => _("Resource Quotas")) %>
14
14
 
15
15
  <% if resource_type == :user %>
16
16
  <% usergroups = @user.cached_usergroups.includes(:resource_quotas).distinct %>
@@ -34,7 +34,7 @@
34
34
  <% unless usergroup.resource_quotas.map(&:name).any? %>
35
35
  <li data-id="<%= usergroup.id %>" class="list-group-item"><%= _('This group has no quotas') %></li>
36
36
  <%end %>
37
- <% usergroup.resource_quotas.map(&:name).each do |quota_name| %>
37
+ <% usergroup.assignable_resource_quotas.map(&:name).each do |quota_name| %>
38
38
  <li data-id="<%= usergroup.id %>" class="list-group-item"><%= quota_name %></li>
39
39
  <% end %>
40
40
  <% end %>
@@ -1,7 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class DropMissingHosts < ActiveRecord::Migration[6.1]
4
- def up
5
- drop_table :resource_quotas_missing_hosts
4
+ def change
5
+ drop_table :resource_quotas_missing_hosts do |t|
6
+ t.references :resource_quota, null: false, foreign_key: { to_table: :resource_quotas }
7
+ t.references :missing_host, null: false, unique: true, foreign_key: { to_table: :hosts }
8
+ t.boolean :no_cpu_cores, default: false
9
+ t.boolean :no_memory_mb, default: false
10
+ t.boolean :no_disk_gb, default: false
11
+ t.timestamps
12
+ end
6
13
  end
7
14
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddUnassignedFlagToResourceQuota < ActiveRecord::Migration[6.1]
4
+ def change
5
+ add_column :resource_quotas, :unassigned, :bool, null: false, default: false
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Default Quota "Unassigned"
4
+ ForemanResourceQuota::ResourceQuota.without_auditing do # rubocop:disable Metrics/BlockLength
5
+ unassigned = ForemanResourceQuota::ResourceQuota.where(
6
+ name: 'Unassigned',
7
+ unassigned: true,
8
+ description: 'Here, you can see all hosts without a dedicated quota.'
9
+ ).first_or_create
10
+
11
+ # Add default quota to all users and usergroups
12
+ User.without_auditing do
13
+ User.all.except_hidden.each do |user|
14
+ unless user.resource_quotas.include?(unassigned)
15
+ user.resource_quotas << unassigned
16
+ user.save!(validate: false)
17
+ end
18
+ end
19
+ end
20
+
21
+ Usergroup.without_auditing do
22
+ Usergroup.all.each do |usergroup|
23
+ unless usergroup.resource_quotas.include?(unassigned)
24
+ usergroup.resource_quotas << unassigned
25
+ usergroup.save!(validate: false)
26
+ end
27
+ end
28
+ end
29
+
30
+ # Move all hosts without a quota to quota "Unassigned"
31
+ Host.without_auditing do
32
+ Host.all.each do |host|
33
+ host.update(resource_quota: unassigned) if host.resource_quota.nil?
34
+ end
35
+ end
36
+ end
@@ -105,5 +105,13 @@ Foreman::Plugin.register :foreman_resource_quota do
105
105
  # Future: Overwrite quota-specific "out of resource"-action and take no ..
106
106
  end
107
107
  end
108
+ extend_page 'hosts/_list' do |context|
109
+ context.with_profile :resource_quota, _('Resource Quota'), default: true do
110
+ add_pagelet :hosts_table_column_header, key: :resource_quota_id, label: s_('Resource Quota'),
111
+ sortable: true, width: '10%', class: 'hidden-xs'
112
+ add_pagelet :hosts_table_column_content, key: :resource_quota_id,
113
+ callback: ->(host) { host.resource_quota.name }, class: 'hidden-xs ellipsis'
114
+ end
115
+ end
108
116
  end
109
117
  # rubocop: enable Metrics/BlockLength
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ForemanResourceQuota
4
- VERSION = '0.5.0'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -4,11 +4,10 @@ require 'rake/testtask'
4
4
 
5
5
  # Tasks
6
6
  namespace :foreman_resource_quota do
7
- namespace :example do
8
- desc 'Example Task'
9
- task task: :environment do
10
- # Task goes here
11
- end
7
+ desc 'EXPERIMENTAL: Revert all database migrations of this plugin, preparing plugin uninstall'
8
+ task revert_db_migrations: :environment do
9
+ plugin = Foreman::Plugin.find ForemanResourceQuota.name.underscore
10
+ ActiveRecord::MigrationContext.new(plugin.migrations_paths, ActiveRecord::SchemaMigration).down
12
11
  end
13
12
  end
14
13
 
data/package.json CHANGED
@@ -24,9 +24,9 @@
24
24
  "@theforeman/vendor": ">= 12.0.1"
25
25
  },
26
26
  "devDependencies": {
27
- "@babel/core": "^7.23.0",
27
+ "@babel/core": "^7.28.0",
28
28
  "@sheerun/mutationobserver-shim": "^0.3.3",
29
- "@testing-library/react": "^16.2.0",
29
+ "@testing-library/react": "^16.3.0",
30
30
  "@testing-library/user-event": "^14.6.1",
31
31
  "@theforeman/builder": ">= 15.0.0",
32
32
  "@theforeman/eslint-plugin-foreman": ">= 15.0.0",
@@ -36,8 +36,7 @@
36
36
  "babel-eslint": "^10.0.3",
37
37
  "eslint": "^6.7.2",
38
38
  "prettier": "^1.19.1",
39
- "react-redux-test-utils": "^0.2.0",
40
- "stylelint": "^16.15.0",
41
- "stylelint-config-standard": "^37.0.0"
39
+ "stylelint": "^16.21.1",
40
+ "stylelint-config-standard": "^38.0.0"
42
41
  }
43
42
  }
@@ -20,6 +20,7 @@ const CreateResourceQuotaModal = () => {
20
20
  <div>
21
21
  <Button
22
22
  id="foreman-resource-quota-create-modal-button"
23
+ ouiaId="foreman-resource-quota-create-modal-button"
23
24
  variant="primary"
24
25
  onClick={() => {
25
26
  setIsOpen(true);
@@ -7,6 +7,7 @@ exports[`ResourceQuotaEmptyState should render 1`] = `
7
7
  <Button
8
8
  id="foreman-resource-quota-welcome-create-modal-button"
9
9
  onClick={[Function]}
10
+ ouiaId="foreman-resource-quota-welcome-create-modal-button"
10
11
  variant="primary"
11
12
  >
12
13
  Create resource quota
@@ -57,6 +58,7 @@ exports[`ResourceQuotaEmptyState should render 1`] = `
57
58
  "disk_gb": null,
58
59
  "memory_mb": null,
59
60
  "name": "",
61
+ "unassigned": false,
60
62
  }
61
63
  }
62
64
  initialStatus={
@@ -75,6 +77,7 @@ exports[`ResourceQuotaEmptyState should render 1`] = `
75
77
  isNewQuota={true}
76
78
  onSubmit={[Function]}
77
79
  quotaChangesCallback={null}
80
+ showAssignmentWarning={false}
78
81
  />
79
82
  </Modal>
80
83
  </div>
@@ -21,6 +21,7 @@ const ResourceQuotaEmptyState = () => {
21
21
  const ActionButton = (
22
22
  <Button
23
23
  id="foreman-resource-quota-welcome-create-modal-button"
24
+ ouiaId="foreman-resource-quota-welcome-create-modal-button"
24
25
  variant="primary"
25
26
  onClick={() => {
26
27
  setIsOpen(true);
@@ -2,6 +2,7 @@
2
2
  export const RESOURCE_IDENTIFIER_ID = 'id';
3
3
  export const RESOURCE_IDENTIFIER_NAME = 'name';
4
4
  export const RESOURCE_IDENTIFIER_DESCRIPTION = 'description';
5
+ export const RESOURCE_IDENTIFIER_UNASSIGNED = 'unassigned';
5
6
  export const RESOURCE_IDENTIFIER_CPU = 'cpu_cores';
6
7
  export const RESOURCE_IDENTIFIER_MEMORY = 'memory_mb';
7
8
  export const RESOURCE_IDENTIFIER_DISK = 'disk_gb';
@@ -42,6 +42,7 @@ const StaticDetail = ({
42
42
  ) : (
43
43
  <TextInput
44
44
  id={id}
45
+ ouiaId={id}
45
46
  onChange={(_event, val) => onChange(val)}
46
47
  value={value}
47
48
  validated={validated}
@@ -19,6 +19,7 @@ const TextInputField = ({
19
19
  isRequired,
20
20
  isTextArea,
21
21
  isNewQuota,
22
+ isDisabled,
22
23
  }) => {
23
24
  const dispatch = useDispatch();
24
25
  const [currentAttribute, setCurrentAttribute] = useState();
@@ -100,7 +101,7 @@ const TextInputField = ({
100
101
  loading={isLoading && currentAttribute === attribute}
101
102
  onEdit={onEdit}
102
103
  value={value}
103
- disabled={false}
104
+ disabled={isDisabled}
104
105
  textArea={isTextArea}
105
106
  validated={validated}
106
107
  {...{ currentAttribute, setCurrentAttribute }}
@@ -114,6 +115,7 @@ TextInputField.defaultProps = {
114
115
  isRequired: false,
115
116
  isRestrictInputValidation: false,
116
117
  isNewQuota: false,
118
+ isDisabled: false,
117
119
  };
118
120
 
119
121
  TextInputField.propTypes = {
@@ -127,6 +129,7 @@ TextInputField.propTypes = {
127
129
  isTextArea: PropTypes.bool,
128
130
  isRequired: PropTypes.bool,
129
131
  isNewQuota: PropTypes.bool,
132
+ isDisabled: PropTypes.bool,
130
133
  };
131
134
 
132
135
  export default TextInputField;
@@ -17,6 +17,7 @@ import {
17
17
  } from '@patternfly/react-core';
18
18
 
19
19
  import {
20
+ ExclamationCircleIcon,
20
21
  UserIcon,
21
22
  UsersIcon,
22
23
  ClusterIcon,
@@ -42,6 +43,8 @@ const Properties = ({
42
43
  initialName,
43
44
  initialDescription,
44
45
  initialStatus,
46
+ unassigned,
47
+ showAssignmentWarning,
45
48
  handleInputValidation,
46
49
  onChange,
47
50
  onApply,
@@ -89,6 +92,8 @@ const Properties = ({
89
92
  reference={tooltipRefFetchButton}
90
93
  >
91
94
  <Button
95
+ id="resource-quota-index-button"
96
+ ouiaId="resource-quota-index-button"
92
97
  isLoading={isFetchLoading}
93
98
  icon={<SyncAltIcon />}
94
99
  size="sm"
@@ -111,32 +116,48 @@ const Properties = ({
111
116
  <FlexItem>
112
117
  <LabelGroup isCompact>
113
118
  <StatusPropertiesLabel
114
- color="blue"
115
- iconChild={<ClusterIcon />}
119
+ color={showAssignmentWarning ? 'red' : 'blue'}
120
+ iconChild={
121
+ showAssignmentWarning ? (
122
+ <ExclamationCircleIcon />
123
+ ) : (
124
+ <ClusterIcon />
125
+ )
126
+ }
116
127
  statusContent={
117
128
  statusProperties[RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS]
118
129
  }
119
130
  linkUrl={`/hosts?search=resource_quota="${initialName}"`}
120
- tooltipText="Number of assigned hosts"
121
- />
122
- <StatusPropertiesLabel
123
- color="blue"
124
- iconChild={<UserIcon />}
125
- statusContent={
126
- statusProperties[RESOURCE_IDENTIFIER_STATUS_NUM_USERS]
127
- }
128
- linkUrl={`/users?search=resource_quota="${initialName}"`}
129
- tooltipText="Number of assigned users"
130
- />
131
- <StatusPropertiesLabel
132
- color="blue"
133
- iconChild={<UsersIcon />}
134
- statusContent={
135
- statusProperties[RESOURCE_IDENTIFIER_STATUS_NUM_USERGROUPS]
131
+ tooltipText={
132
+ showAssignmentWarning
133
+ ? __(
134
+ "The setting 'resource_quota_optional_assignment' is set to 'No' but there are hosts with no quota assignment. Please check your hosts' quota assignments!"
135
+ )
136
+ : __('Number of assigned hosts')
136
137
  }
137
- linkUrl={`/usergroups?search=resource_quota="${initialName}"`}
138
- tooltipText="Number of assigned usergroups"
139
138
  />
139
+ {!unassigned && (
140
+ <StatusPropertiesLabel
141
+ color="blue"
142
+ iconChild={<UserIcon />}
143
+ statusContent={
144
+ statusProperties[RESOURCE_IDENTIFIER_STATUS_NUM_USERS]
145
+ }
146
+ linkUrl={`/users?search=resource_quota="${initialName}"`}
147
+ tooltipText="Number of assigned users"
148
+ />
149
+ )}
150
+ {!unassigned && (
151
+ <StatusPropertiesLabel
152
+ color="blue"
153
+ iconChild={<UsersIcon />}
154
+ statusContent={
155
+ statusProperties[RESOURCE_IDENTIFIER_STATUS_NUM_USERGROUPS]
156
+ }
157
+ linkUrl={`/usergroups?search=resource_quota="${initialName}"`}
158
+ tooltipText="Number of assigned usergroups"
159
+ />
160
+ )}
140
161
  </LabelGroup>
141
162
  </FlexItem>
142
163
  </Flex>
@@ -144,7 +165,7 @@ const Properties = ({
144
165
  };
145
166
 
146
167
  return (
147
- <Card>
168
+ <Card id="resource-quota-index-card" ouiaId="resource-quota-index-card">
148
169
  <CardHeader actions={{ actions: renderSyncButton() }}>
149
170
  {renderHeaderTitle()}
150
171
  </CardHeader>
@@ -161,6 +182,7 @@ const Properties = ({
161
182
  onChange={onChange}
162
183
  isRestrictInputValidation
163
184
  isRequired
185
+ isDisabled={unassigned}
164
186
  />
165
187
  <TextInputField
166
188
  initialValue={initialDescription}
@@ -171,6 +193,7 @@ const Properties = ({
171
193
  onApply={onApply}
172
194
  onChange={onChange}
173
195
  isTextArea
196
+ isDisabled={unassigned}
174
197
  />
175
198
  </TextList>
176
199
  </TextContent>
@@ -187,6 +210,8 @@ Properties.defaultProps = {
187
210
  [RESOURCE_IDENTIFIER_STATUS_NUM_USERS]: null,
188
211
  [RESOURCE_IDENTIFIER_STATUS_NUM_USERGROUPS]: null,
189
212
  },
213
+ unassigned: false,
214
+ showAssignmentWarning: false,
190
215
  };
191
216
 
192
217
  Properties.propTypes = {
@@ -198,6 +223,8 @@ Properties.propTypes = {
198
223
  [RESOURCE_IDENTIFIER_STATUS_NUM_USERS]: PropTypes.number,
199
224
  [RESOURCE_IDENTIFIER_STATUS_NUM_USERGROUPS]: PropTypes.number,
200
225
  }),
226
+ unassigned: PropTypes.bool,
227
+ showAssignmentWarning: PropTypes.bool,
201
228
  handleInputValidation: PropTypes.func.isRequired,
202
229
  onChange: PropTypes.func.isRequired,
203
230
  onApply: PropTypes.func.isRequired,
@@ -49,6 +49,7 @@ const UnitInputField = ({
49
49
  unitDropdownItems = units.map(unit => (
50
50
  <DropdownItem
51
51
  id={`unit-dropdownitem-${unit.symbol.toLowerCase()}`}
52
+ ouiaId={`unit-dropdownitem-${unit.symbol.toLowerCase()}`}
52
53
  key={unit.symbol.toLowerCase()}
53
54
  >
54
55
  {unit.symbol}
@@ -169,10 +170,12 @@ const UnitInputField = ({
169
170
  return (
170
171
  <InputGroupItem>
171
172
  <Dropdown
173
+ ouiaId="resource-quota-unit-input-field-input-group-item-dropdown"
172
174
  onSelect={onUnitSelect}
173
175
  toggle={
174
176
  <DropdownToggle
175
177
  isDisabled={isDisabled}
178
+ ouiaId="resource-quota-unit-input-field-input-group-item-dropdowni-toggle"
176
179
  onToggle={(_event, _val) => onUnitToggle()}
177
180
  >
178
181
  {__(`${selectedUnit.symbol}`)}
@@ -233,7 +236,8 @@ const UnitInputField = ({
233
236
  min={minValue}
234
237
  max={maxValue}
235
238
  validated={validated}
236
- id="reg_token_life_time_input"
239
+ id="resource-quota-reg-token-life-time-input"
240
+ ouiaId="resource-quota-reg-token-life-time-input"
237
241
  onChange={(_event, val) => setInputValue(val)}
238
242
  />
239
243
  </InputGroupItem>
@@ -105,19 +105,20 @@ const UtilizationProgress = ({
105
105
  <Tooltip
106
106
  content={resourceUtilizationTooltipText}
107
107
  reference={tooltipRefUtilization}
108
- />
109
- <div
110
- className={isEnabled ? '' : 'progress-disabled'}
111
- ref={tooltipRefUtilization}
112
108
  >
113
- <Progress
114
- aria-label={`resource-card-${cardId}-progress`}
115
- value={resourceUtilizationPercent}
116
- measureLocation={ProgressMeasureLocation.inside}
117
- size={ProgressSize.lg}
118
- variant={resourceProgressVariant()}
119
- />
120
- </div>
109
+ <div
110
+ className={isEnabled ? '' : 'progress-disabled'}
111
+ ref={tooltipRefUtilization}
112
+ >
113
+ <Progress
114
+ aria-label={`resource-card-${cardId}-progress`}
115
+ value={resourceUtilizationPercent}
116
+ measureLocation={ProgressMeasureLocation.inside}
117
+ size={ProgressSize.lg}
118
+ variant={resourceProgressVariant()}
119
+ />
120
+ </div>
121
+ </Tooltip>
121
122
  </div>
122
123
  );
123
124
  };
@@ -17,11 +17,12 @@ exports[`UnitInputField should render as disabled field 1`] = `
17
17
  <InputGroup>
18
18
  <InputGroupItem>
19
19
  <TextInput
20
- id="reg_token_life_time_input"
20
+ id="resource-quota-reg-token-life-time-input"
21
21
  isDisabled={true}
22
22
  max={5}
23
23
  min={0}
24
24
  onChange={[Function]}
25
+ ouiaId="resource-quota-reg-token-life-time-input"
25
26
  validated="default"
26
27
  value={0}
27
28
  />
@@ -32,11 +33,13 @@ exports[`UnitInputField should render as disabled field 1`] = `
32
33
  Array [
33
34
  <DropdownItem
34
35
  id="unit-dropdownitem-mib"
36
+ ouiaId="unit-dropdownitem-mib"
35
37
  >
36
38
  MiB
37
39
  </DropdownItem>,
38
40
  <DropdownItem
39
41
  id="unit-dropdownitem-gib"
42
+ ouiaId="unit-dropdownitem-gib"
40
43
  >
41
44
  GiB
42
45
  </DropdownItem>,
@@ -44,10 +47,12 @@ exports[`UnitInputField should render as disabled field 1`] = `
44
47
  }
45
48
  isOpen={false}
46
49
  onSelect={[Function]}
50
+ ouiaId="resource-quota-unit-input-field-input-group-item-dropdown"
47
51
  toggle={
48
52
  <DropdownToggle
49
53
  isDisabled={true}
50
54
  onToggle={[Function]}
55
+ ouiaId="resource-quota-unit-input-field-input-group-item-dropdowni-toggle"
51
56
  >
52
57
  MiB
53
58
  </DropdownToggle>
@@ -76,11 +81,12 @@ exports[`UnitInputField should render default 1`] = `
76
81
  <InputGroup>
77
82
  <InputGroupItem>
78
83
  <TextInput
79
- id="reg_token_life_time_input"
84
+ id="resource-quota-reg-token-life-time-input"
80
85
  isDisabled={false}
81
86
  max={5}
82
87
  min={0}
83
88
  onChange={[Function]}
89
+ ouiaId="resource-quota-reg-token-life-time-input"
84
90
  validated="default"
85
91
  value={0}
86
92
  />
@@ -91,11 +97,13 @@ exports[`UnitInputField should render default 1`] = `
91
97
  Array [
92
98
  <DropdownItem
93
99
  id="unit-dropdownitem-mib"
100
+ ouiaId="unit-dropdownitem-mib"
94
101
  >
95
102
  MiB
96
103
  </DropdownItem>,
97
104
  <DropdownItem
98
105
  id="unit-dropdownitem-gib"
106
+ ouiaId="unit-dropdownitem-gib"
99
107
  >
100
108
  GiB
101
109
  </DropdownItem>,
@@ -103,10 +111,12 @@ exports[`UnitInputField should render default 1`] = `
103
111
  }
104
112
  isOpen={false}
105
113
  onSelect={[Function]}
114
+ ouiaId="resource-quota-unit-input-field-input-group-item-dropdown"
106
115
  toggle={
107
116
  <DropdownToggle
108
117
  isDisabled={false}
109
118
  onToggle={[Function]}
119
+ ouiaId="resource-quota-unit-input-field-input-group-item-dropdowni-toggle"
110
120
  >
111
121
  MiB
112
122
  </DropdownToggle>
@@ -135,11 +145,12 @@ exports[`UnitInputField should render without dropdown (single unit) 1`] = `
135
145
  <InputGroup>
136
146
  <InputGroupItem>
137
147
  <TextInput
138
- id="reg_token_life_time_input"
148
+ id="resource-quota-reg-token-life-time-input"
139
149
  isDisabled={false}
140
150
  max={5}
141
151
  min={0}
142
152
  onChange={[Function]}
153
+ ouiaId="resource-quota-reg-token-life-time-input"
143
154
  validated="default"
144
155
  value={0}
145
156
  />
@@ -143,6 +143,8 @@ const Resource = ({
143
143
  variant="primary"
144
144
  onClick={onClickApply}
145
145
  isLoading={isApplyLoading}
146
+ id="resource-quota-resource-index-button-apply"
147
+ ouiaId="resource-quota-resource-index-button-apply"
146
148
  >
147
149
  {__('Apply')}
148
150
  </Button>
@@ -154,6 +156,7 @@ const Resource = ({
154
156
  isExpanded={isExpanded}
155
157
  isDisabledRaised={!isEnabled}
156
158
  id={`resource-card-${cardId}`}
159
+ ouiaId={`resource-card-${cardId}`}
157
160
  >
158
161
  <CardHeader
159
162
  actions={{ actions: renderApplyButton() }}
@@ -164,6 +167,7 @@ const Resource = ({
164
167
  <FlexItem>
165
168
  <Switch
166
169
  id={`switch-${cardId}`}
170
+ ouiaId={`switch-${cardId}`}
167
171
  aria-label={`switch-${cardId}`}
168
172
  onChange={(_event, val) => onChangeEnabled(val)}
169
173
  isChecked={isEnabled}
@@ -48,7 +48,8 @@ const Submit = ({ isValid, onCreate, onSubmit }) => {
48
48
  onClick={handleOnSubmit}
49
49
  isLoading={isSubmitLoading}
50
50
  variant="primary"
51
- id="submit-button"
51
+ id="resource-quota-submit-button"
52
+ ouiaId="resource-quota-submit-button"
52
53
  >
53
54
  {__('Create resource quota')}
54
55
  </Button>
@@ -16,6 +16,7 @@ import {
16
16
  RESOURCE_IDENTIFIER_CPU,
17
17
  RESOURCE_IDENTIFIER_MEMORY,
18
18
  RESOURCE_IDENTIFIER_DISK,
19
+ RESOURCE_IDENTIFIER_UNASSIGNED,
19
20
  RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS,
20
21
  RESOURCE_IDENTIFIER_STATUS_NUM_USERS,
21
22
  RESOURCE_IDENTIFIER_STATUS_NUM_USERGROUPS,
@@ -25,6 +26,7 @@ import {
25
26
 
26
27
  const ResourceQuotaForm = ({
27
28
  isNewQuota,
29
+ showAssignmentWarning,
28
30
  initialProperties,
29
31
  initialStatus,
30
32
  onSubmit,
@@ -72,33 +74,41 @@ const ResourceQuotaForm = ({
72
74
  RESOURCE_IDENTIFIER_DESCRIPTION
73
75
  )}
74
76
  initialStatus={modelState.getQuotaStatus()}
75
- handleInputValidation={modelState.handleInputValidation}
76
- onChange={modelState.onChange}
77
- onApply={modelState.onApply}
78
- onFetch={modelState.onFetchUtilization}
79
- />
80
- )}
81
- </SkeletonLoader>
82
- </GalleryItem>
83
- <GalleryItem key="edit-resource-quota-resources-item">
84
- <SkeletonLoader
85
- skeletonProps={{ width: 400 }}
86
- status={isNewQuota || !isLoading ? STATUS.RESOLVED : STATUS.PENDING}
87
- >
88
- {(!isLoading || isNewQuota) && (
89
- <Resources
90
- isNewQuota={isNewQuota}
91
- initialProperties={modelState.getQuotaProperties()}
92
- initialStatus={modelState.getQuotaStatus(
93
- RESOURCE_IDENTIFIER_STATUS_UTILIZATION
77
+ unassigned={modelState.getQuotaProperties(
78
+ RESOURCE_IDENTIFIER_UNASSIGNED
94
79
  )}
80
+ showAssignmentWarning={showAssignmentWarning}
95
81
  handleInputValidation={modelState.handleInputValidation}
96
82
  onChange={modelState.onChange}
97
83
  onApply={modelState.onApply}
84
+ onFetch={modelState.onFetchUtilization}
98
85
  />
99
86
  )}
100
87
  </SkeletonLoader>
101
88
  </GalleryItem>
89
+ {!modelState.getQuotaProperties(RESOURCE_IDENTIFIER_UNASSIGNED) && (
90
+ <GalleryItem key="edit-resource-quota-resources-item">
91
+ <SkeletonLoader
92
+ skeletonProps={{ width: 400 }}
93
+ status={
94
+ isNewQuota || !isLoading ? STATUS.RESOLVED : STATUS.PENDING
95
+ }
96
+ >
97
+ {(!isLoading || isNewQuota) && (
98
+ <Resources
99
+ isNewQuota={isNewQuota}
100
+ initialProperties={modelState.getQuotaProperties()}
101
+ initialStatus={modelState.getQuotaStatus(
102
+ RESOURCE_IDENTIFIER_STATUS_UTILIZATION
103
+ )}
104
+ handleInputValidation={modelState.handleInputValidation}
105
+ onChange={modelState.onChange}
106
+ onApply={modelState.onApply}
107
+ />
108
+ )}
109
+ </SkeletonLoader>
110
+ </GalleryItem>
111
+ )}
102
112
  {isNewQuota && (
103
113
  <GalleryItem key="edit-resource-quota-submit-item">
104
114
  <Submit
@@ -114,6 +124,7 @@ const ResourceQuotaForm = ({
114
124
  };
115
125
 
116
126
  ResourceQuotaForm.defaultProps = {
127
+ showAssignmentWarning: false,
117
128
  onSubmit: null,
118
129
  quotaChangesCallback: null,
119
130
  initialProperties: {
@@ -122,6 +133,7 @@ ResourceQuotaForm.defaultProps = {
122
133
  [RESOURCE_IDENTIFIER_CPU]: null,
123
134
  [RESOURCE_IDENTIFIER_MEMORY]: null,
124
135
  [RESOURCE_IDENTIFIER_DISK]: null,
136
+ [RESOURCE_IDENTIFIER_UNASSIGNED]: false,
125
137
  },
126
138
  initialStatus: {
127
139
  [RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS]: null,
@@ -138,6 +150,7 @@ ResourceQuotaForm.defaultProps = {
138
150
 
139
151
  ResourceQuotaForm.propTypes = {
140
152
  isNewQuota: PropTypes.bool.isRequired,
153
+ showAssignmentWarning: PropTypes.bool,
141
154
  onSubmit: PropTypes.func,
142
155
  quotaChangesCallback: PropTypes.func,
143
156
  initialProperties: PropTypes.shape({
@@ -147,6 +160,7 @@ ResourceQuotaForm.propTypes = {
147
160
  [RESOURCE_IDENTIFIER_CPU]: PropTypes.number,
148
161
  [RESOURCE_IDENTIFIER_MEMORY]: PropTypes.number,
149
162
  [RESOURCE_IDENTIFIER_DISK]: PropTypes.number,
163
+ [RESOURCE_IDENTIFIER_UNASSIGNED]: PropTypes.bool,
150
164
  }),
151
165
  initialStatus: PropTypes.shape({
152
166
  [RESOURCE_IDENTIFIER_STATUS_NUM_HOSTS]: PropTypes.oneOfType([
@@ -20,7 +20,11 @@ import {
20
20
  RESOURCE_IDENTIFIER_STATUS_UTILIZATION,
21
21
  } from './ResourceQuotaForm/ResourceQuotaFormConstants';
22
22
 
23
- const UpdateResourceQuotaModal = ({ initialProperties, initialStatus }) => {
23
+ const UpdateResourceQuotaModal = ({
24
+ initialProperties,
25
+ initialStatus,
26
+ showAssignmentWarning,
27
+ }) => {
24
28
  const staticId = `${MODAL_ID_UPDATE_RESOURCE_QUOTA}-${initialProperties[RESOURCE_IDENTIFIER_ID]}`;
25
29
  const [isOpen, setIsOpen] = useState(false);
26
30
  const [quotaProperties, setQuotaProperties] = useState(initialProperties);
@@ -52,6 +56,7 @@ const UpdateResourceQuotaModal = ({ initialProperties, initialStatus }) => {
52
56
  >
53
57
  <ResourceQuotaForm
54
58
  isNewQuota={false}
59
+ showAssignmentWarning={showAssignmentWarning}
55
60
  initialProperties={quotaProperties}
56
61
  initialStatus={quotaStatus}
57
62
  quotaChangesCallback={onQuotaChangesCallback}
@@ -138,6 +143,7 @@ UpdateResourceQuotaModal.propTypes = {
138
143
  ]),
139
144
  }),
140
145
  }),
146
+ showAssignmentWarning: PropTypes.bool.isRequired,
141
147
  };
142
148
 
143
149
  export default UpdateResourceQuotaModal;
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_resource_quota
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bastian Schmidt
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-03-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: foreman-tasks
@@ -153,6 +152,8 @@ files:
153
152
  - db/migrate/20240611141939_drop_missing_hosts.rb
154
153
  - db/migrate/20240611142813_create_hosts_resources.rb
155
154
  - db/migrate/20240618163434_remove_resource_quota_from_hosts.rb
155
+ - db/migrate/20250410082728_add_unassigned_flag_to_resource_quota.rb
156
+ - db/seeds.d/030-unassigned_quota.rb
156
157
  - lib/foreman_resource_quota.rb
157
158
  - lib/foreman_resource_quota/async/refresh_resource_quota_utilization.rb
158
159
  - lib/foreman_resource_quota/engine.rb
@@ -306,7 +307,6 @@ licenses:
306
307
  - GPL-3.0
307
308
  metadata:
308
309
  is_foreman_plugin: 'true'
309
- post_install_message:
310
310
  rdoc_options: []
311
311
  require_paths:
312
312
  - lib
@@ -321,8 +321,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
321
321
  - !ruby/object:Gem::Version
322
322
  version: '0'
323
323
  requirements: []
324
- rubygems_version: 3.3.27
325
- signing_key:
324
+ rubygems_version: 3.6.7
326
325
  specification_version: 4
327
326
  summary: Foreman Plug-in for resource quota
328
327
  test_files: []