kaui 4.0.27 → 4.0.29

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c47eb567efa6cc63312500bf5c3b9d14c7ee2aad9e20e2211badc2fe5812c669
4
- data.tar.gz: a711e1b5e1edf9525bade23dd0a1ff5ffdc9d86b0516f38ba051e8c8c1f07fc8
3
+ metadata.gz: fe249d7727e4018a5ac75692ea7bcd96eabf47c7a088831467e3e92a4fa4bfde
4
+ data.tar.gz: 2531c79e6230ef5b94c3488eebc672053d94a61fac348b44373baeaf7557fedc
5
5
  SHA512:
6
- metadata.gz: e7b440f6de6e78902d8f32322afc5fa57d548c042f8d76187126eb74b4a78f9ae29b346f4b250448ea3b8299b572cc230e4340efbab2f3de9585459e58c3b9a0
7
- data.tar.gz: 19f73d170488453def09356e5f55fb495dd4a12f9c1933cf012ae84d1c9cfd8521d8459d1b8b77d43610924243cf0a2399582ca3a48a598de7f0ce09091cf7bf
6
+ metadata.gz: 5b6d597074b06778d4985fed8a1f1343646f00b95837a7dbd041cb63ad0e583d0cb7d856596b01a42ca58be02875929aae6040f4ac57e9fb075dacaed63f305b
7
+ data.tar.gz: e14677cc4af0bd52371fb2ef9bcf62be6db87ffbfcfc1933dc544b1bb06a2841ad9cbcfc7d5728b42dd3e29c879179de8307b9ca581d67f2316833045765249c
@@ -0,0 +1,29 @@
1
+ <svg width="32" height="32" viewBox="-1 -1 38.5 38.5" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M8.96891 13.6363L17.1663 16.2316L19.867 20.7179L7.22926 20.294L5.87472 17.7276C4.70442 15.5102 6.6717 12.9089 8.96891 13.6363Z" fill="url(#paint0_linear_5656_117635)"/>
3
+ <path d="M2.73109 18.8862L23.0656 19.4261L31.0472 32.6526C31.9039 34.0722 30.9562 35.9453 29.3786 35.9506L14.6421 36C12.8282 36.0061 11.0908 35.2177 9.83259 33.8178L0.749744 23.712C-0.899153 21.8774 0.354861 18.8231 2.73109 18.8862Z" fill="url(#paint1_linear_5656_117635)"/>
4
+ <path d="M20.5179 0.919922L15.7246 7.75567L23.189 11.3725L24.8913 4.89582L20.5179 0.919922Z" fill="url(#paint2_linear_5656_117635)"/>
5
+ <path d="M27.5141 34.8764L15.4574 13.4093C14.4372 11.5929 14.565 9.35199 15.785 7.66255C16.4888 6.68792 17.8875 6.54241 18.778 7.35118L34.1718 21.3321C36.1567 23.1348 36.5779 26.0884 35.1757 28.371L31.1361 34.9469C30.2983 36.3107 28.2978 36.2717 27.5141 34.8764Z" fill="url(#paint3_linear_5656_117635)"/>
6
+ <path d="M32.0753 11.0799L21.0977 3.84213C19.1972 2.5891 20.3796 -0.360792 22.623 0.0364992L27.8828 0.967948C29.1553 1.19329 30.1995 2.09954 30.5986 3.32489L32.9165 10.4422C33.0801 10.9445 32.5171 11.3713 32.0753 11.0799Z" fill="url(#paint4_linear_5656_117635)"/>
7
+ <defs>
8
+ <linearGradient id="paint0_linear_5656_117635" x1="6.3713" y1="14.584" x2="17.3232" y2="20.2038" gradientUnits="userSpaceOnUse">
9
+ <stop stop-color="#004EEB"/>
10
+ <stop offset="1" stop-color="#2970FF"/>
11
+ </linearGradient>
12
+ <linearGradient id="paint1_linear_5656_117635" x1="1.04487" y1="21.155" x2="29.4032" y2="32.4876" gradientUnits="userSpaceOnUse">
13
+ <stop stop-color="#2970FF"/>
14
+ <stop offset="1" stop-color="#7CD4FD"/>
15
+ </linearGradient>
16
+ <linearGradient id="paint2_linear_5656_117635" x1="19.8881" y1="9.67384" x2="23.8884" y2="4.93559" gradientUnits="userSpaceOnUse">
17
+ <stop stop-color="#A6EF67"/>
18
+ <stop offset="1" stop-color="#FDE172"/>
19
+ </linearGradient>
20
+ <linearGradient id="paint3_linear_5656_117635" x1="15.1997" y1="8.83686" x2="29.3077" y2="34.3257" gradientUnits="userSpaceOnUse">
21
+ <stop stop-color="#A6EF67"/>
22
+ <stop offset="1" stop-color="#15B79E"/>
23
+ </linearGradient>
24
+ <linearGradient id="paint4_linear_5656_117635" x1="20.833" y1="1.40851" x2="32.7401" y2="9.43028" gradientUnits="userSpaceOnUse">
25
+ <stop stop-color="#FDE172"/>
26
+ <stop offset="1" stop-color="#EAAA08"/>
27
+ </linearGradient>
28
+ </defs>
29
+ </svg>
@@ -30,11 +30,23 @@ function searchParseOperatorText(text) {
30
30
  function getUrlParams() {
31
31
  var params = {};
32
32
  var advanceSearchQuery = new URLSearchParams(window.location.search).get('advance_search_query');
33
- var queryString = advanceSearchQuery !== null ? advanceSearchQuery : window.location.search.substring(1).replace(/ac_id/g, 'account_id');
33
+ // URLSearchParams already decoded advance_search_query (e.g. 'name[like]=%test%'), so it must not be decoded
34
+ // again: decodeURIComponent throws on the bare '%' of a LIKE value, which aborts the table's draw handler
35
+ // and leaves the DataTables loading indicator on screen.
36
+ var alreadyDecoded = advanceSearchQuery !== null;
37
+ var queryString = alreadyDecoded ? advanceSearchQuery : window.location.search.substring(1).replace(/ac_id/g, 'account_id');
38
+ var decode = function(s) {
39
+ if (alreadyDecoded) return s;
40
+ try {
41
+ return decodeURIComponent(s);
42
+ } catch (e) {
43
+ return s;
44
+ }
45
+ };
34
46
  var regex = /([^&=]+)=([^&]*)/g;
35
47
  var m;
36
48
  while (m = regex.exec(queryString)) {
37
- params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
49
+ params[decode(m[1])] = decode(m[2]);
38
50
  }
39
51
  return params;
40
52
  }
@@ -442,7 +454,15 @@ $(document).ready(function() {
442
454
  var searches = getSavedSearches();
443
455
  var params = searches[name];
444
456
  if (params) {
445
- var newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + params;
457
+ var query;
458
+ if (params.indexOf('_q=1') === 0) {
459
+ // Advanced search: same URL format as the Apply button (see _advanced_search_filterbar.html.erb),
460
+ // which is the one understood by the accounts, invoices and payments pages
461
+ query = 'advance_search_query=' + encodeURIComponent(decodeURIComponent(params));
462
+ } else {
463
+ query = params;
464
+ }
465
+ var newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + query;
446
466
  window.location.href = newUrl;
447
467
  }
448
468
  });
@@ -109,3 +109,79 @@
109
109
  width: 100% !important;
110
110
  outline: none;
111
111
  }
112
+
113
+ /* app/views/kaui/role_definitions/index.html.erb (search box styled like the Tags search) */
114
+
115
+ .kaui-users-index #role-definitions-table_filter {
116
+ margin-bottom: 0;
117
+ }
118
+
119
+ .kaui-users-index #role-definitions-table_filter label {
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 0.5rem;
123
+ margin-bottom: 0;
124
+ }
125
+
126
+ .kaui-users-index #role-definitions-table_filter input {
127
+ width: 20rem;
128
+ height: 2.2rem;
129
+ padding: 0.3rem 1rem;
130
+ margin-left: 0;
131
+ border: 0.0625rem solid var(--kb-outline-primary);
132
+ border-radius: 0.375rem;
133
+ font-weight: 500;
134
+ font-size: 0.875rem;
135
+ line-height: 1.25rem;
136
+ color: var(--kb-on-surface-secondary);
137
+ outline: none;
138
+ }
139
+
140
+ .kaui-users-index #role-definitions-table_filter input:focus {
141
+ border-color: var(--kb-primary);
142
+ }
143
+
144
+ /* Pagination styled like the Tags pagination */
145
+ .kaui-users-index div:has(> div > .dataTables_paginate) {
146
+ margin-top: 1.5rem;
147
+ }
148
+
149
+ .kaui-users-index .dataTables_paginate {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 0.5rem;
153
+ font-family: 'Inter', sans-serif;
154
+ font-size: 0.875rem;
155
+ font-weight: 500;
156
+ }
157
+
158
+ .kaui-users-index .dataTables_paginate span .paginate_button {
159
+ width: 2.5rem;
160
+ height: 2.5rem;
161
+ display: inline-flex !important;
162
+ align-items: center !important;
163
+ justify-content: center !important;
164
+ color: var(--kb-on-surface-quaternary) !important;
165
+ text-decoration: none;
166
+ border: 0 !important;
167
+ }
168
+
169
+ .kaui-users-index .dataTables_paginate .paginate_button:hover {
170
+ background-color: var(--kb-surface-container);
171
+ }
172
+
173
+ .kaui-users-index .dataTables_paginate .paginate_button.current {
174
+ background-color: var(--kb-surface-container) !important;
175
+ border-radius: 0.375rem !important;
176
+ color: var(--kb-on-surface-secondary) !important;
177
+ }
178
+
179
+ .kaui-users-index .dataTables_paginate .paginate_button.last,
180
+ .kaui-users-index .dataTables_paginate .paginate_button.previous {
181
+ border: 0.0625rem solid var(--kb-outline-primary);
182
+ border-radius: 0.5rem;
183
+ background-color: var(--kb-neutral-0);
184
+ padding: 0.5rem 0.875rem;
185
+ color: var(--kb-neutral-600);
186
+ }
187
+
@@ -67,6 +67,8 @@ module Kaui
67
67
  version_date: effective_date }
68
68
  end
69
69
 
70
+ @is_aviate_catalog = Dependencies::Aviate::Metering.aviate_catalog?(nil, options.merge(jwt_token: cookies[:jwt_token]))
71
+
70
72
  @latest_version = begin
71
73
  @catalog_versions[-1][:version_date]
72
74
  rescue StandardError
@@ -2,6 +2,56 @@
2
2
 
3
3
  module Kaui
4
4
  class RoleDefinitionsController < Kaui::EngineController
5
+ DEFAULT_PAGE_SIZE = 20
6
+
7
+ def index
8
+ @search_query = params[:q]
9
+ @offset = params[:offset] || 0
10
+ @limit = params[:limit] || DEFAULT_PAGE_SIZE
11
+
12
+ # The roles are paginated and searched on the server (see #pagination), this only checks that listing is supported
13
+ @max_nb_records = all_role_definitions.size
14
+ rescue StandardError => e
15
+ @max_nb_records = 0
16
+ if e.is_a?(KillBillClient::API::ClientError) && [404, 405].include?(e.response.code.to_i)
17
+ # Listing roles requires Kill Bill 0.25+
18
+ @list_unsupported = true
19
+ else
20
+ flash.now[:error] = "Error while retrieving roles: #{as_string(e)}"
21
+ end
22
+ end
23
+
24
+ def pagination
25
+ searcher = lambda do |search_key, offset, limit|
26
+ all = all_role_definitions
27
+ matching = filter_role_definitions(all, search_key)
28
+ page = matching.slice(offset, limit) || []
29
+ max_nb_records = all.size
30
+ total_nb_records = matching.size
31
+ page.define_singleton_method(:pagination_max_nb_records) { max_nb_records }
32
+ page.define_singleton_method(:pagination_total_nb_records) { total_nb_records }
33
+ page
34
+ end
35
+
36
+ data_extractor = lambda do |role_definition, column|
37
+ [
38
+ role_definition.role,
39
+ Array(role_definition.permissions).join(', ')
40
+ ][column]
41
+ end
42
+
43
+ formatter = lambda do |role_definition|
44
+ edit_path = view_context.edit_role_definition_path(role_definition.role)
45
+ [
46
+ view_context.link_to(role_definition.role, edit_path),
47
+ Array(role_definition.permissions).join(', '),
48
+ view_context.link_to('Edit', edit_path, class: 'kaui-button edit-button custom-hover text-decoration-none')
49
+ ]
50
+ end
51
+
52
+ paginate searcher, data_extractor, formatter
53
+ end
54
+
5
55
  def new
6
56
  @role_definition = Kaui::RoleDefinition.new
7
57
 
@@ -17,6 +67,20 @@ module Kaui
17
67
  }
18
68
  end
19
69
 
70
+ def edit
71
+ @role_definition = Kaui::RoleDefinition.find_by_name(params.require(:id), options_for_klient)
72
+
73
+ # Kill Bill answers 200 with no permissions for a role that does not exist
74
+ if Array(@role_definition.permissions).empty?
75
+ redirect_to role_definitions_path, flash: { error: "Role '#{params[:id]}' was not found" }
76
+ return
77
+ end
78
+
79
+ @role_definition.permissions = Array(@role_definition.permissions).join(',')
80
+ rescue StandardError => e
81
+ redirect_to role_definitions_path, flash: { error: "Error while retrieving role: #{as_string(e)}" }
82
+ end
83
+
20
84
  def create
21
85
  # Sanity is done on the server side
22
86
  @role_definition = Kaui::RoleDefinition.new(params.require(:role_definition))
@@ -49,5 +113,35 @@ module Kaui
49
113
  render action: :new
50
114
  end
51
115
  end
116
+
117
+ def update
118
+ @role_definition = Kaui::RoleDefinition.new(role: params.require(:id),
119
+ permissions: params.require(:role_definition)[:permissions].to_s.split(',').map(&:strip).compact_blank)
120
+
121
+ begin
122
+ @role_definition.update(current_user.kb_username, params[:reason], params[:comment], options_for_klient)
123
+ redirect_to role_definitions_path, notice: "Role '#{@role_definition.role}' was successfully updated"
124
+ rescue StandardError => e
125
+ @role_definition.permissions = @role_definition.permissions.join(',')
126
+ flash.now[:error] = "Error while updating role: #{as_string(e)}"
127
+ render action: :edit
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ def all_role_definitions
134
+ Kaui::RoleDefinition.find_all(options_for_klient).sort_by { |role_definition| role_definition.role.to_s }
135
+ end
136
+
137
+ # Case-insensitive match on the role name or any of its permissions
138
+ def filter_role_definitions(role_definitions, search_key)
139
+ return role_definitions if search_key.blank?
140
+
141
+ needle = search_key.to_s.downcase
142
+ role_definitions.select do |role_definition|
143
+ role_definition.role.to_s.downcase.include?(needle) || Array(role_definition.permissions).any? { |permission| permission.to_s.downcase.include?(needle) }
144
+ end
145
+ end
52
146
  end
53
147
  end
@@ -2,5 +2,11 @@
2
2
 
3
3
  module Kaui
4
4
  class RoleDefinition < KillBillClient::Model::RoleDefinition
5
+ # Lists all the roles (Kill Bill 0.25+). Defined here because killbill-client releases up to 4.0.9 do not expose it.
6
+ def self.find_all(options = {})
7
+ return super if defined?(super)
8
+
9
+ get "#{KillBillClient::Model::Security::KILLBILL_API_SECURITY_PREFIX}/roles", {}, options
10
+ end
5
11
  end
6
12
  end
@@ -108,7 +108,7 @@ $(document).ready(function() {
108
108
  url: "<%= accounts_pagination_path(:ordering => @ordering, :format => :json) %>",
109
109
  headers: {"X-CSRF-Token": $("meta[name='csrf-token']").attr("content")},
110
110
  data: function(d) {
111
- d.advance_search_query = "<%= j(@advance_search_query.to_s) %>";
111
+ d.advance_search_query = "<%= raw j(@advance_search_query.to_s) %>"; // raw: ERB would turn '&' into '&amp;'; j() already makes it safe for a JS string
112
112
  return d;
113
113
  },
114
114
  dataSrc: function(json) {
@@ -21,13 +21,25 @@
21
21
 
22
22
 
23
23
  <% if can?(:delete, Kaui::AdminTenant) %>
24
- <%= link_to admin_tenant_delete_catalog_path(id: @tenant.id), :method => :delete, class: "text-decoration-none" do %>
25
- <%= render "kaui/components/button/button", {
26
- label: "Delete Catalog",
27
- variant: "outline-secondary d-inline-flex align-items-center gap-1",
28
- type: "button",
29
- html_class: "kaui-button dots-menu custom-hover",
30
- } %>
24
+ <% if @is_aviate_catalog %>
25
+ <span class="d-inline-block" tabindex="0" title="Aviate catalog cannot be deleted">
26
+ <%= render "kaui/components/button/button", {
27
+ label: "Delete Catalog",
28
+ variant: "outline-secondary d-inline-flex align-items-center gap-1",
29
+ type: "button",
30
+ html_class: "kaui-button dots-menu custom-hover",
31
+ html_options: { disabled: true, style: "pointer-events: none;" }
32
+ } %>
33
+ </span>
34
+ <% else %>
35
+ <%= link_to admin_tenant_delete_catalog_path(id: @tenant.id), :method => :delete, class: "text-decoration-none" do %>
36
+ <%= render "kaui/components/button/button", {
37
+ label: "Delete Catalog",
38
+ variant: "outline-secondary d-inline-flex align-items-center gap-1",
39
+ type: "button",
40
+ html_class: "kaui-button dots-menu custom-hover",
41
+ } %>
42
+ <% end %>
31
43
  <% end %>
32
44
  <% end %>
33
45
  <%= link_to download_catalog_xml_path(id: @tenant.id, effective_date: @latest_version), id: "download_link", class: "text-decoration-none" do %>
@@ -17,7 +17,7 @@
17
17
  <div class="form-group d-flex pb-3">
18
18
  <%= f.label :amount, 'Amount', class: 'col-sm-3 control-label' %>
19
19
  <div class="input-group">
20
- <%= f.number_field :amount, step: :any, id: 'charge_amount', class: 'form-control amount-input', placeholder: '$ 0.00' %>
20
+ <%= f.number_field :amount, step: :any, id: 'charge_amount', class: 'form-control amount-input', placeholder: '0.00' %>
21
21
  <div class="input-group-append">
22
22
  <% if @invoice.present? %>
23
23
  <span class="input-group-text"><%= @invoice.currency %></span>
@@ -4,7 +4,7 @@
4
4
  <div class="form-group d-flex pb-3">
5
5
  <%= f.label :amount, 'Amount', class: 'col-sm-3 control-label' %>
6
6
  <div class="input-group">
7
- <%= f.number_field :amount, step: :any, id: 'charge_amount', class: 'form-control', placeholder: '$ 0.00' %>
7
+ <%= f.number_field :amount, step: :any, id: 'charge_amount', class: 'form-control', placeholder: '0.00' %>
8
8
 
9
9
  <div>
10
10
  <% if @invoice.present? %>
@@ -14,6 +14,7 @@
14
14
  <div class="row row-cols-1 row-cols-md-2 g-4 mb-5">
15
15
  <%= render partial: "kaui/components/dashboard/setting", locals: { path: kaui_engine.admin_tenants_path, icon: "kaui/dashboard/users.svg", title: "Tenant", desc: "Run multiple tenants for each Kill Bill deployment." } %>
16
16
  <%= render partial: "kaui/components/dashboard/setting", locals: { path: kaui_engine.admin_allowed_users_path, icon: "kaui/dashboard/users.svg", title: "Users", desc: "Manage users in Kaui – create user roles, create and edit users, assign roles and tenants to users." } %>
17
+ <%= render partial: "kaui/components/dashboard/setting", locals: { path: kaui_engine.role_definitions_path, icon: "kaui/dashboard/users.svg", title: "Roles", desc: "View and edit role definitions and the permissions they grant." } %>
17
18
  <%= render partial: "kaui/components/dashboard/setting", locals: { path: kaui_engine.tags_path, icon: "kaui/dashboard/tags.svg", title: "Tags", desc: "Apply one or more tags to accounts and invoices, a tag being a single value." } %>
18
19
  <%= render partial: "kaui/components/dashboard/setting", locals: { path: kaui_engine.custom_fields_path, icon: "kaui/dashboard/custom-field.svg", title: "Custom Fields", desc: "Create custom fields (key/value pairs) and attach them to objects like accounts, payments." } %>
19
20
  <% if kpm_plugin_installed %>
@@ -4,6 +4,7 @@
4
4
  <meta name="viewport" content="width=device-width, initial-scale=1">
5
5
 
6
6
  <title>Kaui</title>
7
+ <%= favicon_link_tag 'kaui/favicon.svg', rel: 'icon', type: 'image/svg+xml' %>
7
8
  <%= stylesheet_link_tag 'application', :media => 'all' %>
8
9
  <%= javascript_include_tag 'application' %>
9
10
 
@@ -41,6 +41,7 @@
41
41
  </button>
42
42
  <ul class="dropdown-menu">
43
43
  <li><%= link_to 'Users', kaui_engine.admin_allowed_users_path, class: 'dropdown-item' %></li>
44
+ <li><%= link_to 'Roles', kaui_engine.role_definitions_path, class: 'dropdown-item' %></li>
44
45
  <li><%= link_to 'Tenants', kaui_engine.admin_tenants_path, class: 'dropdown-item' %></li>
45
46
  <li><%= link_to 'Admin', kaui_engine.admin_path, class: 'dropdown-item' %></li>
46
47
  </ul>
@@ -4,6 +4,7 @@
4
4
  session[:tenant_id].present? ? { label: session["kb_tenant_name"], path: kaui_engine.admin_tenant_path(session[:tenant_id]), controller_suffix: "#{session[:tenant_id]}", icon_path: 'kaui/setting/account.svg' } : nil,
5
5
  { label: 'Tenants', path: kaui_engine.admin_tenants_path, controller_suffix: 'admin_tenants', icon_path: 'kaui/setting/tenants.svg' },
6
6
  { label: 'Users', path: kaui_engine.admin_allowed_users_path, controller_suffix: 'admin_allowed_users', icon_path: 'kaui/setting/users.svg' },
7
+ { label: 'Roles', path: kaui_engine.role_definitions_path, controller_suffix: 'role_definitions', icon_path: 'kaui/setting/users.svg' },
7
8
  { label: 'Tags', path: kaui_engine.tags_path, controller_suffix: 'tags', icon_path: 'kaui/setting/tags.svg' },
8
9
  { label: 'Tag Definitions', path: kaui_engine.tag_definitions_path, controller_suffix: 'tag_definitions', icon_path: 'kaui/setting/tag-definitions.svg' },
9
10
  { label: 'Custom Fields', path: kaui_engine.custom_fields_path, controller_suffix: 'custom_fields', icon_path: 'kaui/setting/custom-field.svg' }
@@ -1,8 +1,12 @@
1
- <%= form_for @role_definition, html: { class: 'form-horizontal' } do |f| %>
1
+ <% editing = local_assigns[:editing] %>
2
+ <%= form_for @role_definition,
3
+ url: (editing ? role_definition_path(@role_definition.role) : role_definitions_path),
4
+ method: (editing ? :put : :post),
5
+ html: { class: 'form-horizontal' } do |f| %>
2
6
  <div class="form-group d-flex pb-3">
3
7
  <%= f.label :role, 'Role name', class: 'col-sm-3 control-label' %>
4
8
  <div class="col-sm-9">
5
- <%= f.text_field :role, class: 'form-control' %>
9
+ <%= f.text_field :role, class: 'form-control', readonly: editing %>
6
10
  </div>
7
11
  </div>
8
12
  <div class="form-group d-flex pb-3 premissions-group">
@@ -34,7 +38,7 @@
34
38
  } %>
35
39
 
36
40
  <%= render "kaui/components/button/button", {
37
- label: 'Save User',
41
+ label: (editing ? 'Update Role' : 'Save Role'),
38
42
  variant: "outline-secondary d-inline-flex align-items-center gap-1",
39
43
  type: "submit",
40
44
  html_class: "kaui-dropdown custom-hover",
@@ -47,7 +51,7 @@
47
51
  const input = document.getElementById('chip-input');
48
52
  const wrapper = document.getElementById('chip-wrapper');
49
53
  const hidden = document.getElementById('permissions_hidden');
50
- let permissions = [];
54
+ let permissions = hidden.value ? hidden.value.split(',').map(p => p.trim()).filter(Boolean) : [];
51
55
 
52
56
  function renderChips() {
53
57
  // Remove all chips except the input box
@@ -91,6 +95,8 @@
91
95
  input.value = '';
92
96
  }
93
97
 
98
+ renderChips();
99
+
94
100
  input.addEventListener('keydown', function (e) {
95
101
  if (e.key === 'Enter' || e.key === ',') {
96
102
  e.preventDefault();
@@ -0,0 +1,16 @@
1
+ <div class="register kaui-container kaui-role-definitions-new">
2
+ <div class="">
3
+ <div class="" style="max-width: 37.5rem; margin: 0 auto;">
4
+ <h5 class="new-role-definition-title border-bottom">
5
+ <span class="icon-container">
6
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
7
+ <path d="M13.3334 5.83333C13.3334 7.67428 11.841 9.16667 10.0001 9.16667C8.15913 9.16667 6.66675 7.67428 6.66675 5.83333C6.66675 3.99238 8.15913 2.5 10.0001 2.5C11.841 2.5 13.3334 3.99238 13.3334 5.83333Z" stroke="#414651" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
8
+ <path d="M15.8334 15.8337C15.8334 13.5325 13.9679 11.667 11.6667 11.667H8.33341C6.03223 11.667 4.16675 13.5325 4.16675 15.8337V17.5003H15.8334V15.8337Z" stroke="#414651" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
9
+ </svg>
10
+ </span>
11
+ Edit Role Definition
12
+ </h5>
13
+ <%= render 'form', editing: true %>
14
+ </div>
15
+ </div>
16
+ </div>
@@ -0,0 +1,79 @@
1
+ <div class="kaui-container kaui-users-index">
2
+ <%= render partial: 'kaui/components/breadcrumb/breadcrumb', locals: {
3
+ breadcrumbs: [
4
+ { label: 'Settings', href: '/' },
5
+ { label: "Roles", href: '#' }
6
+ ]
7
+ } %>
8
+
9
+ <div class="d-flex" style="gap: 4rem;">
10
+ <%= render :template => 'kaui/layouts/kaui_setting_sidebar' %>
11
+
12
+ <div class="users-details w-100">
13
+ <div class="d-flex flex-column">
14
+ <div class="users-details-header">
15
+ <h2>Roles</h2>
16
+ <span class="d-flex align-items-start gap-3">
17
+ <span id="role-definitions-search"></span>
18
+ <% if can? :create, Kaui::AdminTenant %>
19
+ <%= link_to new_role_definition_path do %>
20
+ <%= render "kaui/components/button/button", {
21
+ label: "Create Role",
22
+ icon: "kaui/plus.svg",
23
+ variant: "outline-secondary d-inline-flex align-items-center gap-1",
24
+ type: "button",
25
+ html_class: "kaui-dropdown custom-hover",
26
+ } %>
27
+ <% end %>
28
+ <% end %>
29
+ </span>
30
+ </div>
31
+
32
+ <div class="mt-4">
33
+ <% if @list_unsupported %>
34
+ <p>Listing roles requires Kill Bill 0.25 or later.</p>
35
+ <% else %>
36
+ <table id="role-definitions-table" class="allowed-users-table" data-search="<%= @search_query %>">
37
+ <thead>
38
+ <tr>
39
+ <th>Role</th>
40
+ <th>Permissions</th>
41
+ <th></th>
42
+ </tr>
43
+ </thead>
44
+ <tbody>
45
+ </tbody>
46
+ </table>
47
+ <% end %>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ <% unless @list_unsupported %>
55
+ <%= javascript_tag do %>
56
+ $(document).ready(function() {
57
+ $('#role-definitions-table').DataTable({
58
+ "dom": "frt<'row'<'col-md-6'i><'col-md-6'p>>",
59
+ "pagingType": "full_numbers",
60
+ "language": {
61
+ "info": "Showing _START_ to _END_ of _TOTAL_ entries",
62
+ "emptyTable": "No roles found.",
63
+ "zeroRecords": "No roles found."
64
+ },
65
+ "pageLength": <%= @limit.to_i %>,
66
+ "displayStart": <%= @offset.to_i %>,
67
+ "ordering": false,
68
+ "processing": true,
69
+ "serverSide": true,
70
+ "search": {"search": $('#role-definitions-table').attr('data-search') || ''},
71
+ "ajax": "<%= role_definitions_pagination_path(:format => :json) %>",
72
+ "initComplete": function() {
73
+ // Show the search box next to the Create Role button, keeping the standard DataTables search styling
74
+ $('#role-definitions-table_filter').appendTo('#role-definitions-search');
75
+ }
76
+ });
77
+ });
78
+ <% end %>
79
+ <% end %>
@@ -15,7 +15,7 @@
15
15
  <div class="form-group d-flex pb-3 border-bottom mb-3">
16
16
  <%= f.label :amount, 'Amount', class: 'col-sm-3 control-label' %>
17
17
  <div class="input-group">
18
- <%= f.number_field :amount, step: :any, id: 'charge_amount', class: 'form-control', placeholder: '$ 0.00', disabled: fix_transaction_state %>
18
+ <%= f.number_field :amount, step: :any, id: 'charge_amount', class: 'form-control', placeholder: '0.00', disabled: fix_transaction_state %>
19
19
  <div class="input-group-append">
20
20
  <% if @invoice.present? %>
21
21
  <span class="input-group-text"><%= @invoice.currency %></span>
data/config/routes.rb CHANGED
@@ -226,5 +226,8 @@ Kaui::Engine.routes.draw do
226
226
  post '/add_tenant' => 'admin_allowed_users#add_tenant', :as => 'add_tenant'
227
227
  end
228
228
 
229
- resources :role_definitions, only: %i[new create]
229
+ scope '/role_definitions' do
230
+ get '/pagination' => 'role_definitions#pagination', :as => 'role_definitions_pagination'
231
+ end
232
+ resources :role_definitions, only: %i[index new create edit update], constraints: { id: %r{[^/]+} }
230
233
  end
data/lib/kaui/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kaui
4
- VERSION = '4.0.27'
4
+ VERSION = '4.0.29'
5
5
  end
data/lib/kaui.rb CHANGED
@@ -172,7 +172,7 @@ module Kaui
172
172
  values = fields.map do |attr|
173
173
  case attr
174
174
  when 'auth_amount', 'captured_amount', 'purchased_amount', 'refunded_amount', 'credited_amount'
175
- view_context.humanized_money_with_symbol(payment&.send(attr.downcase))
175
+ view_context.humanized_money_with_symbol(payment&.send("#{attr.downcase}_to_money"))
176
176
  when 'payment_number'
177
177
  view_context.link_to(payment.payment_number, view_context.url_for(controller: :payments, action: :show, account_id: payment.account_id, id: payment.payment_id))
178
178
  when 'payment_date'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kaui
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.27
4
+ version: 4.0.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kill Bill core team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-06 00:00:00.000000000 Z
11
+ date: 2026-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -339,6 +339,7 @@ files:
339
339
  - app/assets/images/kaui/duck.png
340
340
  - app/assets/images/kaui/eye-off.svg
341
341
  - app/assets/images/kaui/eye.svg
342
+ - app/assets/images/kaui/favicon.svg
342
343
  - app/assets/images/kaui/file-upload.svg
343
344
  - app/assets/images/kaui/generate.svg
344
345
  - app/assets/images/kaui/header/account.svg
@@ -663,6 +664,8 @@ files:
663
664
  - app/views/kaui/registrations/_form.html.erb
664
665
  - app/views/kaui/registrations/new.html.erb
665
666
  - app/views/kaui/role_definitions/_form.html.erb
667
+ - app/views/kaui/role_definitions/edit.html.erb
668
+ - app/views/kaui/role_definitions/index.html.erb
666
669
  - app/views/kaui/role_definitions/new.html.erb
667
670
  - app/views/kaui/sessions/_form.html.erb
668
671
  - app/views/kaui/sessions/new.html.erb
@@ -735,7 +738,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
735
738
  - !ruby/object:Gem::Version
736
739
  version: '0'
737
740
  requirements: []
738
- rubygems_version: 3.4.10
741
+ rubygems_version: 3.4.19
739
742
  signing_key:
740
743
  specification_version: 4
741
744
  summary: Killbill Admin UI mountable engine