From a00e4ad68d72e13b0ffb664fb63e3bab65acd613 Mon Sep 17 00:00:00 2001 From: Tung Le Date: Sat, 20 Dec 2025 11:05:59 +0700 Subject: [PATCH 1/6] Validate product name and catalog name realtime --- .../kaui/admin_tenants/new_catalog.html.erb | 99 ++++++++++--------- 1 file changed, 51 insertions(+), 48 deletions(-) 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) { From 737378346e3a87d11a6c1689e48cd2681469474b Mon Sep 17 00:00:00 2001 From: Tung Le Date: Sat, 20 Dec 2025 19:45:38 +0700 Subject: [PATCH 2/6] Fix payment missing fields --- app/controllers/kaui/payments_controller.rb | 1 + 1 file changed, 1 insertion(+) 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) From b0346ce0fb031734d14249c193f95f113cb3f375 Mon Sep 17 00:00:00 2001 From: Tung Le Date: Sun, 21 Dec 2025 14:08:18 +0700 Subject: [PATCH 3/6] Add tooltip to invoice item list --- app/assets/javascripts/kaui/kaui_override.js | 7 +++++-- app/views/kaui/invoices/_invoice_table.html.erb | 13 ++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) 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 = - '
Subscription ID
' + + '
' + tooltipTitle + '
' + '
' + '' + objectId + 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(); + } + }); From 6a981fed79a485eb1e09978f5ff58a152bca0eeb Mon Sep 17 00:00:00 2001 From: Tung Le Date: Mon, 22 Dec 2025 12:35:06 +0700 Subject: [PATCH 4/6] Do not allow delete admin users --- .../kaui/admin_allowed_users_controller.rb | 4 +++ .../kaui/admin_allowed_users/index.html.erb | 13 +++++-- .../kaui/admin_allowed_users/show.html.erb | 35 +++++++++++-------- 3 files changed, 34 insertions(+), 18 deletions(-) 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/views/kaui/admin_allowed_users/index.html.erb b/app/views/kaui/admin_allowed_users/index.html.erb index 85de7f78..d9c53ec0 100644 --- a/app/views/kaui/admin_allowed_users/index.html.erb +++ b/app/views/kaui/admin_allowed_users/index.html.erb @@ -60,11 +60,18 @@ <%= 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] || [] %> + <% is_admin = user_roles.include?('admin') %> + <% is_current_user = u.kb_username == current_user.kb_username %> + + <% unless is_admin || is_current_user %> + <%= 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..46f1118a 100644 --- a/app/views/kaui/admin_allowed_users/show.html.erb +++ b/app/views/kaui/admin_allowed_users/show.html.erb @@ -6,21 +6,26 @@

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 }); }" - } - } %> + <% is_admin = @roles.include?('admin') %> + <% is_current_user = @allowed_user.kb_username == current_user.kb_username %> + + <% unless is_admin || is_current_user %> + <%= 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 %> From fc64fea68ae464a8f2634270bd8da3b2cca5d959 Mon Sep 17 00:00:00 2001 From: Tung Le Date: Tue, 23 Dec 2025 09:48:24 +0700 Subject: [PATCH 5/6] Fix CI error --- Gemfile | 4 ++++ 1 file changed, 4 insertions(+) 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' From a1df3a46e6a616b438f4e57c3ea9deb822a238f3 Mon Sep 17 00:00:00 2001 From: Tung Le Date: Wed, 24 Dec 2025 10:30:49 +0700 Subject: [PATCH 6/6] Provent delete root user --- app/helpers/kaui/admin_allowed_users_helper.rb | 14 ++++++++++++++ app/views/kaui/admin_allowed_users/index.html.erb | 5 +---- app/views/kaui/admin_allowed_users/show.html.erb | 5 +---- 3 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 app/helpers/kaui/admin_allowed_users_helper.rb 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 d9c53ec0..2df058fd 100644 --- a/app/views/kaui/admin_allowed_users/index.html.erb +++ b/app/views/kaui/admin_allowed_users/index.html.erb @@ -61,10 +61,7 @@ <%= u.description %> <% user_roles = @roles_by_user[u.kb_username] || [] %> - <% is_admin = user_roles.include?('admin') %> - <% is_current_user = u.kb_username == current_user.kb_username %> - - <% unless is_admin || is_current_user %> + <% 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 diff --git a/app/views/kaui/admin_allowed_users/show.html.erb b/app/views/kaui/admin_allowed_users/show.html.erb index 46f1118a..f7795dd7 100644 --- a/app/views/kaui/admin_allowed_users/show.html.erb +++ b/app/views/kaui/admin_allowed_users/show.html.erb @@ -6,10 +6,7 @@

User details

- <% is_admin = @roles.include?('admin') %> - <% is_current_user = @allowed_user.kb_username == current_user.kb_username %> - - <% unless is_admin || is_current_user %> + <% 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",