button toggle for ip address role active

This commit is contained in:
Dylan Knutson
2025-03-03 17:03:16 +00:00
parent 4ec27ab968
commit 994e60789d
7 changed files with 83 additions and 27 deletions

View File

@@ -1,6 +1,6 @@
# typed: true
class State::IpAddressRolesController < ApplicationController
before_action :set_ip_address_role, only: %i[edit update destroy]
before_action :set_ip_address_role, only: %i[edit update destroy toggle]
before_action :authorize_ip_address_roles
# GET /state/ip_address_roles
@@ -46,6 +46,14 @@ class State::IpAddressRolesController < ApplicationController
notice: "IP address role was successfully deleted."
end
def toggle
@ip_address_role.update!(active: !@ip_address_role.active)
redirect_to state_ip_address_roles_path
rescue ActiveRecord::RecordInvalid => e
redirect_to state_ip_address_roles_path,
alert: "Failed to update status: #{e.message}"
end
private
# Use callbacks to share common setup or constraints between actions
@@ -70,7 +78,7 @@ class State::IpAddressRolesController < ApplicationController
authorize IpAddressRole, policy_class: State::IpAddressRolePolicy
when :create
authorize IpAddressRole, policy_class: State::IpAddressRolePolicy
when :update, :destroy
when :update, :destroy, :toggle
authorize @ip_address_role, policy_class: State::IpAddressRolePolicy
end
end

View File

@@ -2,8 +2,9 @@
class IpAddressRole < ReduxApplicationRecord
extend T::Sig
# Use the same role enum as User for consistency
enum :role, User.roles, default: :user
# Only roles that exist in the User model are allowed
# Only allow 'user' role for now, as it is the least privileged
enum :role, { user: "user" }, default: :user
validates :ip_address, presence: true, uniqueness: true
validates :role, presence: true

View File

@@ -1,4 +1,4 @@
# typed: true
# typed: strict
class State::IpAddressRolePolicy < ApplicationPolicy
extend T::Sig
@@ -32,6 +32,11 @@ class State::IpAddressRolePolicy < ApplicationPolicy
is_role_admin?
end
sig { returns(T::Boolean) }
def toggle?
update?
end
private
class Scope

View File

@@ -51,23 +51,24 @@
<%= ip_role.description %>
</td>
<td class="py-2 pr-4 text-sm">
<% if ip_role.active? %>
<span class="text-emerald-600 font-medium">Active</span>
<% else %>
<span class="text-slate-500 font-medium">Inactive</span>
<%= button_to toggle_state_ip_address_role_path(ip_role),
method: :patch,
class: "#{ip_role.active? ? 'bg-emerald-100 hover:bg-emerald-200 text-emerald-700' : 'bg-slate-100 hover:bg-slate-200 text-slate-700'} font-medium py-1 px-3 rounded inline-flex items-center w-24 justify-center" do %>
<i class="fa-solid <%= ip_role.active? ? 'fa-check-circle' : 'fa-circle-xmark' %> mr-1.5"></i>
<%= ip_role.active? ? 'Active' : 'Inactive' %>
<% end %>
</td>
<td class="py-2 pr-4 text-sm text-slate-500">
<%= ip_role.created_at.strftime('%Y-%m-%d %H:%M') %>
</td>
<td class="py-2 text-right text-sm font-medium">
<td class="py-2 text-right text-sm font-medium space-x-2">
<%= link_to 'Edit',
edit_state_ip_address_role_path(ip_role),
class: "text-blue-600 hover:text-blue-900 mr-3" %>
class: "bg-blue-100 hover:bg-blue-200 text-blue-700 font-medium py-1 px-3 rounded inline-block" %>
<%= button_to 'Delete',
state_ip_address_role_path(ip_role),
method: :delete,
class: "text-red-600 hover:text-red-900 inline",
class: "bg-rose-100 hover:bg-rose-200 text-rose-700 font-medium py-1 px-3 rounded inline-block",
form: {
class: "inline",
data: {

View File

@@ -60,7 +60,9 @@ Rails.application.routes.draw do
authenticate :user, ->(user) { user.admin? } do
# IP address roles management
namespace :state do
resources :ip_address_roles, except: [:show]
resources :ip_address_roles, except: [:show] do
patch :toggle, on: :member
end
end
resources :global_states, path: "state" do

View File

@@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe State::IpAddressRolesController, type: :controller do
let(:user) { create(:user, :admin) }
let(:ip_address_role) { create(:ip_address_role) }
let(:ip_address_role) { create(:ip_address_role, active: true) }
before { sign_in user }
@@ -41,7 +41,7 @@ RSpec.describe State::IpAddressRolesController, type: :controller do
let(:valid_attributes) do
{
ip_address: "192.168.1.0/24",
role: "admin",
role: "user",
description: "Test role",
active: true,
}
@@ -60,7 +60,7 @@ RSpec.describe State::IpAddressRolesController, type: :controller do
end
context "with invalid params" do
let(:invalid_attributes) { { ip_address: "", role: "admin" } }
let(:invalid_attributes) { { ip_address: "", role: "user" } }
it "returns a success response (i.e. to display the 'new' template)" do
post :create, params: { ip_address_role: invalid_attributes }
@@ -245,4 +245,47 @@ RSpec.describe State::IpAddressRolesController, type: :controller do
end
end
end
describe "PATCH #toggle" do
context "when user is authorized" do
before { sign_in user }
it "toggles the active state" do
expect { patch :toggle, params: { id: ip_address_role.id } }.to change {
ip_address_role.reload.active
}.from(true).to(false)
expect(response).to redirect_to(state_ip_address_roles_path)
expect(flash[:notice]).to be_nil
end
end
context "when user is not authorized" do
let(:regular_user) { create(:user, :user) }
before { sign_in regular_user }
it "prevents toggling the active state" do
patch :toggle, params: { id: ip_address_role.id }
expect(response).to redirect_to(root_path)
expect(flash[:alert]).to eq(
"You are not authorized to perform this action.",
)
end
it "does not update the active state" do
expect {
patch :toggle, params: { id: ip_address_role.id }
}.not_to change { ip_address_role.reload.active }
end
end
context "when user is not authenticated" do
before { sign_out :user }
it "redirects to login" do
patch :toggle, params: { id: ip_address_role.id }
expect(response).to redirect_to(new_user_session_path)
end
end
end
end

View File

@@ -14,7 +14,7 @@ RSpec.describe IpAddressRole, type: :model do
)
# Now test if creating another with the same IP fails
new_role = IpAddressRole.new(ip_address: "192.168.1.1", role: "admin")
new_role = IpAddressRole.new(ip_address: "192.168.1.1", role: "user")
expect(new_role).not_to be_valid
expect(new_role.errors[:ip_address]).to include("has already been taken")
end
@@ -25,7 +25,7 @@ RSpec.describe IpAddressRole, type: :model do
@existing_role =
IpAddressRole.create!(
ip_address: "192.168.0.0/16", # This covers 192.168.0.0 to 192.168.255.255
role: "moderator",
role: "user",
active: true,
)
end
@@ -35,12 +35,10 @@ RSpec.describe IpAddressRole, type: :model do
new_role =
IpAddressRole.new(
ip_address: "192.168.1.0/24",
role: "admin",
role: "user",
active: true,
)
# If we have a validation for overlapping ranges, this should fail
# Note: This test might need to be updated if this validation doesn't exist yet
expect(new_role).not_to be_valid
expect(new_role.errors[:ip_address]).to include(
"overlaps with an existing IP range",
@@ -56,8 +54,6 @@ RSpec.describe IpAddressRole, type: :model do
active: true,
)
# If we have a validation for overlapping IPs, this should fail
# Note: This test might need to be updated if this validation doesn't exist yet
expect(new_role).not_to be_valid
expect(new_role.errors[:ip_address]).to include(
"overlaps with an existing IP range",
@@ -69,7 +65,7 @@ RSpec.describe IpAddressRole, type: :model do
new_role =
IpAddressRole.new(
ip_address: "10.0.0.0/8",
role: "admin",
role: "user",
active: true,
)
@@ -81,9 +77,9 @@ RSpec.describe IpAddressRole, type: :model do
it { is_expected.to validate_presence_of(:role) }
end
describe "role enum" do
it "defines expected roles" do
expect(IpAddressRole.roles.keys).to match_array(%w[user admin moderator])
describe "role" do
it "only allows user role" do
expect(IpAddressRole.roles.keys).to match_array(%w[user])
end
it "defaults to user role" do