diff --git a/Gemfile b/Gemfile
index 0bec5644..77f90f87 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,6 +7,10 @@ gemspec
gem 'rails', '~> 7.0.1'
+# Lock minitest to 5.x until Rails 7.1+ adds Minitest 6.0 support
+# Minitest 6.0.0 was released Dec 2024 with breaking API changes
+gem 'minitest', '~> 5.0'
+
# This fix is temporary until the next release of the gem
# See https://stackoverflow.com/questions/79360526/uninitialized-constant-activesupportloggerthreadsafelevellogger-nameerror
gem 'concurrent-ruby', '1.3.4'
diff --git a/app/assets/javascripts/kaui/kaui_override.js b/app/assets/javascripts/kaui/kaui_override.js
index 75e14622..1b3c5377 100644
--- a/app/assets/javascripts/kaui/kaui_override.js
+++ b/app/assets/javascripts/kaui/kaui_override.js
@@ -439,7 +439,7 @@ function setObjectIdPopover(){
// Custom tooltip function for object IDs
// attributes:
// data-id = content of the tooltip, object id; required
-// title = title of the tooltip; not required
+// data-title = title of the tooltip; not required
function setObjectIdTooltip() {
// Remove any existing tooltips
$(".custom-tooltip").remove();
@@ -476,6 +476,9 @@ function showCustomTooltip(element, objectId) {
var position = $element.offset();
var elementHeight = $element.outerHeight();
var elementWidth = $element.outerWidth();
+
+ // Get custom title from data-title attribute or default to "Subscription ID"
+ var tooltipTitle = $element.data("title") || "Subscription ID";
// Create tooltip content with header and custom SVG icon
var svgIcon =
@@ -485,7 +488,7 @@ function showCustomTooltip(element, objectId) {
"";
var tooltipContent =
- '
' +
+ '' +
'' +
'' +
objectId +
diff --git a/app/controllers/kaui/admin_allowed_users_controller.rb b/app/controllers/kaui/admin_allowed_users_controller.rb
index 3410e0c9..54ea5c90 100644
--- a/app/controllers/kaui/admin_allowed_users_controller.rb
+++ b/app/controllers/kaui/admin_allowed_users_controller.rb
@@ -6,6 +6,10 @@ class AdminAllowedUsersController < Kaui::EngineController
def index
@allowed_users = retrieve_allowed_users_for_current_user
+ @roles_by_user = {}
+ @allowed_users.each do |user|
+ @roles_by_user[user.kb_username] = roles_for_user(user)
+ end
end
def new
diff --git a/app/controllers/kaui/payments_controller.rb b/app/controllers/kaui/payments_controller.rb
index e75849fc..862c720a 100644
--- a/app/controllers/kaui/payments_controller.rb
+++ b/app/controllers/kaui/payments_controller.rb
@@ -24,6 +24,7 @@ def download
if all_fields_checked
columns = KillBillClient::Model::PaymentAttributes.instance_variable_get('@json_attributes') - %w[transactions audit_logs]
+ columns += %w[payment_date status] # additional fields not part of the model
csv_headers = columns.dup
Kaui::Payment::REMAPPING_FIELDS.each do |k, v|
index = csv_headers.index(k)
diff --git a/app/helpers/kaui/admin_allowed_users_helper.rb b/app/helpers/kaui/admin_allowed_users_helper.rb
new file mode 100644
index 00000000..28e8d277
--- /dev/null
+++ b/app/helpers/kaui/admin_allowed_users_helper.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Kaui
+ module AdminAllowedUsersHelper
+ # Check if a user can be deleted
+ # Returns false if the user is an admin or the current logged-in user
+ def can_delete_user?(user, user_roles)
+ is_admin = user_roles.include?('admin') || user.kb_username == Kaui.root_username
+ is_current_user = user.kb_username == current_user.kb_username
+
+ !is_admin && !is_current_user
+ end
+ end
+end
diff --git a/app/views/kaui/admin_allowed_users/index.html.erb b/app/views/kaui/admin_allowed_users/index.html.erb
index 85de7f78..2df058fd 100644
--- a/app/views/kaui/admin_allowed_users/index.html.erb
+++ b/app/views/kaui/admin_allowed_users/index.html.erb
@@ -60,11 +60,15 @@
<%= link_to u.kb_username, admin_allowed_user_path(u.id) %> |
<%= u.description %> |
- <%= form_tag kaui_engine.admin_allowed_user_path(u.id), method: :delete, class: 'd-inline', onsubmit: "return confirm('Are you sure?');" do %>
- <%= button_tag type: 'submit', class: 'btn btn-outline-secondary d-inline-flex align-items-center gap-1 kaui-button delete-button custom-hover' do %>
- Delete
+ <% user_roles = @roles_by_user[u.kb_username] || [] %>
+ <% if can_delete_user?(u, user_roles) %>
+ <%= form_tag kaui_engine.admin_allowed_user_path(u.id), method: :delete, class: 'd-inline', onsubmit: "return confirm('Are you sure?');" do %>
+ <%= button_tag type: 'submit', class: 'btn btn-outline-secondary d-inline-flex align-items-center gap-1 kaui-button delete-button custom-hover' do %>
+ Delete
+ <% end %>
<% end %>
<% end %>
+
<%= link_to kaui_engine.edit_admin_allowed_user_path(u.id), class: 'text-decoration-none' do %>
<%= render "kaui/components/button/button", {
label: "Edit",
diff --git a/app/views/kaui/admin_allowed_users/show.html.erb b/app/views/kaui/admin_allowed_users/show.html.erb
index 1dfcbc58..f7795dd7 100644
--- a/app/views/kaui/admin_allowed_users/show.html.erb
+++ b/app/views/kaui/admin_allowed_users/show.html.erb
@@ -6,21 +6,23 @@
User details
- <%= render "kaui/components/button/button", {
- label: 'Delete',
- variant: "outline-secondary d-inline-flex align-items-center gap-1",
- type: "button",
- html_class: "kaui-button delete-button custom-hover",
- html_options: {
- data: {
- confirm: "Are you sure?",
- method: "delete",
- turbo: false, # Optional: disable Turbo if using Rails Turbo
- "url": kaui_engine.admin_allowed_user_path(@allowed_user.id)
- },
- onclick: "if (confirm(this.dataset.confirm)) { Rails.ajax({ url: this.dataset.url, type: this.dataset.method }); }"
- }
- } %>
+ <% if can_delete_user?(@allowed_user, @roles) %>
+ <%= render "kaui/components/button/button", {
+ label: 'Delete',
+ variant: "outline-secondary d-inline-flex align-items-center gap-1",
+ type: "button",
+ html_class: "kaui-button delete-button custom-hover",
+ html_options: {
+ data: {
+ confirm: "Are you sure?",
+ method: "delete",
+ turbo: false, # Optional: disable Turbo if using Rails Turbo
+ "url": kaui_engine.admin_allowed_user_path(@allowed_user.id)
+ },
+ onclick: "if (confirm(this.dataset.confirm)) { Rails.ajax({ url: this.dataset.url, type: this.dataset.method }); }"
+ }
+ } %>
+ <% end %>
<%= link_to kaui_engine.edit_admin_allowed_user_path(@allowed_user.id),
:class => '' do %>
diff --git a/app/views/kaui/admin_tenants/new_catalog.html.erb b/app/views/kaui/admin_tenants/new_catalog.html.erb
index 3e4487ff..5ca31677 100644
--- a/app/views/kaui/admin_tenants/new_catalog.html.erb
+++ b/app/views/kaui/admin_tenants/new_catalog.html.erb
@@ -264,55 +264,58 @@ $(document).ready(function() {
recompute_available_base_products_for_ao();
});
- // Custom validation since HTML5 validation is not working
- // (possibly due to jQuery autocomplete or other JavaScript interference)
- $('#catalog_simple form').on('submit', function(e) {
- var isValid = true;
- var pattern = /^[a-zA-Z_][\w\.-]*$/;
-
- // Validate Product Name
- var productName = $('#simple_plan_product_name').val().trim();
- var productNameField = $('#simple_plan_product_name');
-
- if (productName === '') {
- showError(productNameField, 'Product Name is required');
- isValid = false;
- } else if (!pattern.test(productName)) {
- showError(productNameField, 'Product Name must start with a letter or underscore, and can only contain letters, digits, underscores, hyphens, and periods');
- isValid = false;
- } else {
- clearError(productNameField);
- }
-
- // Validate Plan Name
- var planName = $('#simple_plan_plan_id').val().trim();
- var planNameField = $('#simple_plan_plan_id');
-
- if (planName === '') {
- showError(planNameField, 'Plan Name is required');
- isValid = false;
- } else if (!pattern.test(planName)) {
- showError(planNameField, 'Plan Name must start with a letter or underscore, and can only contain letters, digits, underscores, hyphens, and periods');
- isValid = false;
- } else {
- clearError(planNameField);
- }
-
- if (!isValid) {
- e.preventDefault();
- e.stopPropagation();
- // Scroll to first error
- $('.error-message:first').get(0)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
- return false;
- }
-
- return true;
- });
+ // Realtime validation function
+ function validateField(field) {
+ var pattern = /^[a-zA-Z_][\w\.-]*$/;
+ var value = field.val().trim();
+ var fieldName = field.attr('id') === 'simple_plan_product_name' ? 'Product Name' : 'Plan Name';
+
+ if (value === '') {
+ showError(field, fieldName + ' is required');
+ return false;
+ } else if (!pattern.test(value)) {
+ showError(field, fieldName + ' must start with a letter or underscore, and can only contain letters, digits, underscores, hyphens, and periods');
+ return false;
+ } else {
+ clearError(field);
+ return true;
+ }
+ }
+
+ // Realtime validation on input
+ $('#simple_plan_product_name, #simple_plan_plan_id').on('input', function() {
+ validateField($(this));
+ });
+
+ // Validation on blur (when user leaves the field)
+ $('#simple_plan_product_name, #simple_plan_plan_id').on('blur', function() {
+ validateField($(this));
+ });
+
+ // Custom validation on form submit
+ $('#catalog_simple form').on('submit', function(e) {
+ var isValid = true;
+
+ // Validate Product Name
+ if (!validateField($('#simple_plan_product_name'))) {
+ isValid = false;
+ }
- // Clear errors on input
- $('#simple_plan_product_name, #simple_plan_plan_id').on('input', function() {
- clearError($(this));
- });
+ // Validate Plan Name
+ if (!validateField($('#simple_plan_plan_id'))) {
+ isValid = false;
+ }
+
+ if (!isValid) {
+ e.preventDefault();
+ e.stopPropagation();
+ // Scroll to first error
+ $('.error-message:first').get(0)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ return false;
+ }
+
+ return true;
+ });
});
function showError(field, message) {
diff --git a/app/views/kaui/invoices/_invoice_table.html.erb b/app/views/kaui/invoices/_invoice_table.html.erb
index 3e2f6658..a2117c93 100644
--- a/app/views/kaui/invoices/_invoice_table.html.erb
+++ b/app/views/kaui/invoices/_invoice_table.html.erb
@@ -91,7 +91,11 @@
|
<% end %>
- <%= item.pretty_plan_name.blank? || !item.item_type.in?(%w{USAGE RECURRING}) ? item.description : item.pretty_plan_name %> |
+
+
+ <%= item.pretty_plan_name.blank? || !item.item_type.in?(%w{USAGE RECURRING}) ? item.description : item.pretty_plan_name %>
+
+ |
<%= item.start_date.html_safe if item.start_date %> |
<%= item.end_date.html_safe if item.end_date %> |
<%= item.subscription_id %> |
@@ -149,4 +153,11 @@
}
});
}
+
+ $(document).ready(function() {
+ // Initialize tooltips for invoice item IDs
+ if (typeof setObjectIdTooltip === 'function') {
+ setObjectIdTooltip();
+ }
+ });