foreman_resource_quota 0.4.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.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb +1 -1
- data/app/controllers/foreman_resource_quota/concerns/api/v2/hosts_controller_extensions.rb +21 -0
- data/app/helpers/foreman_resource_quota/hosts_helper.rb +18 -7
- data/app/lib/foreman_resource_quota/exceptions.rb +1 -0
- data/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb +9 -10
- data/app/models/concerns/foreman_resource_quota/user_extensions.rb +27 -0
- data/app/models/concerns/foreman_resource_quota/usergroup_extensions.rb +18 -0
- data/app/models/foreman_resource_quota/resource_quota.rb +23 -0
- data/app/views/foreman_resource_quota/resource_quotas/index.html.erb +9 -1
- data/app/views/hosts/_form_quota_fields.html.erb +14 -2
- data/app/views/users/_form_quota_tab.html.erb +2 -2
- data/db/migrate/20240611141939_drop_missing_hosts.rb +9 -2
- data/db/migrate/20250410082728_add_unassigned_flag_to_resource_quota.rb +7 -0
- data/db/seeds.d/030-unassigned_quota.rb +36 -0
- data/lib/foreman_resource_quota/register.rb +8 -0
- data/lib/foreman_resource_quota/version.rb +1 -1
- data/lib/tasks/foreman_resource_quota_tasks.rake +4 -5
- data/package.json +9 -10
- data/webpack/components/CreateResourceQuotaModal.js +1 -0
- data/webpack/components/ResourceQuotaEmptyState/__test__/__snapshots__/ResourceQuotaEmptyState.test.js.snap +4 -0
- data/webpack/components/ResourceQuotaEmptyState/index.js +1 -0
- data/webpack/components/ResourceQuotaForm/ResourceQuotaForm.scss +1 -1
- data/webpack/components/ResourceQuotaForm/ResourceQuotaFormConstants.js +1 -0
- data/webpack/components/ResourceQuotaForm/components/Properties/Properties.scss +4 -3
- data/webpack/components/ResourceQuotaForm/components/Properties/StaticDetail.js +3 -2
- data/webpack/components/ResourceQuotaForm/components/Properties/TextInputField.js +4 -1
- data/webpack/components/ResourceQuotaForm/components/Properties/index.js +86 -45
- data/webpack/components/ResourceQuotaForm/components/Resource/Resource.scss +5 -5
- data/webpack/components/ResourceQuotaForm/components/Resource/UnitInputField.js +84 -37
- data/webpack/components/ResourceQuotaForm/components/Resource/UnitInputField.scss +7 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.js +14 -13
- data/webpack/components/ResourceQuotaForm/components/Resource/UtilizationProgress.scss +4 -4
- data/webpack/components/ResourceQuotaForm/components/Resource/__test__/__snapshots__/UnitInputField.test.js.snap +149 -140
- data/webpack/components/ResourceQuotaForm/components/Resource/index.js +28 -17
- data/webpack/components/ResourceQuotaForm/components/Submit.js +2 -1
- data/webpack/components/ResourceQuotaForm/index.js +33 -19
- data/webpack/components/UpdateResourceQuotaModal.js +7 -1
- data/webpack/lib/ActionableDetail.scss +1 -1
- data/webpack/lib/EditableSwitch.js +1 -1
- data/webpack/lib/EditableTextInput/EditableTextInput.js +81 -77
- data/webpack/lib/EditableTextInput/editableTextInput.scss +30 -28
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e395e2dfdbaf6a690ee47ed3885daa6d1bf02d66fa9ab4ddc9616214913e1bbe
|
4
|
+
data.tar.gz: c6804f4c3ac2245a891f54ddbec65b5736983466c05e18fedc3d0b6ff926c932
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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)
|
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_(
|
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
|
-
|
7
|
-
|
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
|
-
|
22
|
+
user_quotas,
|
11
23
|
:id,
|
12
24
|
:to_label,
|
13
|
-
|
14
|
-
|
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
|
-
|
134
|
-
|
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.
|
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
|
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,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
|
@@ -4,11 +4,10 @@ require 'rake/testtask'
|
|
4
4
|
|
5
5
|
# Tasks
|
6
6
|
namespace :foreman_resource_quota do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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,20 +24,19 @@
|
|
24
24
|
"@theforeman/vendor": ">= 12.0.1"
|
25
25
|
},
|
26
26
|
"devDependencies": {
|
27
|
-
"@babel/core": "^7.
|
27
|
+
"@babel/core": "^7.28.0",
|
28
28
|
"@sheerun/mutationobserver-shim": "^0.3.3",
|
29
|
-
"@testing-library/react": "^16.
|
29
|
+
"@testing-library/react": "^16.3.0",
|
30
30
|
"@testing-library/user-event": "^14.6.1",
|
31
|
-
"@theforeman/builder": ">=
|
32
|
-
"@theforeman/eslint-plugin-foreman": ">=
|
33
|
-
"@theforeman/find-foreman": ">=
|
34
|
-
"@theforeman/test": ">=
|
35
|
-
"@theforeman/vendor-dev": ">=
|
31
|
+
"@theforeman/builder": ">= 15.0.0",
|
32
|
+
"@theforeman/eslint-plugin-foreman": ">= 15.0.0",
|
33
|
+
"@theforeman/find-foreman": ">= 15.0.0",
|
34
|
+
"@theforeman/test": ">= 15.0.0",
|
35
|
+
"@theforeman/vendor-dev": ">= 15.0.0",
|
36
36
|
"babel-eslint": "^10.0.3",
|
37
37
|
"eslint": "^6.7.2",
|
38
38
|
"prettier": "^1.19.1",
|
39
|
-
"
|
40
|
-
"stylelint": "^
|
41
|
-
"stylelint-config-standard": "^37.0.0"
|
39
|
+
"stylelint": "^16.21.1",
|
40
|
+
"stylelint-config-standard": "^38.0.0"
|
42
41
|
}
|
43
42
|
}
|
@@ -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
|
@@ -42,6 +43,7 @@ exports[`ResourceQuotaEmptyState should render 1`] = `
|
|
42
43
|
onClose={[Function]}
|
43
44
|
ouiaId="foreman-resource-quota-create-modal"
|
44
45
|
ouiaSafe={true}
|
46
|
+
position="default"
|
45
47
|
showClose={true}
|
46
48
|
title="Create resource quota"
|
47
49
|
titleIconVariant={null}
|
@@ -56,6 +58,7 @@ exports[`ResourceQuotaEmptyState should render 1`] = `
|
|
56
58
|
"disk_gb": null,
|
57
59
|
"memory_mb": null,
|
58
60
|
"name": "",
|
61
|
+
"unassigned": false,
|
59
62
|
}
|
60
63
|
}
|
61
64
|
initialStatus={
|
@@ -74,6 +77,7 @@ exports[`ResourceQuotaEmptyState should render 1`] = `
|
|
74
77
|
isNewQuota={true}
|
75
78
|
onSubmit={[Function]}
|
76
79
|
quotaChangesCallback={null}
|
80
|
+
showAssignmentWarning={false}
|
77
81
|
/>
|
78
82
|
</Modal>
|
79
83
|
</div>
|
@@ -1 +1 @@
|
|
1
|
-
@import '
|
1
|
+
@import 'foremanReact/common/variables';
|
@@ -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';
|
@@ -34,7 +34,7 @@ const StaticDetail = ({
|
|
34
34
|
{isTextArea ? (
|
35
35
|
<TextArea
|
36
36
|
id={id}
|
37
|
-
onChange={onChange}
|
37
|
+
onChange={(_event, val) => onChange(val)}
|
38
38
|
value={value}
|
39
39
|
validated={validated}
|
40
40
|
isRequired={isRequired}
|
@@ -42,7 +42,8 @@ const StaticDetail = ({
|
|
42
42
|
) : (
|
43
43
|
<TextInput
|
44
44
|
id={id}
|
45
|
-
|
45
|
+
ouiaId={id}
|
46
|
+
onChange={(_event, val) => onChange(val)}
|
46
47
|
value={value}
|
47
48
|
validated={validated}
|
48
49
|
isRequired={isRequired}
|
@@ -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={
|
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;
|