From 6502e09adf8a35db24254c5ca4b9cf314808694e Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 22 May 2023 14:04:17 +0400 Subject: [PATCH 001/132] Updated .gitignore file --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fa4b92d..d0e07dd 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,4 @@ /node_modules /.yarn/cache/* -/.yarn/install-state.gz \ No newline at end of file +.yarn/install-state.gz From 9993aa148f56ed6d76ac2c18a42e1801dcc012d4 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 22 May 2023 14:09:37 +0400 Subject: [PATCH 002/132] Untrecked .yarn/install-state.gs --- .yarn/install-state.gz | Bin 120097 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .yarn/install-state.gz diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz deleted file mode 100644 index 4d5da3b7fac6ff57b529b3603b0d01dd7bd14533..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120097 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 l5(n~66^%|A!%;2MbP$cg9aG}{ Date: Tue, 30 May 2023 18:00:24 +0400 Subject: [PATCH 003/132] added service to user and device model, added features with service. Now user (except admin) can manage devices only under his service --- app/controllers/admin/device_controller.rb | 15 +- app/controllers/admin/users_controller.rb | 7 +- app/controllers/application_controller.rb | 4 +- app/controllers/concerns/device_concern.rb | 21 +- app/controllers/device_controller.rb | 22 +- app/models/ability.rb | 3 +- app/models/device.rb | 3 +- app/models/service.rb | 1 + app/models/user.rb | 5 +- app/views/admin/users/_form.html.erb | 3 + app/views/admin/users/index.html.erb | 15 +- app/views/devise/registrations/edit.html.erb | 2 +- app/views/devise/registrations/new.html.erb | 2 +- app/views/layouts/application.html.erb | 2 +- app/views/shared/form/_device.html.erb | 13 +- app/views/shared/index/_device.html.erb | 13 +- config/locales/ru.yml | 4 + db/migrate/20230222071637_create_devices.rb | 1 + .../20230222071659_devise_create_users.rb | 1 + db/schema.rb | 6 + db/seeds.rb | 207 ++++++++++-------- 21 files changed, 210 insertions(+), 140 deletions(-) diff --git a/app/controllers/admin/device_controller.rb b/app/controllers/admin/device_controller.rb index 9c3caab..b009460 100644 --- a/app/controllers/admin/device_controller.rb +++ b/app/controllers/admin/device_controller.rb @@ -16,7 +16,7 @@ def new def create authorize!(:create, :device_admin) - device_create(device_params) + device_create end def show @@ -32,7 +32,7 @@ def edit def update authorize!(:update, :device_admin) - device_update(@device, device_params) + device_update(@device) end def destroy @@ -50,15 +50,4 @@ def create_inspection def set_device @device = Device.find(params[:id]) end - - def device_params - params.require(:device).permit(:inventory_id, - :tabel_id, - :serial_id, - :device_model_id, - :device_reg_group_id, - :year_of_production, - :year_of_commissioning, - :supplementary_kit_id) - end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 90c3fc2..bb60830 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -59,9 +59,9 @@ def destroy if assigned_inspections_count.zero? @user.destroy - flash[:success] = t("message.user.delete.success") + flash[:success] = t('message.user.delete.success') else - flash[:error] = t("message.user.delete.error") + flash[:error] = t('message.user.delete.error') end redirect_to(admin_users_path) end @@ -83,6 +83,7 @@ def user_params :password, :password_confirmation, :role, - :timezone) + :timezone, + :service_id) end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c4170c1..8a4e217 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -29,8 +29,8 @@ def set_time_zone(&block) protected def configure_permitted_parameters - attributes_sign_up = [:first_name, :last_name, :tabel_id, :email, :timezone] - attributes_update = [*attributes_sign_up, :second_name, :phone] + attributes_sign_up = [:first_name, :last_name, :tabel_id, :service_id, :timezone] + attributes_update = [*attributes_sign_up.excluding(:service_id), :second_name, :email, :phone] devise_parameter_sanitizer.permit(:sign_up, keys: attributes_sign_up) devise_parameter_sanitizer.permit(:account_update, keys: attributes_update) end diff --git a/app/controllers/concerns/device_concern.rb b/app/controllers/concerns/device_concern.rb index c702e53..303d7a4 100644 --- a/app/controllers/concerns/device_concern.rb +++ b/app/controllers/concerns/device_concern.rb @@ -8,8 +8,8 @@ def device_show(device) end end - def device_create(params) - @device = Device.new(params) + def device_create + @device = Device.new(device_params) if @device.save redirect_back(fallback_location: root_path) else @@ -17,8 +17,8 @@ def device_create(params) end end - def device_update(device, params) - if device.update(params) + def device_update(device) + if device.update(device_params) redirect_back(fallback_location: root_path) else render(:edit, status: :unprocessable_entity) @@ -39,5 +39,18 @@ def create_inspection_for_device(device) def inspection_params params.require(:inspection).permit(:type_target) end + + def device_params + params.require(:device).permit(:inventory_id, + :tabel_id, + :serial_id, + :device_model_id, + :device_reg_group_id, + :year_of_production, + :year_of_commissioning, + :supplementary_kit_id, + :room_id, + :service_id) + end end end diff --git a/app/controllers/device_controller.rb b/app/controllers/device_controller.rb index 768fb2c..bcf038a 100644 --- a/app/controllers/device_controller.rb +++ b/app/controllers/device_controller.rb @@ -6,9 +6,13 @@ class DeviceController < ApplicationController def index @query = Device.ransack(params[:q]) + condition = unless current_user.admin? + { service_id: current_user.service_id } + end @pagy, @devices = pagy(@query.result. includes(:device_model, :supplementary_kit). - order(:tabel_id)) + order(:tabel_id). + where(condition)) end def new @@ -16,7 +20,7 @@ def new end def create - device_create(device_params) + device_create end def show @@ -25,7 +29,7 @@ def show end def update - device_update(@device, device_params) + device_update(@device) end def destroy @@ -52,16 +56,4 @@ def download def set_device @device = Device.find(params[:id]) end - - def device_params - params.require(:device).permit(:inventory_id, - :tabel_id, - :serial_id, - :device_model_id, - :device_reg_group_id, - :year_of_production, - :year_of_commissioning, - :supplementary_kit_id, - :room_id) - end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 6141d27..b83ddee 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -25,7 +25,8 @@ def initialize(user) end if user.engineer? - can([:manage, :create_inspection], Device) + can([:manage, :create_inspection], Device, service_id: user.service_id) + cannot(:destroy, Device) can(:create, [DeviceModel, SupplementaryKit, DeviceRegGroup, MeasurementClass, MeasurementGroup, Manufacturer, DeviceComponent]) inspector(user) diff --git a/app/models/device.rb b/app/models/device.rb index b7f770f..6c42d36 100644 --- a/app/models/device.rb +++ b/app/models/device.rb @@ -10,6 +10,7 @@ class Device < ApplicationRecord belongs_to :device_reg_group belongs_to :supplementary_kit, optional: true belongs_to :room, optional: true + belongs_to :service has_many :inspections has_one :channel @@ -27,7 +28,7 @@ def last_successful_inspection def self.ransackable_attributes(_auth_object = nil) ['created_at', 'device_model_id', 'device_reg_group_id', 'id', 'inventory_id', 'serial_id', 'tabel_id', 'updated_at', - 'year_of_commissioning', 'year_of_production', 'supplementary_kit_id'] + 'year_of_commissioning', 'year_of_production', 'supplementary_kit_id', 'service_id'] end def self.ransackable_associations(_auth_object = nil) diff --git a/app/models/service.rb b/app/models/service.rb index ac75a28..6a3daf8 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -4,5 +4,6 @@ class Service < ApplicationRecord belongs_to :building has_many :devices + has_many :users has_many :servers end diff --git a/app/models/user.rb b/app/models/user.rb index 1a742b1..b46b482 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,6 +2,9 @@ class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable + + belongs_to :service + has_many :inspections has_many :posts @@ -25,7 +28,7 @@ class User < ApplicationRecord def self.ransackable_attributes(_auth_object = nil) ['avatar_url', 'created_at', 'email', 'encrypted_password', 'first_name', 'id', 'last_name', 'phone', 'remember_created_at', - 'reset_password_sent_at', 'reset_password_token', 'role', 'second_name', 'tabel_id', 'updated_at'] + 'reset_password_sent_at', 'reset_password_token', 'role', 'second_name', 'tabel_id', 'updated_at', 'service_id'] end def self.ransackable_associations(_auth_object = nil) diff --git a/app/views/admin/users/_form.html.erb b/app/views/admin/users/_form.html.erb index f54221b..9753609 100644 --- a/app/views/admin/users/_form.html.erb +++ b/app/views/admin/users/_form.html.erb @@ -24,6 +24,9 @@
<%= f.input :phone %>
+
+ <%= f.association :service, include_blank: false %> +
diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb index c3e6d04..b28d392 100644 --- a/app/views/admin/users/index.html.erb +++ b/app/views/admin/users/index.html.erb @@ -25,12 +25,12 @@ <%= f.label :last_name, class:"mb-1" %> <%= f.search_field :last_name_cont, class: 'form-control', placeholder: 'Иванов' %>
-
-
<%= f.label :second_name, class:"mb-1" %> <%= f.search_field :second_name_cont, class: 'form-control', placeholder: 'Иванович' %>
+
+
<%= f.label :role, class:"mb-1" %> hidden="hidden">
@@ -32,7 +33,7 @@

<% end %> - <% if @inspection.state == Inspection::STATES[:verification_successful] || @inspection.state == Inspection::STATES[:close] %> + <% if @inspection.state == Inspection::STATES[:verification_successful] || @inspection.state == Inspection::STATES[:closed] %>

<%= t('activerecord.attributes.inspection.conclusion')%>: @@ -53,7 +54,7 @@

<% if can? :destroy, @inspection %>
- <%= button_to t("b_delete"), inspection_path(@inspection), method: :delete, class: "btn btn-danger"%> + <%= button_to t("b_delete"), inspection_path(id: @inspection.id, previous_action: @previous_action), method: :delete, class: "btn btn-danger"%>
<% end %> <% end %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 62692c9..fa9d605 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -28,7 +28,11 @@
  • <% if can? :read, Inspection %> - <%= link_to t(".inspections"), new_tasks_inspection_index_path, class: "nav-link px-2 link-secondary" %> + <% if can? :new_tasks, Inspection %> + <%= link_to t(".inspections"), new_tasks_inspection_index_path, class: "nav-link px-2 link-secondary" %> + <% elsif can? :service_tasks, Inspection %> + <%= link_to t(".inspections"), service_tasks_inspection_index_path, class: "nav-link px-2 link-secondary" %> + <%end%> <%end%>
  • <% end %> diff --git a/app/views/shared/index/_device.html.erb b/app/views/shared/index/_device.html.erb index e7bc9d5..5e5ae10 100644 --- a/app/views/shared/index/_device.html.erb +++ b/app/views/shared/index/_device.html.erb @@ -118,8 +118,12 @@ function formClear(){ let form = document.getElementById('for-clear') let inputs = Array.from(form.getElementsByTagName("input")) + let selects = Array.from(document.getElementById('for-clear').getElementsByTagName("select")) inputs.forEach((input) =>{ input.value = null }) + selects.forEach((select) =>{ + select.value = "" + }) } diff --git a/app/views/shared/index/_inspection.html.erb b/app/views/shared/index/_inspection.html.erb index 51813d1..53843d6 100644 --- a/app/views/shared/index/_inspection.html.erb +++ b/app/views/shared/index/_inspection.html.erb @@ -11,42 +11,48 @@
    <%= search_form_for(@query, url: path, merhod: :get, class: "rounded accordion-body") do |f| %> - <%= f.label t('.device_serial_id'), class: "mb-2" %> - <%= f.search_field :device_serial_id_cont, class: 'form-control', placeholder: '123-123-N' %> - <%= f.label t('.device_tabel_id'), class: "my-2" %> - <%= f.search_field :device_tabel_id_eq, class: 'form-control', placeholder: '1' %> - <% unless is_new_tasks %> - <%= f.label t('.assigned_user'), class: "my-2" %> - <%= f.search_field :performer_last_name_cont, class: 'form-control', placeholder: t('.performer_last_name') %> - <%= f.search_field :performer_first_name_cont, class: 'form-control mt-1', placeholder: t('.performer_first_name') %> - <%= f.search_field :performer_second_name_cont, class: 'form-control mt-1', placeholder: t('.performer_second_name') %> - <% end %> - <% if @states_to_show.count > 1 %> - <%= f.label :state, class: "my-2" %> - + + <% @states_to_show.each do |key,value| %> + <% if key == @selected_state %> + <% end %> - <%= t("activerecord.attributes.inspection.#{value}") %> - <% end %> - - <% end %> - <% if is_completed_tasks %> - <%= f.label :conclusion, class: "my-2" %> - <%= f.search_field :conclusion_cont, class: 'form-control', placeholder: t('.inspection_conclusion') %> - <% end %> - <%= f.label t('.creator'), class: "my-2" %> - <%= f.search_field :creator_last_name_cont, class: 'form-control', placeholder: t('.creator_last_name') %> - <%= f.search_field :creator_first_name_cont, class: 'form-control mt-1', placeholder: t('.creator_first_name') %> - <%= f.search_field :creator_second_name_cont, class: 'form-control mt-1', placeholder: t('.creator_second_name') %> + + <% end %> + <% if is_completed_tasks %> + <%= f.label t('.conclusion_date_from'), class:"my-2" %> + <%= f.date_field :conclusion_date_gteq, class: 'form-control ' %> + <%= f.label t('.conclusion_date_to'), class:"my-2" %> + <%= f.date_field :conclusion_date_lteq, class: 'form-control' %> + <% end %> + <% unless current_user.role == User::ROLES[:engineer]%> + <%= f.label t('.creator'), class: "my-2" %> + <%= f.search_field :creator_last_name_cont, class: 'form-control', placeholder: t('.creator_last_name') %> + <%= f.search_field :creator_first_name_cont, class: 'form-control mt-1', placeholder: t('.creator_first_name') %> + <%= f.search_field :creator_second_name_cont, class: 'form-control mt-1', placeholder: t('.creator_second_name') %> + <% end %> +
    <%= f.submit t("b_accept"), class: 'btn my-2 w-100 btn-primary'%> - +
    <% end %>
    @@ -79,7 +85,11 @@ <%= inspection.device.serial_id %> <%= inspection.device.tabel_id %> <% unless is_new_tasks %> - <%= inspection.performer.last_name + " " + inspection.performer.first_name %> + <% if inspection.performer.present? %> + <%= inspection.performer.last_name + " " + inspection.performer.first_name %> + <% else %> + + <% end %> <% end %> <%= t("activerecord.attributes.inspection.#{inspection.type_target}") %> <%= t("activerecord.attributes.inspection.#{inspection.state}") %> @@ -95,65 +105,67 @@
    - <%= link_to inspection_path(inspection), class: 'btn p-0' do %> + <%= link_to inspection_path(id: inspection.id, previous_action: @previous_action), class: 'btn p-0' do %> <% end %>
    - <% unless path == all_tasks_inspection_index_path %> - <% case inspection.state %> - <% when "task_created"%> -
    - <%= button_to accept_task_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> - - <% end%> -
    - <% when "task_accepted"%> -
    - <%= button_to complete_verification_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> - - <% end %> -
    -
    - <%= button_to fail_verification_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> - - <% end %> -
    - <% when "verification_failed"%> -
    - <%= button_to send_to_repair_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> - - <% end %> -
    -
    - <%= button_to close_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> - - <% end %> -
    - <% when "sent_to_repair"%> -
    - <%= button_to return_from_repair_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> - - <% end %> -
    - <% when "returned_from_repair"%> -
    - <%= button_to send_from_repair_to_verification_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> - - <% end %> -
    -
    - <%= button_to send_from_repair_to_close_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> - - <% end %> -
    + <% if can? :accept_task, Inspection %> + <% unless path == all_tasks_inspection_index_path %> + <% case inspection.state %> + <% when "task_created"%> +
    + <%= button_to accept_task_inspection_path(inspection), + method: :post, class: 'btn p-0' do %> + + <% end%> +
    + <% when "task_accepted"%> +
    + <%= button_to complete_verification_inspection_path(inspection), + method: :post, class: 'btn p-0' do %> + + <% end %> +
    +
    + <%= button_to fail_verification_inspection_path(inspection), + method: :post, class: 'btn p-0' do %> + + <% end %> +
    + <% when "verification_failed"%> +
    + <%= button_to send_to_repair_inspection_path(inspection), + method: :post, class: 'btn p-0' do %> + + <% end %> +
    +
    + <%= button_to close_inspection_path(inspection), + method: :post, class: 'btn p-0' do %> + + <% end %> +
    + <% when "sent_to_repair"%> +
    + <%= button_to return_from_repair_inspection_path(inspection), + method: :post, class: 'btn p-0' do %> + + <% end %> +
    + <% when "returned_from_repair"%> +
    + <%= button_to send_from_repair_to_verification_inspection_path(inspection), + method: :post, class: 'btn p-0' do %> + + <% end %> +
    +
    + <%= button_to send_from_repair_to_close_inspection_path(inspection), + method: :post, class: 'btn p-0' do %> + + <% end %> +
    + <% end %> <% end %> <% end %>
    @@ -183,4 +195,17 @@

    <%= t('.text-no-new-task')%>

    <% end%> - \ No newline at end of file + + \ No newline at end of file diff --git a/app/views/shared/navbar/_inspection.html.erb b/app/views/shared/navbar/_inspection.html.erb index d9704d2..dd67be2 100644 --- a/app/views/shared/navbar/_inspection.html.erb +++ b/app/views/shared/navbar/_inspection.html.erb @@ -1,17 +1,25 @@ diff --git a/config/locales/en.yml b/config/locales/en.yml index 9def760..794fdac 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -15,6 +15,10 @@ en: delete: success: User was deleted. error: User wasn't deleted! The user has inspections. + inspection: + create_from_device: + success: Inspection was created + error: Error! Inspection wasn't created layouts: application: home: Home @@ -78,6 +82,8 @@ en: creator_last_name: Petrov creator_first_name: Petr creator_second_name: Petrovich + conclusion_date_from: Сonclusion date from + conclusion_date_to: Сonclusion date to device: b_add_device: Add device b_add_device_component: Add device diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 766e3e0..b8cad8f 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -147,6 +147,10 @@ ru: delete: success: Пользователь успешно удален. error: Ошибка! Пользователь не удален! На пользователя ссылаются инспекции. + inspection: + create_from_device: + success: Инспекция создана + error: Ошибка! Инспекция не создана activerecord: models: device_model: Модель @@ -311,6 +315,8 @@ ru: creator_last_name: Петров creator_first_name: Петр creator_second_name: Петрович + conclusion_date_from: Дата заключения от + conclusion_date_to: Дата заключения до device: b_add_device: Прибор b_add_device_component: Компонент diff --git a/config/routes.rb b/config/routes.rb index 914a32b..4c8ebec 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -36,6 +36,7 @@ get :my_tasks, :to => 'inspection#my_tasks', :on => :collection get :completed_tasks, :to => 'inspection#completed_tasks', :on => :collection get :all_tasks, :to => 'inspection#all_tasks', :on => :collection + get :service_tasks, :to => 'inspection#service_tasks', :on => :collection post :accept_task, :to => 'inspection#accept_task', :on => :member post :complete_verification, :to => 'inspection#complete_verification', :on => :member post :fail_verification, :to => 'inspection#fail_verification', :on => :member diff --git a/db/seeds.rb b/db/seeds.rb index 2454085..96a468b 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -220,6 +220,17 @@ ) end +# seed Service + +10.times do |i| + Service.create( + name: "Service-#{i}", + division: Division.find_by(id: rand(1..10)), + organization: Organization.first, + building: Building.find_by(id: rand(1..10)) + ) +end + # seed Server 10.times do |i| @@ -232,17 +243,6 @@ ) end -# seed Service - -10.times do |i| - Service.create( - name: "Service-#{i}", - division: Division.find_by(id: rand(1..10)), - organization: Organization.first, - building: Building.find_by(id: rand(1..10)) - ) -end - # seed User User.create( From bd0f0ef8a92c780f05ba9591b0240b0512b44754 Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 1 Jun 2023 00:18:24 +0400 Subject: [PATCH 007/132] fixed routes for modals (all device's modals routes now do not go through the admin). Add uniq to device_model name attr --- .../admin/device_component_controller.rb | 12 +++---- .../admin/device_model_controller.rb | 12 +++---- .../admin/device_reg_group_controller.rb | 12 +++---- .../admin/manufacturer_controller.rb | 12 +++---- .../admin/measurement_class_controller.rb | 12 +++---- .../admin/measurement_group_controller.rb | 12 +++---- .../admin/supplementary_kit_controller.rb | 12 +++---- .../concerns/device_component_concern.rb | 26 ++++++++++++++ .../concerns/device_model_concern.rb | 34 +++++++++++++++++++ .../concerns/device_reg_group_concern.rb | 20 +++++++++++ .../concerns/manufacturer_concern.rb | 24 +++++++++++++ .../concerns/measurement_class_concern.rb | 22 ++++++++++++ .../concerns/measurement_group_concern.rb | 20 +++++++++++ .../concerns/supplementary_kit_concern.rb | 22 ++++++++++++ .../device_component_controller.rb | 12 +++++++ app/controllers/device_model_controller.rb | 12 +++++++ .../device_reg_group_controller.rb | 12 +++++++ app/controllers/manufacturer_controller.rb | 12 +++++++ .../measurement_class_controller.rb | 12 +++++++ .../measurement_group_controller.rb | 13 +++++++ .../supplementary_kit_controller.rb | 12 +++++++ app/models/ability.rb | 7 ++++ app/models/device_model.rb | 2 +- .../device_component/create.turbo_stream.erb | 1 + .../device_component/new.turbo_stream.erb | 3 ++ .../device_model/create.turbo_stream.erb | 1 + app/views/device_model/new.turbo_stream.erb | 4 +++ .../device_reg_group/create.turbo_stream.erb | 1 + .../device_reg_group/new.turbo_stream.erb | 3 ++ .../manufacturer/create.turbo_stream.erb | 1 + app/views/manufacturer/new.turbo_stream.erb | 3 ++ .../measurement_class/create.turbo_stream.erb | 1 + .../measurement_class/new.turbo_stream.erb | 3 ++ .../measurement_group/create.turbo_stream.erb | 1 + .../measurement_group/new.turbo_stream.erb | 3 ++ .../shared/admin/form/_device_model.html.erb | 6 ++-- app/views/shared/form/_device.html.erb | 6 ++-- app/views/shared/index/_device.html.erb | 2 +- .../supplementary_kit/create.turbo_stream.erb | 1 + .../supplementary_kit/new.turbo_stream.erb | 3 ++ config/routes.rb | 2 ++ .../device_model_controller_test.rb | 18 ++++++++-- 42 files changed, 349 insertions(+), 60 deletions(-) create mode 100644 app/controllers/concerns/device_component_concern.rb create mode 100644 app/controllers/concerns/device_model_concern.rb create mode 100644 app/controllers/concerns/device_reg_group_concern.rb create mode 100644 app/controllers/concerns/manufacturer_concern.rb create mode 100644 app/controllers/concerns/measurement_class_concern.rb create mode 100644 app/controllers/concerns/measurement_group_concern.rb create mode 100644 app/controllers/concerns/supplementary_kit_concern.rb create mode 100644 app/controllers/device_component_controller.rb create mode 100644 app/controllers/device_model_controller.rb create mode 100644 app/controllers/device_reg_group_controller.rb create mode 100644 app/controllers/manufacturer_controller.rb create mode 100644 app/controllers/measurement_class_controller.rb create mode 100644 app/controllers/measurement_group_controller.rb create mode 100644 app/controllers/supplementary_kit_controller.rb create mode 100644 app/views/device_component/create.turbo_stream.erb create mode 100644 app/views/device_component/new.turbo_stream.erb create mode 100644 app/views/device_model/create.turbo_stream.erb create mode 100644 app/views/device_model/new.turbo_stream.erb create mode 100644 app/views/device_reg_group/create.turbo_stream.erb create mode 100644 app/views/device_reg_group/new.turbo_stream.erb create mode 100644 app/views/manufacturer/create.turbo_stream.erb create mode 100644 app/views/manufacturer/new.turbo_stream.erb create mode 100644 app/views/measurement_class/create.turbo_stream.erb create mode 100644 app/views/measurement_class/new.turbo_stream.erb create mode 100644 app/views/measurement_group/create.turbo_stream.erb create mode 100644 app/views/measurement_group/new.turbo_stream.erb create mode 100644 app/views/supplementary_kit/create.turbo_stream.erb create mode 100644 app/views/supplementary_kit/new.turbo_stream.erb diff --git a/app/controllers/admin/device_component_controller.rb b/app/controllers/admin/device_component_controller.rb index 5425387..f76da7d 100644 --- a/app/controllers/admin/device_component_controller.rb +++ b/app/controllers/admin/device_component_controller.rb @@ -1,6 +1,7 @@ class Admin::DeviceComponentController < ApplicationController - before_action :set_device_component, only: [:show, :edit, :update, :destroy] + include DeviceComponentConcern load_and_authorize_resource + before_action :set_device_component, only: [:show, :edit, :update, :destroy] def index @query = DeviceComponent.ransack(params[:q]) @@ -10,16 +11,13 @@ def index end def new + authorize!(:new, :device_component_admin) @device_component = DeviceComponent.new end def create - @device_component = DeviceComponent.new(device_component_params) - if @device_component.save - redirect_back(fallback_location: root_path) - else - render(:new, status: :unprocessable_entity) - end + authorize!(:create, :device_component_admin) + device_component_create end def update diff --git a/app/controllers/admin/device_model_controller.rb b/app/controllers/admin/device_model_controller.rb index f91fc24..47c18ab 100644 --- a/app/controllers/admin/device_model_controller.rb +++ b/app/controllers/admin/device_model_controller.rb @@ -1,6 +1,7 @@ class Admin::DeviceModelController < ApplicationController - before_action :set_device_model, only: [:show, :edit, :update, :destroy] + include DeviceModelConcern load_and_authorize_resource + before_action :set_device_model, only: [:show, :edit, :update, :destroy] def index @query = DeviceModel.ransack(params[:q]) @@ -10,16 +11,13 @@ def index end def new + authorize!(:new, :device_model_admin) @device_model = DeviceModel.new end def create - @device_model = DeviceModel.new(device_model_params) - if @device_model.save - redirect_back(fallback_location: root_path) - else - render(:new, status: :unprocessable_entity) - end + authorize!(:create, :device_model_admin) + device_model_create end def update diff --git a/app/controllers/admin/device_reg_group_controller.rb b/app/controllers/admin/device_reg_group_controller.rb index 2b3daf5..c451642 100644 --- a/app/controllers/admin/device_reg_group_controller.rb +++ b/app/controllers/admin/device_reg_group_controller.rb @@ -1,6 +1,7 @@ class Admin::DeviceRegGroupController < ApplicationController - before_action :set_device_reg_group, only: [:edit, :update, :destroy] + include DeviceRegGroupConcern load_and_authorize_resource + before_action :set_device_reg_group, only: [:edit, :update, :destroy] def index @device_reg_group = DeviceRegGroup.new @@ -10,16 +11,13 @@ def index end def new + authorize!(:new, :device_reg_group_admin) @device_reg_group = DeviceRegGroup.new end def create - @device_reg_group = DeviceRegGroup.new(device_reg_group_params) - if @device_reg_group.save - redirect_back(fallback_location: root_path) - else - render(:new, status: :unprocessable_entity) - end + authorize!(:create, :device_reg_group_admin) + device_reg_group_create end def update diff --git a/app/controllers/admin/manufacturer_controller.rb b/app/controllers/admin/manufacturer_controller.rb index f904b9f..2dcfd9e 100644 --- a/app/controllers/admin/manufacturer_controller.rb +++ b/app/controllers/admin/manufacturer_controller.rb @@ -1,6 +1,7 @@ class Admin::ManufacturerController < ApplicationController - before_action :set_manufacturer, only: [:edit, :update, :destroy] + include ManufacturerConcern load_and_authorize_resource + before_action :set_manufacturer, only: [:edit, :update, :destroy] def index @query = Manufacturer.ransack(params[:q]) @@ -9,16 +10,13 @@ def index end def new + authorize!(:new, :manufacturer_admin) @manufacturer = Manufacturer.new end def create - @manufacturer = Manufacturer.new(manufacturer_params) - if @manufacturer.save - redirect_back(fallback_location: root_path) - else - render(:new, status: :unprocessable_entity) - end + authorize!(:create, :manufacturer_admin) + manufacturer_create end def update diff --git a/app/controllers/admin/measurement_class_controller.rb b/app/controllers/admin/measurement_class_controller.rb index 9ddd389..7b6b0f2 100644 --- a/app/controllers/admin/measurement_class_controller.rb +++ b/app/controllers/admin/measurement_class_controller.rb @@ -1,6 +1,7 @@ class Admin::MeasurementClassController < ApplicationController - before_action :set_measurement_class, only: [:show, :edit, :update, :destroy] + include MeasurementClassConcern load_and_authorize_resource + before_action :set_measurement_class, only: [:show, :edit, :update, :destroy] def index @query = MeasurementClass.ransack(params[:q]) @@ -9,16 +10,13 @@ def index end def new + authorize!(:new, :measurement_class_admin) @measurement_class = MeasurementClass.new end def create - @measurement_class = MeasurementClass.new(measurement_class_params) - if @measurement_class.save - redirect_back(fallback_location: root_path) - else - render(:new, status: :unprocessable_entity) - end + authorize!(:create, :measurement_class_admin) + measurement_class_create end def update diff --git a/app/controllers/admin/measurement_group_controller.rb b/app/controllers/admin/measurement_group_controller.rb index afc4192..b142eb8 100644 --- a/app/controllers/admin/measurement_group_controller.rb +++ b/app/controllers/admin/measurement_group_controller.rb @@ -1,6 +1,7 @@ class Admin::MeasurementGroupController < ApplicationController - before_action :set_measurement_group, only: [:edit, :update, :destroy] + include MeasurementGroupConcern load_and_authorize_resource + before_action :set_measurement_group, only: [:edit, :update, :destroy] def index @query = MeasurementGroup.ransack(params[:q]) @@ -9,16 +10,13 @@ def index end def new + authorize!(:new, :measurement_group_admin) @measurement_group = MeasurementGroup.new end def create - @measurement_group = MeasurementGroup.new(measurement_group_params) - if @measurement_group.save - redirect_back(fallback_location: root_path) - else - render(:new, status: :unprocessable_entity) - end + authorize!(:create, :measurement_group_admin) + measurement_group_create end def update diff --git a/app/controllers/admin/supplementary_kit_controller.rb b/app/controllers/admin/supplementary_kit_controller.rb index fbac024..2483541 100644 --- a/app/controllers/admin/supplementary_kit_controller.rb +++ b/app/controllers/admin/supplementary_kit_controller.rb @@ -1,6 +1,7 @@ class Admin::SupplementaryKitController < ApplicationController - before_action :set_supplementary_kit, only: [:edit, :update, :destroy] + include SupplementaryKitConcern load_and_authorize_resource + before_action :set_supplementary_kit, only: [:edit, :update, :destroy] def index @query = SupplementaryKit.ransack(params[:q]) @@ -10,16 +11,13 @@ def index end def new + authorize!(:new, :supplementary_kit_admin) @supplementary_kit = SupplementaryKit.new end def create - @supplementary_kit = SupplementaryKit.new(supplementary_kit_params) - if @supplementary_kit.save - redirect_back(fallback_location: root_path) - else - render(:new, status: :unprocessable_entity) - end + authorize!(:create, :supplementary_kit_admin) + supplementary_kit_create end def update diff --git a/app/controllers/concerns/device_component_concern.rb b/app/controllers/concerns/device_component_concern.rb new file mode 100644 index 0000000..7bff185 --- /dev/null +++ b/app/controllers/concerns/device_component_concern.rb @@ -0,0 +1,26 @@ +module DeviceComponentConcern + extend ActiveSupport::Concern + + included do + def device_component_create + @device_component = DeviceComponent.new(device_component_params) + if @device_component.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def device_component_params + params.require(:device_component).permit( + :serial_id, + :name, + :supplementary_kit_id, + :measurement_max, + :measurement_min, + :measuring_unit, + :description, + ) + end + end +end diff --git a/app/controllers/concerns/device_model_concern.rb b/app/controllers/concerns/device_model_concern.rb new file mode 100644 index 0000000..09ca0fd --- /dev/null +++ b/app/controllers/concerns/device_model_concern.rb @@ -0,0 +1,34 @@ +module DeviceModelConcern + extend ActiveSupport::Concern + + included do + def device_model_create + @device_model = DeviceModel.new(device_model_params) + if @device_model.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def device_model_params + params.require(:device_model).permit( + :name, + :measurement_group_id, + :measurement_class_id, + :measuring_unit, + :safety_class, + :accuracy_class, + :measurement_sensitivity, + :measurement_min, + :measurement_max, + :manufacturer_id, + :supplementary_kit_id, + :is_complete_device, + :is_tape_rolling_mechanism, + :doc_url, + :image_url, + ) + end + end +end diff --git a/app/controllers/concerns/device_reg_group_concern.rb b/app/controllers/concerns/device_reg_group_concern.rb new file mode 100644 index 0000000..0edddcc --- /dev/null +++ b/app/controllers/concerns/device_reg_group_concern.rb @@ -0,0 +1,20 @@ +module DeviceRegGroupConcern + extend ActiveSupport::Concern + + included do + def device_reg_group_create + @device_reg_group = DeviceRegGroup.new(device_reg_group_params) + if @device_reg_group.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def device_reg_group_params + params.require(:device_reg_group).permit( + :name, + ) + end + end +end diff --git a/app/controllers/concerns/manufacturer_concern.rb b/app/controllers/concerns/manufacturer_concern.rb new file mode 100644 index 0000000..e27bb8f --- /dev/null +++ b/app/controllers/concerns/manufacturer_concern.rb @@ -0,0 +1,24 @@ +module ManufacturerConcern + extend ActiveSupport::Concern + + included do + def manufacturer_create + @manufacturer = Manufacturer.new(manufacturer_params) + if @manufacturer.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def manufacturer_params + params.require(:manufacturer).permit( + :name, + :adress, + :phone, + :email, + :site_url, + ) + end + end +end diff --git a/app/controllers/concerns/measurement_class_concern.rb b/app/controllers/concerns/measurement_class_concern.rb new file mode 100644 index 0000000..9b49417 --- /dev/null +++ b/app/controllers/concerns/measurement_class_concern.rb @@ -0,0 +1,22 @@ +module MeasurementClassConcern + extend ActiveSupport::Concern + + included do + def measurement_class_create + @measurement_class = MeasurementClass.new(measurement_class_params) + if @measurement_class.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def measurement_class_params + params.require(:measurement_class).permit( + :name, + :measurement_group_id, + :arms_device_type, + ) + end + end +end diff --git a/app/controllers/concerns/measurement_group_concern.rb b/app/controllers/concerns/measurement_group_concern.rb new file mode 100644 index 0000000..5ec6daf --- /dev/null +++ b/app/controllers/concerns/measurement_group_concern.rb @@ -0,0 +1,20 @@ +module MeasurementGroupConcern + extend ActiveSupport::Concern + + included do + def measurement_group_create + @measurement_group = MeasurementGroup.new(measurement_group_params) + if @measurement_group.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def measurement_group_params + params.require(:measurement_group).permit( + :name, + ) + end + end +end diff --git a/app/controllers/concerns/supplementary_kit_concern.rb b/app/controllers/concerns/supplementary_kit_concern.rb new file mode 100644 index 0000000..3a05bf5 --- /dev/null +++ b/app/controllers/concerns/supplementary_kit_concern.rb @@ -0,0 +1,22 @@ +module SupplementaryKitConcern + extend ActiveSupport::Concern + + included do + def supplementary_kit_create + @supplementary_kit = SupplementaryKit.new(supplementary_kit_params) + if @supplementary_kit.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def supplementary_kit_params + params.require(:supplementary_kit).permit( + :name, + :serial_id, + :description, + ) + end + end +end diff --git a/app/controllers/device_component_controller.rb b/app/controllers/device_component_controller.rb new file mode 100644 index 0000000..769851a --- /dev/null +++ b/app/controllers/device_component_controller.rb @@ -0,0 +1,12 @@ +class DeviceComponentController < ApplicationController + include DeviceComponentConcern + load_and_authorize_resource + + def new + @device_component = DeviceComponent.new + end + + def create + device_component_create + end +end diff --git a/app/controllers/device_model_controller.rb b/app/controllers/device_model_controller.rb new file mode 100644 index 0000000..4eeb2c0 --- /dev/null +++ b/app/controllers/device_model_controller.rb @@ -0,0 +1,12 @@ +class DeviceModelController < ApplicationController + include DeviceModelConcern + load_and_authorize_resource + + def new + @device_model = DeviceModel.new + end + + def create + device_model_create + end +end diff --git a/app/controllers/device_reg_group_controller.rb b/app/controllers/device_reg_group_controller.rb new file mode 100644 index 0000000..59b04bd --- /dev/null +++ b/app/controllers/device_reg_group_controller.rb @@ -0,0 +1,12 @@ +class DeviceRegGroupController < ApplicationController + include DeviceRegGroupConcern + load_and_authorize_resource + + def new + @device_reg_group = DeviceRegGroup.new + end + + def create + device_reg_group_create + end +end diff --git a/app/controllers/manufacturer_controller.rb b/app/controllers/manufacturer_controller.rb new file mode 100644 index 0000000..9160e20 --- /dev/null +++ b/app/controllers/manufacturer_controller.rb @@ -0,0 +1,12 @@ +class ManufacturerController < ApplicationController + include ManufacturerConcern + load_and_authorize_resource + + def new + @manufacturer = Manufacturer.new + end + + def create + manufacturer_create + end +end diff --git a/app/controllers/measurement_class_controller.rb b/app/controllers/measurement_class_controller.rb new file mode 100644 index 0000000..0bda5a4 --- /dev/null +++ b/app/controllers/measurement_class_controller.rb @@ -0,0 +1,12 @@ +class MeasurementClassController < ApplicationController + include MeasurementClassConcern + load_and_authorize_resource + + def new + @measurement_class = MeasurementClass.new + end + + def create + measurement_class_create + end +end diff --git a/app/controllers/measurement_group_controller.rb b/app/controllers/measurement_group_controller.rb new file mode 100644 index 0000000..aca385b --- /dev/null +++ b/app/controllers/measurement_group_controller.rb @@ -0,0 +1,13 @@ +class MeasurementGroupController < ApplicationController + include MeasurementGroupConcern + before_action :set_measurement_group, only: [:edit, :update, :destroy] + load_and_authorize_resource + + def new + @measurement_group = MeasurementGroup.new + end + + def create + measurement_group_create + end +end diff --git a/app/controllers/supplementary_kit_controller.rb b/app/controllers/supplementary_kit_controller.rb new file mode 100644 index 0000000..13fb6e4 --- /dev/null +++ b/app/controllers/supplementary_kit_controller.rb @@ -0,0 +1,12 @@ +class SupplementaryKitController < ApplicationController + include SupplementaryKitConcern + load_and_authorize_resource + + def new + @supplementary_kit = SupplementaryKit.new + end + + def create + supplementary_kit_create + end +end diff --git a/app/models/ability.rb b/app/models/ability.rb index 55ec879..ec083b6 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -6,6 +6,13 @@ def initialize(user) user ||= User.new cannot(:manage, :device_admin) + cannot(:manage, :device_model_admin) + cannot(:manage, :device_reg_group_admin) + cannot(:manage, :manufacturer_admin) + cannot(:manage, :measurement_class_admin) + cannot(:manage, :device_component_admin) + cannot(:manage, :measurement_group_admin) + cannot(:manage, :supplementary_kit_admin) if user.present? can([:read, :create], Post) diff --git a/app/models/device_model.rb b/app/models/device_model.rb index fc6f355..e00020e 100644 --- a/app/models/device_model.rb +++ b/app/models/device_model.rb @@ -4,7 +4,7 @@ class DeviceModel < ApplicationRecord belongs_to :manufacturer has_many :devices - validates :name, presence: true + validates :name, presence: true, uniqueness: true validates :measurement_min, :measurement_max, :accuracy_class, :measurement_sensitivity, numericality: true, allow_nil: true validate :validate_measurement_group_belongs_to_measurement_class diff --git a/app/views/device_component/create.turbo_stream.erb b/app/views/device_component/create.turbo_stream.erb new file mode 100644 index 0000000..d98c9f8 --- /dev/null +++ b/app/views/device_component/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> \ No newline at end of file diff --git a/app/views/device_component/new.turbo_stream.erb b/app/views/device_component/new.turbo_stream.erb new file mode 100644 index 0000000..fc121a6 --- /dev/null +++ b/app/views/device_component/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.device_component.new.add_device_component') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/device_component", + locals: {path: device_component_index_path, device_component: @device_component } %> \ No newline at end of file diff --git a/app/views/device_model/create.turbo_stream.erb b/app/views/device_model/create.turbo_stream.erb new file mode 100644 index 0000000..d98c9f8 --- /dev/null +++ b/app/views/device_model/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> \ No newline at end of file diff --git a/app/views/device_model/new.turbo_stream.erb b/app/views/device_model/new.turbo_stream.erb new file mode 100644 index 0000000..2d062a8 --- /dev/null +++ b/app/views/device_model/new.turbo_stream.erb @@ -0,0 +1,4 @@ +<%= turbo_stream.update "modal-title", t('admin.device_model.new.add_device_model') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/device_model", + locals: {path: device_model_index_path, device_model: @device_model, + filtrator_class_id: "filtrator_class_modal", to_filter_class_id:dom_id(MeasurementClass.new), prev_mc_val: nil } %> \ No newline at end of file diff --git a/app/views/device_reg_group/create.turbo_stream.erb b/app/views/device_reg_group/create.turbo_stream.erb new file mode 100644 index 0000000..d98c9f8 --- /dev/null +++ b/app/views/device_reg_group/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> \ No newline at end of file diff --git a/app/views/device_reg_group/new.turbo_stream.erb b/app/views/device_reg_group/new.turbo_stream.erb new file mode 100644 index 0000000..65d6a4e --- /dev/null +++ b/app/views/device_reg_group/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.device_reg_group.new.add_device_reg_group') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/device_reg_group", + locals: {path: device_reg_group_index_path, device_reg_group: @device_reg_group } %> diff --git a/app/views/manufacturer/create.turbo_stream.erb b/app/views/manufacturer/create.turbo_stream.erb new file mode 100644 index 0000000..d98c9f8 --- /dev/null +++ b/app/views/manufacturer/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> \ No newline at end of file diff --git a/app/views/manufacturer/new.turbo_stream.erb b/app/views/manufacturer/new.turbo_stream.erb new file mode 100644 index 0000000..9ceb7bc --- /dev/null +++ b/app/views/manufacturer/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.manufacturer.new.add_manufacturer') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/manufacturer", + locals: {path: manufacturer_index_path, manufacturer: @manufacturer } %> diff --git a/app/views/measurement_class/create.turbo_stream.erb b/app/views/measurement_class/create.turbo_stream.erb new file mode 100644 index 0000000..d98c9f8 --- /dev/null +++ b/app/views/measurement_class/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> \ No newline at end of file diff --git a/app/views/measurement_class/new.turbo_stream.erb b/app/views/measurement_class/new.turbo_stream.erb new file mode 100644 index 0000000..dd81bf3 --- /dev/null +++ b/app/views/measurement_class/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.measurement_class.new.add_measurement_class') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/measurement_class", + locals: {path: measurement_class_index_path, measurement_class: @measurement_class } %> \ No newline at end of file diff --git a/app/views/measurement_group/create.turbo_stream.erb b/app/views/measurement_group/create.turbo_stream.erb new file mode 100644 index 0000000..d98c9f8 --- /dev/null +++ b/app/views/measurement_group/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> \ No newline at end of file diff --git a/app/views/measurement_group/new.turbo_stream.erb b/app/views/measurement_group/new.turbo_stream.erb new file mode 100644 index 0000000..912c7ed --- /dev/null +++ b/app/views/measurement_group/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.measurement_group.new.add_measurement_group') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/measurement_group", + locals: {path: measurement_group_index_path, measurement_group: @measurement_group } %> \ No newline at end of file diff --git a/app/views/shared/admin/form/_device_model.html.erb b/app/views/shared/admin/form/_device_model.html.erb index 1b78338..d5f512c 100644 --- a/app/views/shared/admin/form/_device_model.html.erb +++ b/app/views/shared/admin/form/_device_model.html.erb @@ -13,7 +13,7 @@ <%= f.association :manufacturer, label: false, input_html: {:tabindex => 2}, include_blank: false %>
    - <%= render 'shared/modal_button_add', path: new_admin_manufacturer_path, classes: "btn btn-ligth", text: nil %> + <%= render 'shared/modal_button_add', path: new_manufacturer_path, classes: "btn btn-ligth", text: nil %>
    @@ -30,7 +30,7 @@ 'to_filter_class': to_filter_class_id, 'filtrator_class': filtrator_class_id}, id: filtrator_class_id}, include_blank: false %>
    - <%= render 'shared/modal_button_add', path: new_admin_measurement_group_path, classes: "btn btn-ligth", text: nil %> + <%= render 'shared/modal_button_add', path: new_measurement_group_path, classes: "btn btn-ligth", text: nil %>
    @@ -43,7 +43,7 @@ <%= f.association :measurement_class, label:false, input_html: {tabindex: 4, id: to_filter_class_id}, include_blank: false %>
    - <%= render 'shared/modal_button_add', path: new_admin_measurement_class_path, classes: "btn btn-ligth", text: nil %> + <%= render 'shared/modal_button_add', path: new_measurement_class_path, classes: "btn btn-ligth", text: nil %>
    diff --git a/app/views/shared/form/_device.html.erb b/app/views/shared/form/_device.html.erb index 026de12..4950588 100644 --- a/app/views/shared/form/_device.html.erb +++ b/app/views/shared/form/_device.html.erb @@ -13,7 +13,7 @@ <%= f.association :device_model, label:false, input_html: {tabindex: 7}, include_blank: false %>
    - <%= render 'shared/modal_button_add', path: new_admin_device_model_path, classes: "btn btn-ligth", text: nil %> + <%= render 'shared/modal_button_add', path: new_device_model_path, classes: "btn btn-ligth", text: nil %>
    @@ -29,7 +29,7 @@ <%= f.association :device_reg_group, label: false, input_html: {:tabindex => 4}, include_blank: false %>
    - <%= render 'shared/modal_button_add', path: new_admin_device_reg_group_path, classes: "btn btn-ligth", text: nil %> + <%= render 'shared/modal_button_add', path: new_device_reg_group_path, classes: "btn btn-ligth", text: nil %>
    @@ -43,7 +43,7 @@ <%= f.association :supplementary_kit, label:false, :include_blank => t('combobox_blank'), input_html: {:tabindex => 8} %>
    - <%= render 'shared/modal_button_add', path: new_admin_supplementary_kit_path, classes: "btn btn-ligth", text: nil %> + <%= render 'shared/modal_button_add', path: new_supplementary_kit_path, classes: "btn btn-ligth", text: nil %>
    diff --git a/app/views/shared/index/_device.html.erb b/app/views/shared/index/_device.html.erb index 5e5ae10..dab70c3 100644 --- a/app/views/shared/index/_device.html.erb +++ b/app/views/shared/index/_device.html.erb @@ -51,7 +51,7 @@ <%= render 'shared/modal_button_add', path: new_path, classes: "btn btn-primary w-100", text: t(".b_add_device") %> - <%= render 'shared/modal_button_add', path: new_admin_device_component_path, classes: "btn btn-primary mt-2 w-100", text: t(".b_add_device_component") %> + <%= render 'shared/modal_button_add', path: new_device_component_path, classes: "btn btn-primary mt-2 w-100", text: t(".b_add_device_component") %> <%= button_to device_download_path(request.params.merge(format: :pdf)), data: {turbo: :false}, class: "btn btn-primary my-2 w-100 shadow" do %> <%=t('b_download_pdf')%> <% end %> diff --git a/app/views/supplementary_kit/create.turbo_stream.erb b/app/views/supplementary_kit/create.turbo_stream.erb new file mode 100644 index 0000000..d98c9f8 --- /dev/null +++ b/app/views/supplementary_kit/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> \ No newline at end of file diff --git a/app/views/supplementary_kit/new.turbo_stream.erb b/app/views/supplementary_kit/new.turbo_stream.erb new file mode 100644 index 0000000..dae6e6e --- /dev/null +++ b/app/views/supplementary_kit/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.supplementary_kit.new.add_supplementary_kit') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/supplementary_kit", + locals: {path: supplementary_kit_index_path, supplementary_kit: @supplementary_kit } %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 4c8ebec..c66064d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -30,6 +30,8 @@ resources :device do post :create_inspection, :to => 'device#create_inspection' end + resources :device_model, :measurement_class, + :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, only: [:create, :new] resources :inspection do get :new_tasks, :to => 'inspection#new_tasks', :on => :collection diff --git a/test/controllers/device_model_controller_test.rb b/test/controllers/device_model_controller_test.rb index aabfb1e..75132ce 100644 --- a/test/controllers/device_model_controller_test.rb +++ b/test/controllers/device_model_controller_test.rb @@ -5,6 +5,9 @@ class Admin::DeviceModelControllerTest < ActionController::TestCase def setup sign_in(users(:admin)) + @mg = create(:measurement_group) + @mc = create(:measurement_class, measurement_group_id: @mg.id) + @manufacturer = create(:manufacturer) end test 'should get index' do @@ -20,7 +23,12 @@ def setup test 'should patch update' do device_model = create(:device_model) - device_model_attrs = device_model.as_json + device_model_attrs = attributes_for( + :device_model, + measurement_class_id: @mc.id, + measurement_group_id: @mg.id, + manufacturer_id: @manufacturer.id, + ) device_model_attrs.delete('id') patch :update, params: { id: device_model.id, device_model: device_model_attrs } assert_response :redirect @@ -28,8 +36,12 @@ def setup test 'should post create' do device_model = create(:device_model) - device_model_attrs = device_model.as_json - device_model_attrs.delete('id') + device_model_attrs = attributes_for( + :device_model, + measurement_class_id: @mc.id, + measurement_group_id: @mg.id, + manufacturer_id: @manufacturer.id, + ) post :create, params: { id: device_model.id, device_model: device_model_attrs } assert_response :redirect end From 5e39778abc59db46242d36df09cf084501c6199e Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 2 Jun 2023 08:53:53 +0400 Subject: [PATCH 008/132] Fix inf. exec. fetch method --- app/javascript/Components/Root.jsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/javascript/Components/Root.jsx b/app/javascript/Components/Root.jsx index 3686b21..29e63b8 100644 --- a/app/javascript/Components/Root.jsx +++ b/app/javascript/Components/Root.jsx @@ -1,11 +1,13 @@ -import React from "react"; -import Armstrong from "./Armstrong/Armstrong"; +import React, { useEffect } from 'react'; +import Armstrong from './Armstrong/Armstrong'; export default function Root() { + useEffect(() => { + document.body.setAttribute('data-turbo', 'false'); + return () => { + document.body.removeAttribute('data-turbo'); + }; + }, []); - return ( - <> - - - ); + return ; } From 95cad51cacdb821408cc809d098c9f66162fa9b0 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 2 Jun 2023 09:09:18 +0400 Subject: [PATCH 009/132] Apple refactoring --- .../Components/Armstrong/Armstrong.jsx | 88 +++++++++---------- .../Components/Armstrong/Filter/Filter.jsx | 16 ++-- .../Components/Armstrong/Table/Table.jsx | 70 +++++++++------ .../Components/Armstrong/Table/TableBody.jsx | 46 +++++++--- .../Components/Armstrong/Table/TableHead.jsx | 18 +++- 5 files changed, 141 insertions(+), 97 deletions(-) diff --git a/app/javascript/Components/Armstrong/Armstrong.jsx b/app/javascript/Components/Armstrong/Armstrong.jsx index 9811351..59b59b0 100644 --- a/app/javascript/Components/Armstrong/Armstrong.jsx +++ b/app/javascript/Components/Armstrong/Armstrong.jsx @@ -1,50 +1,28 @@ -import React, { useEffect, useState } from "react"; -import ky from "ky"; -import moment from "moment-timezone"; -import "moment-timezone"; -import { sortBy } from "lodash"; -import Table from "./Table/Table"; -import Filter from "./Filter/Filter"; +import React, { useEffect, useState } from 'react'; +import ky from 'ky'; +import moment from 'moment-timezone'; +import 'moment-timezone'; +import { sortBy } from 'lodash'; +import Table from './Table/Table'; +import Filter from './Filter/Filter'; export default function Armstrong() { const [data, setData] = useState([]); - const [filter, setFilter] = useState(""); - const [timeZone, setTimeZone] = useState(""); + const [filter, setFilter] = useState(''); + const [timeZone, setTimeZone] = useState(''); - useEffect(() => { - setTimeZone(Intl.DateTimeFormat().resolvedOptions().timeZone); - getData(); - setLoop(); - - return () => { - console.log("call returned"); - clearInterval(interval); - }; - }, []); + const normalizeData = (rawData) => { + const normAndSortedDate = []; - async function getData() { - let result = await ky.get("/api/v1/armstrong").json(); - setData(normalizeData(sortBy(result, ["server_id", "channel_id"]))); - console.log(result); - } - - const normalizeData = (data) => { - let normAndSortedDate = []; - - data.map((channel) => { + rawData.forEach((channel) => { normAndSortedDate.push({ state: - channel.state === "normal" ? ( + channel.state === 'normal' ? ( ) : ( ), - specialControl: ( - - ), + specialControl: , id: channel.id, serverId: channel.server_id, channelId: channel.channel_id, @@ -54,7 +32,7 @@ export default function Armstrong() { locationDescription: channel.location_description, eventSystemValue: channel.event_system_value.toExponential(3), eventNotSystemValue: channel.event_not_system_value.toExponential(3), - eventDatetime: moment.tz(channel.event_datetime, timeZone).format("HH:mm:SS"), + eventDatetime: moment.tz(channel.event_datetime, timeZone).format('HH:mm:SS'), eventCount: channel.event_count, eventImpulseValue: channel.event_impulse_value.toExponential(3), }); @@ -63,13 +41,29 @@ export default function Armstrong() { return normAndSortedDate; }; - const setLoop = async function () { + async function getData() { + const result = await ky.get('/api/v1/armstrong').json(); + setData(normalizeData(sortBy(result, ['server_id', 'channel_id']))); + } + + const setLoop = async () => { const interval = setInterval(() => { getData(); }, 10000); return () => clearInterval(interval); }; + useEffect(() => { + setTimeZone(Intl.DateTimeFormat().resolvedOptions().timeZone); + getData(); + setLoop(); + + return () => { + // eslint-disable-next-line no-undef + clearInterval(interval); + }; + }, []); + const handleFilterChange = (event) => { setFilter(event.target.value); }; @@ -78,19 +72,17 @@ export default function Armstrong() { (item) => item.name.toLowerCase().includes(filter.toLowerCase()) || item.deviceModel.toLowerCase().includes(filter.toLowerCase()) || - String(item.serverId).toLowerCase().includes(filter.toLowerCase()) + String(item.serverId).toLowerCase().includes(filter.toLowerCase()), ); return ( - <> -
    -
    - -
    -
    - - +
    +
    + +
    +
    +
    - + ); } diff --git a/app/javascript/Components/Armstrong/Filter/Filter.jsx b/app/javascript/Components/Armstrong/Filter/Filter.jsx index c58500c..7d57c4a 100644 --- a/app/javascript/Components/Armstrong/Filter/Filter.jsx +++ b/app/javascript/Components/Armstrong/Filter/Filter.jsx @@ -1,15 +1,15 @@ -import React from "react"; +import React from 'react'; +import PropTypes from 'prop-types'; export default function Filter({ filter, onFilterChange }) { return (
    - +
    ); } + +Filter.propTypes = { + filter: PropTypes.string.isRequired, + onFilterChange: PropTypes.func.isRequired, +}; diff --git a/app/javascript/Components/Armstrong/Table/Table.jsx b/app/javascript/Components/Armstrong/Table/Table.jsx index 44ff4b5..7494bd9 100644 --- a/app/javascript/Components/Armstrong/Table/Table.jsx +++ b/app/javascript/Components/Armstrong/Table/Table.jsx @@ -1,33 +1,51 @@ -import React from "react"; -import TableBody from "./TableBody"; -import TableHead from "./TableHead"; +import React from 'react'; +import PropTypes from 'prop-types'; +import TableBody from './TableBody'; +import TableHead from './TableHead'; -export default function Table({data}) { +export default function Table({ data }) { const columns = [ - { label: "State", accessor: "state" }, - { label: "SC", accessor: "specialControl" }, - { label: "Server", accessor: "serverId" }, - { label: "Channel", accessor: "channelId" }, - { label: "Name", accessor: "name" }, - { label: "DB", accessor: "deviceModel" }, - { label: "Room", accessor: "location" }, - { label: "Description", accessor: "locationDescription" }, - { label: "S. value", accessor: "eventSystemValue" }, - { label: "Not S. value", accessor: "eventNotSystemValue" }, - { label: "Time", accessor: "eventDatetime" }, - { label: "Count", accessor: "eventCount" }, - { label: "Impulses", accessor: "eventImpulseValue" }, + { label: 'State', accessor: 'state' }, + { label: 'SC', accessor: 'specialControl' }, + { label: 'Server', accessor: 'serverId' }, + { label: 'Channel', accessor: 'channelId' }, + { label: 'Name', accessor: 'name' }, + { label: 'DB', accessor: 'deviceModel' }, + { label: 'Room', accessor: 'location' }, + { label: 'Description', accessor: 'locationDescription' }, + { label: 'S. value', accessor: 'eventSystemValue' }, + { label: 'Not S. value', accessor: 'eventNotSystemValue' }, + { label: 'Time', accessor: 'eventDatetime' }, + { label: 'Count', accessor: 'eventCount' }, + { label: 'Impulses', accessor: 'eventImpulseValue' }, ]; return ( - <> -
    -
    - - - -
    Таблица мониторинга системы ARMStrong
    -
    - +
    + + + + +
    Таблица мониторинга системы ARMStrong
    +
    ); } + +Table.propTypes = { + data: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number.isRequired, + serverId: PropTypes.number, + channelId: PropTypes.number, + name: PropTypes.string, + deviceModel: PropTypes.string, + location: PropTypes.string, + locationDescription: PropTypes.string, + eventSystemValue: PropTypes.string, + eventNotSystemValue: PropTypes.string, + eventDatetime: PropTypes.string, + eventCount: PropTypes.number, + eventImpulseValue: PropTypes.string, + }), + ).isRequired, +}; diff --git a/app/javascript/Components/Armstrong/Table/TableBody.jsx b/app/javascript/Components/Armstrong/Table/TableBody.jsx index 966fed4..1b458c0 100644 --- a/app/javascript/Components/Armstrong/Table/TableBody.jsx +++ b/app/javascript/Components/Armstrong/Table/TableBody.jsx @@ -1,18 +1,42 @@ -import React from "react"; +import React from 'react'; +import PropTypes from 'prop-types'; export default function TableBody({ columns, data }) { return ( - {data.map((data) => { - return ( - - {columns.map(({ accessor }) => { - const tData = data[accessor] ? data[accessor] : "——"; - return {tData}; - })} - - ); - })} + {data.map((row) => ( + + {columns.map(({ accessor }) => { + const tData = row[accessor] ? row[accessor] : '——'; + return {tData}; + })} + + ))} ); } + +TableBody.propTypes = { + columns: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.string, + accessor: PropTypes.string, + }), + ).isRequired, + data: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number.isRequired, + serverId: PropTypes.number, + channelId: PropTypes.number, + name: PropTypes.string, + deviceModel: PropTypes.string, + location: PropTypes.string, + locationDescription: PropTypes.string, + eventSystemValue: PropTypes.string, + eventNotSystemValue: PropTypes.string, + eventDatetime: PropTypes.string, + eventCount: PropTypes.number, + eventImpulseValue: PropTypes.string, + }), + ).isRequired, +}; diff --git a/app/javascript/Components/Armstrong/Table/TableHead.jsx b/app/javascript/Components/Armstrong/Table/TableHead.jsx index db47062..e64d10e 100644 --- a/app/javascript/Components/Armstrong/Table/TableHead.jsx +++ b/app/javascript/Components/Armstrong/Table/TableHead.jsx @@ -1,13 +1,23 @@ -import React from "react"; +import React from 'react'; +import PropTypes from 'prop-types'; export default function TableHead({ columns }) { return ( - {columns.map(({ label, accessor }) => { - return {label}; - })} + {columns.map(({ label, accessor }) => ( + {label} + ))} ); } + +TableHead.propTypes = { + columns: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.string, + accessor: PropTypes.string, + }), + ).isRequired, +}; From 52cc5d7edf3e9349df0c5e7a9f11ad176e6bd025 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 2 Jun 2023 13:58:57 +0400 Subject: [PATCH 010/132] Update index page --- app/views/armstrong/index.html.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/armstrong/index.html.erb b/app/views/armstrong/index.html.erb index 4c09f3b..78ee16c 100644 --- a/app/views/armstrong/index.html.erb +++ b/app/views/armstrong/index.html.erb @@ -3,3 +3,5 @@ controller: "armstrong", })%>
    + +<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> From 415fd851d1ddbd234a7e3d8249685bb8478bc12f Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 2 Jun 2023 15:59:09 +0400 Subject: [PATCH 011/132] fixed bug when after saving conslusion inspection's delete redirects to this inspection and few small fixes --- app/controllers/inspection_controller.rb | 8 ++++++-- app/views/inspection/show.html.erb | 1 - config/routes.rb | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/controllers/inspection_controller.rb b/app/controllers/inspection_controller.rb index fd8c940..34ef424 100644 --- a/app/controllers/inspection_controller.rb +++ b/app/controllers/inspection_controller.rb @@ -81,8 +81,12 @@ def update def destroy @inspection.destroy - action = params[:previous_action] - redirect_to(action:) + action = if params[:previous_action].present? + action = params[:previous_action] + else + action = "new_tasks" + end + redirect_to(action: action) end def set_state(condition) diff --git a/app/views/inspection/show.html.erb b/app/views/inspection/show.html.erb index 746fa38..a07e968 100644 --- a/app/views/inspection/show.html.erb +++ b/app/views/inspection/show.html.erb @@ -1,6 +1,5 @@ <% provide :page_title, "Инспекция #{@inspection.id}" %> <%= render 'shared/flash_alert' %> - hidden="hidden">
    diff --git a/config/routes.rb b/config/routes.rb index c66064d..50def73 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -33,7 +33,7 @@ resources :device_model, :measurement_class, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, only: [:create, :new] - resources :inspection do + resources :inspection, except: [:index] do get :new_tasks, :to => 'inspection#new_tasks', :on => :collection get :my_tasks, :to => 'inspection#my_tasks', :on => :collection get :completed_tasks, :to => 'inspection#completed_tasks', :on => :collection From 2ecceb5c46c01d80b886b39483c56ddf6b2f3e30 Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 2 Jun 2023 15:59:59 +0400 Subject: [PATCH 012/132] used rubocop --- app/controllers/inspection_controller.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/inspection_controller.rb b/app/controllers/inspection_controller.rb index 34ef424..43bd9ef 100644 --- a/app/controllers/inspection_controller.rb +++ b/app/controllers/inspection_controller.rb @@ -81,12 +81,12 @@ def update def destroy @inspection.destroy - action = if params[:previous_action].present? - action = params[:previous_action] - else - action = "new_tasks" - end - redirect_to(action: action) + action = action = if params[:previous_action].present? + params[:previous_action] + else + 'new_tasks' + end + redirect_to(action:) end def set_state(condition) From 298cb88295663f2a656725afa0c870b7b552cc06 Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 2 Jun 2023 16:01:28 +0400 Subject: [PATCH 013/132] small fix --- app/controllers/inspection_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/inspection_controller.rb b/app/controllers/inspection_controller.rb index 43bd9ef..eefed8b 100644 --- a/app/controllers/inspection_controller.rb +++ b/app/controllers/inspection_controller.rb @@ -81,7 +81,7 @@ def update def destroy @inspection.destroy - action = action = if params[:previous_action].present? + action = if params[:previous_action].present? params[:previous_action] else 'new_tasks' From eff6f125718c5e3e75fd1d4284f5a7839fb86c4d Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 2 Jun 2023 16:02:21 +0400 Subject: [PATCH 014/132] again used rubocop --- app/controllers/inspection_controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/inspection_controller.rb b/app/controllers/inspection_controller.rb index eefed8b..ee1bcae 100644 --- a/app/controllers/inspection_controller.rb +++ b/app/controllers/inspection_controller.rb @@ -82,10 +82,10 @@ def update def destroy @inspection.destroy action = if params[:previous_action].present? - params[:previous_action] - else - 'new_tasks' - end + params[:previous_action] + else + 'new_tasks' + end redirect_to(action:) end From 34afcc71cdb7f16d34bbb8860f27ced0d18ef6d7 Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 7 Jun 2023 16:16:36 +0400 Subject: [PATCH 015/132] fix: rights for 'dosimetrist' role, flashs translates. add: flash alert on sign_in page --- .../admin/device_model_controller.rb | 2 +- .../admin/device_reg_group_controller.rb | 2 +- .../admin/manufacturer_controller.rb | 2 +- .../admin/measurement_class_controller.rb | 2 +- .../admin/measurement_group_controller.rb | 2 +- .../admin/supplementary_kit_controller.rb | 2 +- app/controllers/admin/users_controller.rb | 4 +-- app/controllers/concerns/device_concern.rb | 2 +- app/controllers/inspection_controller.rb | 4 +-- app/models/ability.rb | 1 + app/views/devise/registrations/new.html.erb | 1 - app/views/devise/sessions/new.html.erb | 2 +- app/views/shared/_flash_alert.html.erb | 2 +- config/locales/en.yml | 31 +++++++++++++++-- config/locales/ru.yml | 34 +++++++++++++++++-- 15 files changed, 74 insertions(+), 19 deletions(-) diff --git a/app/controllers/admin/device_model_controller.rb b/app/controllers/admin/device_model_controller.rb index 47c18ab..cdec15e 100644 --- a/app/controllers/admin/device_model_controller.rb +++ b/app/controllers/admin/device_model_controller.rb @@ -35,7 +35,7 @@ def destroy @device_model.destroy redirect_to(admin_device_model_index_path) else - flash[:error] = 'Ошибка! На модель прибора ссылаются приборы!' + flash[:error] = t('message.device_model.delete.error') redirect_to(admin_device_model_path(@device_model)) end end diff --git a/app/controllers/admin/device_reg_group_controller.rb b/app/controllers/admin/device_reg_group_controller.rb index c451642..273c3a6 100644 --- a/app/controllers/admin/device_reg_group_controller.rb +++ b/app/controllers/admin/device_reg_group_controller.rb @@ -34,7 +34,7 @@ def destroy if assigned_devices_count.zero? @device_reg_group.destroy else - flash[:error] = 'Ошибка! На эту регистрационную группу ссылаются приборы.' + flash[:error] = t('message.device_reg_group.delete.error') end redirect_to(admin_device_reg_group_index_path) end diff --git a/app/controllers/admin/manufacturer_controller.rb b/app/controllers/admin/manufacturer_controller.rb index 2dcfd9e..d346ca8 100644 --- a/app/controllers/admin/manufacturer_controller.rb +++ b/app/controllers/admin/manufacturer_controller.rb @@ -33,7 +33,7 @@ def destroy if assigned_device_models_count.zero? @manufacturer.destroy else - flash[:error] = 'Ошибка! На этого производителя ссылаются модели приборов!' + flash[:error] = t('message.manufacturer.delete.error') end redirect_to(admin_manufacturer_index_path) end diff --git a/app/controllers/admin/measurement_class_controller.rb b/app/controllers/admin/measurement_class_controller.rb index 7b6b0f2..448bdc3 100644 --- a/app/controllers/admin/measurement_class_controller.rb +++ b/app/controllers/admin/measurement_class_controller.rb @@ -33,7 +33,7 @@ def destroy if assigned_device_models_count.zero? @measurement_class.destroy else - flash[:error] = 'Ошибка! На класс измерения ссылаются модели приборов!' + flash[:error] = t('message.measurement_class.delete.error') end redirect_to(admin_measurement_class_index_path) end diff --git a/app/controllers/admin/measurement_group_controller.rb b/app/controllers/admin/measurement_group_controller.rb index b142eb8..db23401 100644 --- a/app/controllers/admin/measurement_group_controller.rb +++ b/app/controllers/admin/measurement_group_controller.rb @@ -33,7 +33,7 @@ def destroy if assigned_measurement_classes_count.zero? @measurement_group.destroy else - flash[:error] = 'Ошибка! На этот группу измерений ссылаются классы измерений!' + flash[:error] = t('message.measurement_group.delete.error') end redirect_to(admin_measurement_group_index_path) end diff --git a/app/controllers/admin/supplementary_kit_controller.rb b/app/controllers/admin/supplementary_kit_controller.rb index 2483541..ec5f64a 100644 --- a/app/controllers/admin/supplementary_kit_controller.rb +++ b/app/controllers/admin/supplementary_kit_controller.rb @@ -35,7 +35,7 @@ def destroy if assigned_devices_count.zero? && assigned_device_components_count.zero? @supplementary_kit.destroy else - flash[:error] = 'Ошибка! На этот набор ссылаются приборы или компоненты!' + flash[:error] = t('message.supplementary_kit.delete.error') end redirect_to(admin_supplementary_kit_index_path) end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index c628cc8..c49f9f0 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -32,7 +32,7 @@ def update params[:user].delete(:password_confirmation) end if @user.update(user_params) - flash[:notice] = 'User was updated' + flash[:notice] = t('message.user.update.success') if @user.id = current_user.id Time.use_zone(user_params[:timezone]) { nil } end @@ -50,7 +50,7 @@ def create @user = User.new(user_params) if @user.save - flash[:success] = 'User was added' + flash[:success] = t('message.user.create.success') redirect_to(admin_users_path) else render(:new, status: :unprocessable_entity) diff --git a/app/controllers/concerns/device_concern.rb b/app/controllers/concerns/device_concern.rb index dcd04a6..b0b197e 100644 --- a/app/controllers/concerns/device_concern.rb +++ b/app/controllers/concerns/device_concern.rb @@ -33,7 +33,7 @@ def device_destroy(device, path_success, path_failure) device.destroy redirect_to(path_success) else - flash[:error] = 'Ошибка! На этот прибор ссылаются инспекции или каналы!' + flash[:error] = t('message.device.delete.error') redirect_to(path_failure) end end diff --git a/app/controllers/inspection_controller.rb b/app/controllers/inspection_controller.rb index ee1bcae..3acc5fa 100644 --- a/app/controllers/inspection_controller.rb +++ b/app/controllers/inspection_controller.rb @@ -95,7 +95,7 @@ def set_state(condition) redirect_back(fallback_location: new_tasks_inspection_index_path) true else - flash[:error] = "Can't change state" + flash[:error] = t('message.inspection.change_state.error') redirect_back(fallback_location: new_tasks_inspection_index_path) end end @@ -104,7 +104,7 @@ def accept_task if set_state(@inspection.can_accept_task?) { @inspection.accept_task } @inspection.update(performer_id: current_user.id) else - flash.now[:error] = "Can't change state" + flash.now[:error] = t('message.inspection.change_state.error') end end diff --git a/app/models/ability.rb b/app/models/ability.rb index ec083b6..a6976a8 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -25,6 +25,7 @@ def initialize(user) end if user.dosimetrist? + can(:read, :armstrong) end if user.inspector? diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index 45db246..6997795 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -2,7 +2,6 @@ <%= image_tag "atom-logo.svg", class: "mb-4 mt-5", width: "72", height: "57" %>

    Форма регистрации

    <%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> - <%= f.error_notification %>
    diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 138adc8..2d118a3 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -1,7 +1,7 @@
    <%= image_tag "atom-logo.svg", class: "mb-4 mt-5", width: "72", height: "57" %>

    Форма входа

    - + <%= render 'shared/flash_alert'%> <%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
    <%= f.input :tabel_id, diff --git a/app/views/shared/_flash_alert.html.erb b/app/views/shared/_flash_alert.html.erb index 1de9eb0..9e11891 100644 --- a/app/views/shared/_flash_alert.html.erb +++ b/app/views/shared/_flash_alert.html.erb @@ -1,5 +1,5 @@ <% flash.each do |type, msg| %> - <% flash_class = if type == 'error' + <% flash_class = if type == 'error' || 'alert' "danger" elsif type == 'notice' "info" diff --git a/config/locales/en.yml b/config/locales/en.yml index 794fdac..1972a2c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -12,13 +12,40 @@ en: full: "%e %B %Y %H:%M:%S" message: user: + create: + success: User was created. + update: + success: User was updated. delete: success: User was deleted. error: User wasn't deleted! The user has inspections. + device: + delete: + error: Device wasn't deleted! This device has inspections and channels! + device_model: + delete: + error: Device model wasn't deleted! This model has devices! + device_reg_group: + delete: + error: Reg. group wasn't deleted! This reg. group has devices! + manufacturer: + delete: + error: Manufacturer wasn't deleted! This manufacturer has device models! + measurement_class: + delete: + error: Measurement class wasn't deleted! This measurement class has device models! + measurement_group: + delete: + error: Measurement group wasn't deleted! This measurement group has measurement classess! + supplementary_kit: + delete: + error: Kit wasn't deleted! This kit has приборы или компоненты! inspection: create_from_device: - success: Inspection was created - error: Error! Inspection wasn't created + success: Inspection was created. + error: Inspection wasn't created! + change_state: + error: State wasn't changed! layouts: application: home: Home diff --git a/config/locales/ru.yml b/config/locales/ru.yml index b8cad8f..124e899 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -144,13 +144,40 @@ ru: year: Год message: user: + create: + success: Пользователь создан. + update: + success: Пользователь обновлен. delete: success: Пользователь успешно удален. - error: Ошибка! Пользователь не удален! На пользователя ссылаются инспекции. + error: Пользователь не удален! На пользователя ссылаются инспекции! + device: + delete: + error: Прибор не удален! На этот прибор ссылаются инспекции или каналы! + device_model: + delete: + error: Модель прибора не удалена! На модель прибора ссылаются приборы! + device_reg_group: + delete: + error: Рег. группа не удалена! На эту рег. группу ссылаются приборы! + manufacturer: + delete: + error: Производитель не удален! На этого производителя ссылаются модели приборов! + measurement_class: + delete: + error: Класс измерений не удален! На этот класс измерений ссылаются модели приборов! + measurement_group: + delete: + error: Группа измерений не удалена! На группу измерений ссылаются классы измерений! + supplementary_kit: + delete: + error: Набор не удален! На этот набор ссылаются приборы или компоненты! inspection: create_from_device: - success: Инспекция создана - error: Ошибка! Инспекция не создана + success: Инспекция создана. + error: Инспекция не создана! + change_state: + error: Состояние не изменено! activerecord: models: device_model: Модель @@ -243,6 +270,7 @@ ru: current_password: Текущий пароль service: Служба service_id: Служба + timezone: Часовой пояс inspection: performer_id: Выполняющий performer: Выполняющий From 4c0c568b49d40546156f93323124a01bb817c416 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 8 Jun 2023 08:16:30 +0400 Subject: [PATCH 016/132] Fixed drop-down lists --- app/views/armstrong/index.html.erb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/armstrong/index.html.erb b/app/views/armstrong/index.html.erb index 78ee16c..4c09f3b 100644 --- a/app/views/armstrong/index.html.erb +++ b/app/views/armstrong/index.html.erb @@ -3,5 +3,3 @@ controller: "armstrong", })%>
    - -<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> From 5db71d0045b405d8cb92dec58893ed6448feeccb Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 12 Jun 2023 17:48:29 +0400 Subject: [PATCH 017/132] added feature to generate successful inspection from device. Fixied translations and now scrol doesn't dissapear after closing modal --- app/controllers/concerns/device_concern.rb | 14 ++++- .../controllers/modal_controller.js | 3 +- app/views/inspection/_form.html.erb | 3 +- app/views/shared/_flash_alert.html.erb | 2 +- app/views/shared/_modal.html.erb | 2 +- app/views/shared/show/_device.html.erb | 31 ++++++++-- config/locales/en.yml | 56 ++++++++++--------- config/locales/ru.yml | 2 + 8 files changed, 75 insertions(+), 38 deletions(-) diff --git a/app/controllers/concerns/device_concern.rb b/app/controllers/concerns/device_concern.rb index b0b197e..53207be 100644 --- a/app/controllers/concerns/device_concern.rb +++ b/app/controllers/concerns/device_concern.rb @@ -39,7 +39,17 @@ def device_destroy(device, path_success, path_failure) end def create_inspection_for_device(device) - inspection = device.inspections.build(creator_id: current_user.id, type_target: inspection_params[:type_target]) + if inspection_params[:is_admin] + inspection = device.inspections.build(creator_id: current_user.id, + performer_id: current_user.id, + type_target: inspection_params[:type_target], + conclusion_date: inspection_params[:conclusion_date].to_datetime + 12.hours, + conclusion: t('message.inspection.create_from_device.generated'), + state: Inspection::STATES[:verification_successful] + ) + else + inspection = device.inspections.build(creator_id: current_user.id, type_target: inspection_params[:type_target]) + end if inspection.save flash[:success] = t('message.inspection.create_from_device.success') redirect_to(device_path(device)) @@ -50,7 +60,7 @@ def create_inspection_for_device(device) end def inspection_params - params.require(:inspection).permit(:type_target) + params.require(:inspection).permit(:type_target, :conclusion_date, :is_admin) end def device_params diff --git a/app/javascript/controllers/modal_controller.js b/app/javascript/controllers/modal_controller.js index a4bf957..35cd2f8 100644 --- a/app/javascript/controllers/modal_controller.js +++ b/app/javascript/controllers/modal_controller.js @@ -16,7 +16,7 @@ export default class extends Controller { open() { document.getElementById('modal').classList.remove('d-none'); document.getElementById('modal').classList.add('d-flex'); - document.body.classList.add('fixed', 'inset-x-0', 'overflow-hidden'); + document.body.classList.add('inset-x-0', 'overflow-hidden'); this.containerTarget.classList.remove(this.toggleClass); if (this.background) { this.background.remove(); @@ -35,6 +35,7 @@ export default class extends Controller { } document.getElementById('modal').classList.remove('d-flex'); document.getElementById('modal').classList.add('d-none'); + document.body.classList.remove('inset-x-0', 'overflow-hidden'); } modalBackgroundHTML() { diff --git a/app/views/inspection/_form.html.erb b/app/views/inspection/_form.html.erb index a6ada65..6349493 100644 --- a/app/views/inspection/_form.html.erb +++ b/app/views/inspection/_form.html.erb @@ -1,4 +1,3 @@ -<%= render 'shared/flash_alert'%> <%= simple_form_for @inspection, as: :inspection, url: path, class: "form-group row" do |f| %>
    @@ -14,7 +13,7 @@ <% end %> <% if current_user.admin? %> - <%= f.input :conclusion_date, include_blank: true %> + <%= f.input :conclusion_date, as: :datetime, html5: true, include_blank: true %> <%= f.association :performer, label_method: :tabel_id, collection: User.where(role: [User::ROLES[:inspector], User::ROLES[:engineer], User::ROLES[:admin]]), include_blank: "Не выбран" %> <% end %>
    diff --git a/app/views/shared/_flash_alert.html.erb b/app/views/shared/_flash_alert.html.erb index 9e11891..f5bb23b 100644 --- a/app/views/shared/_flash_alert.html.erb +++ b/app/views/shared/_flash_alert.html.erb @@ -1,5 +1,5 @@ <% flash.each do |type, msg| %> - <% flash_class = if type == 'error' || 'alert' + <% flash_class = if type == 'error' || type == 'alert' "danger" elsif type == 'notice' "info" diff --git a/app/views/shared/_modal.html.erb b/app/views/shared/_modal.html.erb index 97d299b..9cd0d27 100644 --- a/app/views/shared/_modal.html.erb +++ b/app/views/shared/_modal.html.erb @@ -1,5 +1,5 @@
    - - - - - - - - - - - - - <% @manufacturers.each do |manufacturer| %> - - - - - - - +
    +
    <%= t('activerecord.attributes.manufacturer.name') %><%= t('activerecord.attributes.manufacturer.adress') %><%= t('activerecord.attributes.manufacturer.phone') %><%= t('activerecord.attributes.manufacturer.email') %><%= t('activerecord.attributes.manufacturer.site_url') %><%= t('action') %>
    <%= manufacturer.name %><%= manufacturer.adress %><%= manufacturer.phone %><%= manufacturer.email %><%= manufacturer.site_url %> -
    - <%= link_to edit_admin_manufacturer_path(manufacturer.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_manufacturer_path(manufacturer.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + + + + + + + + - <% end %> - -
    <%= t('activerecord.attributes.manufacturer.name') %><%= t('activerecord.attributes.manufacturer.adress') %><%= t('activerecord.attributes.manufacturer.phone') %><%= t('activerecord.attributes.manufacturer.email') %><%= t('activerecord.attributes.manufacturer.site_url') %><%= t('action') %>
    + + + <% @manufacturers.each do |manufacturer| %> + + <%= manufacturer.name %> + <%= manufacturer.adress %> + <%= manufacturer.phone %> + <%= manufacturer.email %> + <%= manufacturer.site_url %> + +
    + <%= link_to edit_admin_manufacturer_path(manufacturer.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to admin_manufacturer_path(manufacturer.id), method: :delete, class: "btn" do %> + + <% end %> +
    + + + <% end %> + + +
    @@ -110,4 +112,4 @@ input.value = null }) } - \ No newline at end of file + From 153e69ac9813e890807db87a1afe96172fd15483 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 15 Sep 2023 09:30:47 +0400 Subject: [PATCH 043/132] Updated measurement_class view --- .../admin/measurement_class/index.html.erb | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/app/views/admin/measurement_class/index.html.erb b/app/views/admin/measurement_class/index.html.erb index 9bb377e..a7ec6ab 100644 --- a/app/views/admin/measurement_class/index.html.erb +++ b/app/views/admin/measurement_class/index.html.erb @@ -33,35 +33,37 @@ <%= render 'shared/modal_button_add', path: new_admin_measurement_class_path, classes: "btn btn-primary w-100", text: t("b_add") %>
    - - - - - - - - - - - <% @measurement_classes.each do |measurement_class| %> - - - - - +
    +
    <%= t('activerecord.attributes.measurement_class.name') %><%= t('activerecord.attributes.measurement_class.arms_device_type') %><%= t('activerecord.attributes.measurement_class.measuremnt_group') %><%= t('action') %>
    <%= measurement_class.name %><%= measurement_class.arms_device_type %><%= measurement_class.measurement_group.name %> -
    - <%= link_to edit_admin_measurement_class_path(measurement_class.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_measurement_class_path(measurement_class.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + + + + + + - <% end %> - -
    <%= t('activerecord.attributes.measurement_class.name') %><%= t('activerecord.attributes.measurement_class.arms_device_type') %><%= t('activerecord.attributes.measurement_class.measuremnt_group') %><%= t('action') %>
    + + + <% @measurement_classes.each do |measurement_class| %> + + <%= measurement_class.name %> + <%= measurement_class.arms_device_type %> + <%= measurement_class.measurement_group.name %> + +
    + <%= link_to edit_admin_measurement_class_path(measurement_class.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to admin_measurement_class_path(measurement_class.id), method: :delete, class: "btn" do %> + + <% end %> +
    + + + <% end %> + + +
    From 1854823139b19b3d8ea2a715ba959b1348ccfe29 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 15 Sep 2023 09:31:32 +0400 Subject: [PATCH 044/132] Updated measurement_group view --- .../admin/measurement_group/index.html.erb | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/app/views/admin/measurement_group/index.html.erb b/app/views/admin/measurement_group/index.html.erb index e43f4c8..16af796 100644 --- a/app/views/admin/measurement_group/index.html.erb +++ b/app/views/admin/measurement_group/index.html.erb @@ -28,31 +28,33 @@ <%= render 'shared/modal_button_add', path: new_admin_measurement_group_path, classes: "btn btn-primary w-100", text: t("b_add") %>
    - - - - - - - - - <% @measurement_groupes.each do |measurement_group| %> - - - +
    +
    <%= t('activerecord.attributes.measurement_group.name') %><%= t('action') %>
    <%= measurement_group.name %> -
    - <%= link_to edit_admin_measurement_group_path(measurement_group.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_measurement_group_path(measurement_group.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + + + + - <% end %> - -
    <%= t('activerecord.attributes.measurement_group.name') %><%= t('action') %>
    + + + <% @measurement_groupes.each do |measurement_group| %> + + <%= measurement_group.name %> + +
    + <%= link_to edit_admin_measurement_group_path(measurement_group.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to admin_measurement_group_path(measurement_group.id), method: :delete, class: "btn" do %> + + <% end %> +
    + + + <% end %> + + +
    From 1856244cd5e2a76221f0b2c6a647d7535b9be472 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 18 Sep 2023 09:57:56 +0400 Subject: [PATCH 045/132] Added channels color for channel state --- .../Components/Armstrong/Armstrong.jsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/javascript/Components/Armstrong/Armstrong.jsx b/app/javascript/Components/Armstrong/Armstrong.jsx index ed4fba8..897c375 100644 --- a/app/javascript/Components/Armstrong/Armstrong.jsx +++ b/app/javascript/Components/Armstrong/Armstrong.jsx @@ -30,17 +30,25 @@ export default function Armstrong() { }; const closeChart = () => setChartOpen(false); + const setChannelColor = (state) => { + switch (state) { + case 'normal': + return ; + case 'warning': + return ; + case 'danger': + return ; + default: + return ; + } + }; + const normalizeData = (rawData) => { const normAndSortedDate = []; rawData.forEach((channel) => { normAndSortedDate.push({ - state: - channel.state === 'normal' ? ( - - ) : ( - - ), + state: setChannelColor(channel.state), specialControl: , id: channel.id, serverId: channel.server_id, From cd5f3191bc7e581d2501c7e6ddb2d0636752c115 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 20 Sep 2023 15:08:46 +0400 Subject: [PATCH 046/132] Updated login screen and add included GNU GPLv3 license text (ru and eng) --- app/controllers/about_controller.rb | 3 - app/controllers/licenses_controller.rb | 5 + app/helpers/about_helper.rb | 2 - app/helpers/license_helper.rb | 2 + app/views/about/index.html.erb | 43 - app/views/devise/sessions/new.html.erb | 44 +- app/views/layouts/application.html.erb | 3 - app/views/licenses/_en.html.erb | 845 +++++++++++++++++ app/views/licenses/_ru.html.erb | 910 +++++++++++++++++++ app/views/licenses/show.html.erb | 24 + app/views/shared/_about.html.erb | 42 + config/routes.rb | 2 +- test/controllers/about_controller_test.rb | 8 - test/controllers/licenses_controller_test.rb | 4 + 14 files changed, 1859 insertions(+), 78 deletions(-) delete mode 100644 app/controllers/about_controller.rb create mode 100644 app/controllers/licenses_controller.rb delete mode 100644 app/helpers/about_helper.rb create mode 100644 app/helpers/license_helper.rb delete mode 100644 app/views/about/index.html.erb create mode 100644 app/views/licenses/_en.html.erb create mode 100644 app/views/licenses/_ru.html.erb create mode 100644 app/views/licenses/show.html.erb create mode 100644 app/views/shared/_about.html.erb delete mode 100644 test/controllers/about_controller_test.rb create mode 100644 test/controllers/licenses_controller_test.rb diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb deleted file mode 100644 index 65749b3..0000000 --- a/app/controllers/about_controller.rb +++ /dev/null @@ -1,3 +0,0 @@ -class AboutController < ApplicationController - def index; end -end diff --git a/app/controllers/licenses_controller.rb b/app/controllers/licenses_controller.rb new file mode 100644 index 0000000..5ba959b --- /dev/null +++ b/app/controllers/licenses_controller.rb @@ -0,0 +1,5 @@ +class LicensesController < ApplicationController + def show + @locale = params[:locale] + end +end diff --git a/app/helpers/about_helper.rb b/app/helpers/about_helper.rb deleted file mode 100644 index 68e69ae..0000000 --- a/app/helpers/about_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module AboutHelper -end diff --git a/app/helpers/license_helper.rb b/app/helpers/license_helper.rb new file mode 100644 index 0000000..fcad16a --- /dev/null +++ b/app/helpers/license_helper.rb @@ -0,0 +1,2 @@ +module LicenseHelper +end diff --git a/app/views/about/index.html.erb b/app/views/about/index.html.erb deleted file mode 100644 index f5f94c1..0000000 --- a/app/views/about/index.html.erb +++ /dev/null @@ -1,43 +0,0 @@ -
    diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 2d118a3..1bdecb3 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -1,22 +1,30 @@ -
    - <%= image_tag "atom-logo.svg", class: "mb-4 mt-5", width: "72", height: "57" %> -

    Форма входа

    - <%= render 'shared/flash_alert'%> - <%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> -
    - <%= f.input :tabel_id, - required: false, - autofocus: true, - input_html: { autocomplete: "tabel-id" } %> - <%= f.input :password, - required: false, - input_html: { autocomplete: "current-password" } %> -
    +
    +
    +
    +
    + <%= image_tag "atom-logo.svg", class: "mb-4 mt-5", width: "72", height: "57" %> +

    Форма входа

    + <%= render 'shared/flash_alert'%> + <%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> +
    + <%= f.input :tabel_id, + required: false, + autofocus: true, + input_html: { autocomplete: "tabel-id" } %> + <%= f.input :password, + required: false, + input_html: { autocomplete: "current-password" } %> +
    + +
    + <%= f.button :submit, t("b_login"), class: "btn btn-primary"%> +
    + <% end %> -
    - <%= f.button :submit, t("b_login"), class: "btn btn-primary"%> + <%= render "devise/shared/links" %>
    - <% end %> +
    + <%= render "shared/about" %> +
    - <%= render "devise/shared/links" %>
    diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 8447d70..0d8ca57 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -36,7 +36,6 @@ <%end%> <% end %> -
  • <%= link_to t(".about"), about_index_path, class: "nav-link px-2 link-secondary" %>
  • <% if user_signed_in? %> @@ -58,8 +57,6 @@
  • <%= button_to t("b_logout"), destroy_user_session_path, method: :delete, class: "dropdown-item"%>
  • - <% else %> - <%= link_to t("b_login"), new_user_session_path, class: "btn btn-primary" %> <% end %>
    diff --git a/app/views/licenses/_en.html.erb b/app/views/licenses/_en.html.erb new file mode 100644 index 0000000..ef4cb2a --- /dev/null +++ b/app/views/licenses/_en.html.erb @@ -0,0 +1,845 @@ +
    +
    +

    GNU GENERAL PUBLIC LICENSE

    +

    Version 3, 29 June 2007

    +
    + +

    + Copyright © 2007 + Free Software Foundation, Inc. +

    + +

    + Everyone is permitted to copy and distribute verbatim copies of this license + document, but changing it is not allowed. +

    + +

    Preamble

    +

    + The GNU General Public License is a free, copyleft license for software and + other kinds of works. +

    + +

    + The licenses for most software and other practical works are designed to + take away your freedom to share and change the works. By contrast, the GNU + General Public License is intended to guarantee your freedom to share and + change all versions of a program--to make sure it remains free software for + all its users. We, the Free Software Foundation, use the GNU General Public + License for most of our software; it applies also to any other work released + this way by its authors. You can apply it to your programs, too. +

    + +

    + When we speak of free software, we are referring to freedom, not price. Our + General Public Licenses are designed to make sure that you have the freedom + to distribute copies of free software (and charge for them if you wish), + that you receive source code or can get it if you want it, that you can + change the software or use pieces of it in new free programs, and that you + know you can do these things. +

    + +

    + To protect your rights, we need to prevent others from denying you these + rights or asking you to surrender the rights. Therefore, you have certain + responsibilities if you distribute copies of the software, or if you modify + it: responsibilities to respect the freedom of others. +

    + +

    + For example, if you distribute copies of such a program, whether gratis or + for a fee, you must pass on to the recipients the same freedoms that you + received. You must make sure that they, too, receive or can get the source + code. And you must show them these terms so they know their rights. +

    + +

    + Developers that use the GNU GPL protect your rights with two steps: (1) + assert copyright on the software, and (2) offer you this License giving you + legal permission to copy, distribute and/or modify it. +

    + +

    + For the developers' and authors' protection, the GPL clearly explains that + there is no warranty for this free software. For both users' and authors' + sake, the GPL requires that modified versions be marked as changed, so that + their problems will not be attributed erroneously to authors of previous + versions. +

    + +

    + Some devices are designed to deny users access to install or run modified + versions of the software inside them, although the manufacturer can do so. + This is fundamentally incompatible with the aim of protecting users' freedom + to change the software. The systematic pattern of such abuse occurs in the + area of products for individuals to use, which is precisely where it is most + unacceptable. Therefore, we have designed this version of the GPL to + prohibit the practice for those products. If such problems arise + substantially in other domains, we stand ready to extend this provision to + those domains in future versions of the GPL, as needed to protect the + freedom of users. +

    + +

    + Finally, every program is threatened constantly by software patents. States + should not allow patents to restrict development and use of software on + general-purpose computers, but in those that do, we wish to avoid the + special danger that patents applied to a free program could make it + effectively proprietary. To prevent this, the GPL assures that patents + cannot be used to render the program non-free. +

    + +

    + The precise terms and conditions for copying, distribution and modification + follow. +

    + +

    TERMS AND CONDITIONS

    +

    0. Definitions.

    + +

    “This License” refers to version 3 of the GNU General Public License.

    + +

    + “Copyright” also means copyright-like laws that apply to other kinds of + works, such as semiconductor masks. +

    + +

    + “The Program” refers to any copyrightable work licensed under this License. + Each licensee is addressed as “you”. “Licensees” and “recipients” may be + individuals or organizations. +

    + +

    + To “modify” a work means to copy from or adapt all or part of the work in a + fashion requiring copyright permission, other than the making of an exact + copy. The resulting work is called a “modified version” of the earlier work + or a work “based on” the earlier work. +

    + +

    + A “covered work” means either the unmodified Program or a work based on the + Program. +

    + +

    + To “propagate” a work means to do anything with it that, without permission, + would make you directly or secondarily liable for infringement under + applicable copyright law, except executing it on a computer or modifying a + private copy. Propagation includes copying, distribution (with or without + modification), making available to the public, and in some countries other + activities as well. +

    + +

    + To “convey” a work means any kind of propagation that enables other parties + to make or receive copies. Mere interaction with a user through a computer + network, with no transfer of a copy, is not conveying. +

    + +

    + An interactive user interface displays “Appropriate Legal Notices” to the + extent that it includes a convenient and prominently visible feature that + (1) displays an appropriate copyright notice, and (2) tells the user that + there is no warranty for the work (except to the extent that warranties are + provided), that licensees may convey the work under this License, and how to + view a copy of this License. If the interface presents a list of user + commands or options, such as a menu, a prominent item in the list meets this + criterion. +

    + +

    1. Source Code.

    + +

    + The “source code” for a work means the preferred form of the work for making + modifications to it. “Object code” means any non-source form of a work. +

    + +

    + A “Standard Interface” means an interface that either is an official + standard defined by a recognized standards body, or, in the case of + interfaces specified for a particular programming language, one that is + widely used among developers working in that language. +

    + +

    + The “System Libraries” of an executable work include anything, other than + the work as a whole, that (a) is included in the normal form of packaging a + Major Component, but which is not part of that Major Component, and (b) + serves only to enable use of the work with that Major Component, or to + implement a Standard Interface for which an implementation is available to + the public in source code form. A “Major Component”, in this context, means + a major essential component (kernel, window system, and so on) of the + specific operating system (if any) on which the executable work runs, or a + compiler used to produce the work, or an object code interpreter used to run + it. +

    + +

    + The “Corresponding Source” for a work in object code form means all the + source code needed to generate, install, and (for an executable work) run + the object code and to modify the work, including scripts to control those + activities. However, it does not include the work's System Libraries, or + general-purpose tools or generally available free programs which are used + unmodified in performing those activities but which are not part of the + work. For example, Corresponding Source includes interface definition files + associated with source files for the work, and the source code for shared + libraries and dynamically linked subprograms that the work is specifically + designed to require, such as by intimate data communication or control flow + between those subprograms and other parts of the work. +

    + +

    + The Corresponding Source need not include anything that users can regenerate + automatically from other parts of the Corresponding Source. +

    + +

    + The Corresponding Source for a work in source code form is that same work. +

    + +

    2. Basic Permissions.

    + +

    + All rights granted under this License are granted for the term of copyright + on the Program, and are irrevocable provided the stated conditions are met. + This License explicitly affirms your unlimited permission to run the + unmodified Program. The output from running a covered work is covered by + this License only if the output, given its content, constitutes a covered + work. This License acknowledges your rights of fair use or other equivalent, + as provided by copyright law. +

    + +

    + You may make, run and propagate covered works that you do not convey, + without conditions so long as your license otherwise remains in force. You + may convey covered works to others for the sole purpose of having them make + modifications exclusively for you, or provide you with facilities for + running those works, provided that you comply with the terms of this License + in conveying all material for which you do not control copyright. Those thus + making or running the covered works for you must do so exclusively on your + behalf, under your direction and control, on terms that prohibit them from + making any copies of your copyrighted material outside their relationship + with you. +

    + +

    + Conveying under any other circumstances is permitted solely under the + conditions stated below. Sublicensing is not allowed; section 10 makes it + unnecessary. +

    + +

    3. Protecting Users' Legal Rights From Anti-Circumvention Law.

    + +

    + No covered work shall be deemed part of an effective technological measure + under any applicable law fulfilling obligations under article 11 of the WIPO + copyright treaty adopted on 20 December 1996, or similar laws prohibiting or + restricting circumvention of such measures. +

    + +

    + When you convey a covered work, you waive any legal power to forbid + circumvention of technological measures to the extent such circumvention is + effected by exercising rights under this License with respect to the covered + work, and you disclaim any intention to limit operation or modification of + the work as a means of enforcing, against the work's users, your or third + parties' legal rights to forbid circumvention of technological measures. +

    + +

    4. Conveying Verbatim Copies.

    + +

    + You may convey verbatim copies of the Program's source code as you receive + it, in any medium, provided that you conspicuously and appropriately publish + on each copy an appropriate copyright notice; keep intact all notices + stating that this License and any non-permissive terms added in accord with + section 7 apply to the code; keep intact all notices of the absence of any + warranty; and give all recipients a copy of this License along with the + Program. +

    + +

    + You may charge any price or no price for each copy that you convey, and you + may offer support or warranty protection for a fee. +

    + +

    5. Conveying Modified Source Versions.

    + +

    + You may convey a work based on the Program, or the modifications to produce + it from the Program, in the form of source code under the terms of section + 4, provided that you also meet all of these conditions: +

    + +
      +
    1. + The work must carry prominent notices stating that you modified it, and + giving a relevant date. +
    2. +
    3. + The work must carry prominent notices stating that it is released under + this License and any conditions added under section 7. This requirement + modifies the requirement in section 4 to “keep intact all notices”. +
    4. +
    5. + You must license the entire work, as a whole, under this License to anyone + who comes into possession of a copy. This License will therefore apply, + along with any applicable section 7 additional terms, to the whole of the + work, and all its parts, regardless of how they are packaged. This License + gives no permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +
    6. +
    7. + If the work has interactive user interfaces, each must display Appropriate + Legal Notices; however, if the Program has interactive interfaces that do + not display Appropriate Legal Notices, your work need not make them do so. +
    8. +
    + +

    + A compilation of a covered work with other separate and independent works, + which are not by their nature extensions of the covered work, and which are + not combined with it such as to form a larger program, in or on a volume of + a storage or distribution medium, is called an “aggregate” if the + compilation and its resulting copyright are not used to limit the access or + legal rights of the compilation's users beyond what the individual works + permit. Inclusion of a covered work in an aggregate does not cause this + License to apply to the other parts of the aggregate. +

    + +

    6. Conveying Non-Source Forms.

    + +

    + You may convey a covered work in object code form under the terms of + sections 4 and 5, provided that you also convey the machine-readable + Corresponding Source under the terms of this License, in one of these ways: +

    + +
      +
    1. + Convey the object code in, or embodied in, a physical product (including a + physical distribution medium), accompanied by the Corresponding Source + fixed on a durable physical medium customarily used for software + interchange. +
    2. +
    3. + Convey the object code in, or embodied in, a physical product (including a + physical distribution medium), accompanied by a written offer, valid for + at least three years and valid for as long as you offer spare parts or + customer support for that product model, to give anyone who possesses the + object code either (1) a copy of the Corresponding Source for all the + software in the product that is covered by this License, on a durable + physical medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this conveying of + source, or (2) access to copy the Corresponding Source from a network + server at no charge. +
    4. +
    5. + Convey individual copies of the object code with a copy of the written + offer to provide the Corresponding Source. This alternative is allowed + only occasionally and noncommercially, and only if you received the object + code with such an offer, in accord with subsection 6b. +
    6. +
    7. + Convey the object code by offering access from a designated place (gratis + or for a charge), and offer equivalent access to the Corresponding Source + in the same way through the same place at no further charge. You need not + require recipients to copy the Corresponding Source along with the object + code. If the place to copy the object code is a network server, the + Corresponding Source may be on a different server (operated by you or a + third party) that supports equivalent copying facilities, provided you + maintain clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the Corresponding + Source, you remain obligated to ensure that it is available for as long as + needed to satisfy these requirements. +
    8. +
    9. + Convey the object code using peer-to-peer transmission, provided you + inform other peers where the object code and Corresponding Source of the + work are being offered to the general public at no charge under subsection + 6d. +
    10. +
    + +

    + A separable portion of the object code, whose source code is excluded from + the Corresponding Source as a System Library, need not be included in + conveying the object code work. +

    + +

    + A “User Product” is either (1) a “consumer product”, which means any + tangible personal property which is normally used for personal, family, or + household purposes, or (2) anything designed or sold for incorporation into + a dwelling. In determining whether a product is a consumer product, doubtful + cases shall be resolved in favor of coverage. For a particular product + received by a particular user, “normally used” refers to a typical or common + use of that class of product, regardless of the status of the particular + user or of the way in which the particular user actually uses, or expects or + is expected to use, the product. A product is a consumer product regardless + of whether the product has substantial commercial, industrial or + non-consumer uses, unless such uses represent the only significant mode of + use of the product. +

    +

    + +

    + “Installation Information” for a User Product means any methods, procedures, + authorization keys, or other information required to install and execute + modified versions of a covered work in that User Product from a modified + version of its Corresponding Source. The information must suffice to ensure + that the continued functioning of the modified object code is in no case + prevented or interfered with solely because modification has been made. +

    + +

    + If you convey an object code work under this section in, or with, or + specifically for use in, a User Product, and the conveying occurs as part of + a transaction in which the right of possession and use of the User Product + is transferred to the recipient in perpetuity or for a fixed term + (regardless of how the transaction is characterized), the Corresponding + Source conveyed under this section must be accompanied by the Installation + Information. But this requirement does not apply if neither you nor any + third party retains the ability to install modified object code on the User + Product (for example, the work has been installed in ROM). +

    + +

    + The requirement to provide Installation Information does not include a + requirement to continue to provide support service, warranty, or updates for + a work that has been modified or installed by the recipient, or for the User + Product in which it has been modified or installed. Access to a network may + be denied when the modification itself materially and adversely affects the + operation of the network or violates the rules and protocols for + communication across the network. +

    + +

    + Corresponding Source conveyed, and Installation Information provided, in + accord with this section must be in a format that is publicly documented + (and with an implementation available to the public in source code form), + and must require no special password or key for unpacking, reading or + copying. +

    + +

    7. Additional Terms.

    + +

    + “Additional permissions” are terms that supplement the terms of this License + by making exceptions from one or more of its conditions. Additional + permissions that are applicable to the entire Program shall be treated as + though they were included in this License, to the extent that they are valid + under applicable law. If additional permissions apply only to part of the + Program, that part may be used separately under those permissions, but the + entire Program remains governed by this License without regard to the + additional permissions. +

    + +

    + When you convey a copy of a covered work, you may at your option remove any + additional permissions from that copy, or from any part of it. (Additional + permissions may be written to require their own removal in certain cases + when you modify the work.) You may place additional permissions on material, + added by you to a covered work, for which you have or can give appropriate + copyright permission. +

    + +

    + Notwithstanding any other provision of this License, for material you add to + a covered work, you may (if authorized by the copyright holders of that + material) supplement the terms of this License with terms: +

    + +
      +
    1. + Disclaiming warranty or limiting liability differently from the terms of + sections 15 and 16 of this License; or +
    2. +
    3. + Requiring preservation of specified reasonable legal notices or author + attributions in that material or in the Appropriate Legal Notices + displayed by works containing it; or +
    4. +
    5. + Prohibiting misrepresentation of the origin of that material, or requiring + that modified versions of such material be marked in reasonable ways as + different from the original version; or +
    6. +
    7. + Limiting the use for publicity purposes of names of licensors or authors + of the material; or +
    8. +
    9. + Declining to grant rights under trademark law for use of some trade names, + trademarks, or service marks; or +
    10. +
    11. + Requiring indemnification of licensors and authors of that material by + anyone who conveys the material (or modified versions of it) with + contractual assumptions of liability to the recipient, for any liability + that these contractual assumptions directly impose on those licensors and + authors. +
    12. +
    + +

    + All other non-permissive additional terms are considered “further + restrictions” within the meaning of section 10. If the Program as you + received it, or any part of it, contains a notice stating that it is + governed by this License along with a term that is a further restriction, + you may remove that term. If a license document contains a further + restriction but permits relicensing or conveying under this License, you may + add to a covered work material governed by the terms of that license + document, provided that the further restriction does not survive such + relicensing or conveying. +

    + +

    + If you add terms to a covered work in accord with this section, you must + place, in the relevant source files, a statement of the additional terms + that apply to those files, or a notice indicating where to find the + applicable terms. +

    + +

    + Additional terms, permissive or non-permissive, may be stated in the form of + a separately written license, or stated as exceptions; the above + requirements apply either way. +

    + +

    8. Termination.

    + +

    + You may not propagate or modify a covered work except as expressly provided + under this License. Any attempt otherwise to propagate or modify it is void, + and will automatically terminate your rights under this License (including + any patent licenses granted under the third paragraph of section 11). +

    + +

    + However, if you cease all violation of this License, then your license from + a particular copyright holder is reinstated (a) provisionally, unless and + until the copyright holder explicitly and finally terminates your license, + and (b) permanently, if the copyright holder fails to notify you of the + violation by some reasonable means prior to 60 days after the cessation. +

    + +

    + Moreover, your license from a particular copyright holder is reinstated + permanently if the copyright holder notifies you of the violation by some + reasonable means, this is the first time you have received notice of + violation of this License (for any work) from that copyright holder, and you + cure the violation prior to 30 days after your receipt of the notice. +

    + +

    + Termination of your rights under this section does not terminate the + licenses of parties who have received copies or rights from you under this + License. If your rights have been terminated and not permanently reinstated, + you do not qualify to receive new licenses for the same material under + section 10. +

    + +

    9. Acceptance Not Required for Having Copies.

    + +

    + You are not required to accept this License in order to receive or run a + copy of the Program. Ancillary propagation of a covered work occurring + solely as a consequence of using peer-to-peer transmission to receive a copy + likewise does not require acceptance. However, nothing other than this + License grants you permission to propagate or modify any covered work. These + actions infringe copyright if you do not accept this License. Therefore, by + modifying or propagating a covered work, you indicate your acceptance of + this License to do so. +

    +

    + +

    10. Automatic Licensing of Downstream Recipients.

    + +

    + Each time you convey a covered work, the recipient automatically receives a + license from the original licensors, to run, modify and propagate that work, + subject to this License. You are not responsible for enforcing compliance by + third parties with this License. +

    + +

    + An “entity transaction” is a transaction transferring control of an + organization, or substantially all assets of one, or subdividing an + organization, or merging organizations. If propagation of a covered work + results from an entity transaction, each party to that transaction who + receives a copy of the work also receives whatever licenses to the work the + party's predecessor in interest had or could give under the previous + paragraph, plus a right to possession of the Corresponding Source of the + work from the predecessor in interest, if the predecessor has it or can get + it with reasonable efforts. +

    + +

    + You may not impose any further restrictions on the exercise of the rights + granted or affirmed under this License. For example, you may not impose a + license fee, royalty, or other charge for exercise of rights granted under + this License, and you may not initiate litigation (including a cross-claim + or counterclaim in a lawsuit) alleging that any patent claim is infringed by + making, using, selling, offering for sale, or importing the Program or any + portion of it. +

    + +

    11. Patents.

    + +

    + A “contributor” is a copyright holder who authorizes use under this License + of the Program or a work on which the Program is based. The work thus + licensed is called the contributor's “contributor version”. +

    + +

    + A contributor's “essential patent claims” are all patent claims owned or + controlled by the contributor, whether already acquired or hereafter + acquired, that would be infringed by some manner, permitted by this License, + of making, using, or selling its contributor version, but do not include + claims that would be infringed only as a consequence of further modification + of the contributor version. For purposes of this definition, “control” + includes the right to grant patent sublicenses in a manner consistent with + the requirements of this License. +

    + +

    + Each contributor grants you a non-exclusive, worldwide, royalty-free patent + license under the contributor's essential patent claims, to make, use, sell, + offer for sale, import and otherwise run, modify and propagate the contents + of its contributor version. +

    + +

    + In the following three paragraphs, a “patent license” is any express + agreement or commitment, however denominated, not to enforce a patent (such + as an express permission to practice a patent or covenant not to sue for + patent infringement). To “grant” such a patent license to a party means to + make such an agreement or commitment not to enforce a patent against the + party. +

    + +

    + If you convey a covered work, knowingly relying on a patent license, and the + Corresponding Source of the work is not available for anyone to copy, free + of charge and under the terms of this License, through a publicly available + network server or other readily accessible means, then you must either (1) + cause the Corresponding Source to be so available, or (2) arrange to deprive + yourself of the benefit of the patent license for this particular work, or + (3) arrange, in a manner consistent with the requirements of this License, + to extend the patent license to downstream recipients. “Knowingly relying” + means you have actual knowledge that, but for the patent license, your + conveying the covered work in a country, or your recipient's use of the + covered work in a country, would infringe one or more identifiable patents + in that country that you have reason to believe are valid. +

    + +

    + If, pursuant to or in connection with a single transaction or arrangement, + you convey, or propagate by procuring conveyance of, a covered work, and + grant a patent license to some of the parties receiving the covered work + authorizing them to use, propagate, modify or convey a specific copy of the + covered work, then the patent license you grant is automatically extended to + all recipients of the covered work and works based on it. +

    + +

    + A patent license is “discriminatory” if it does not include within the scope + of its coverage, prohibits the exercise of, or is conditioned on the + non-exercise of one or more of the rights that are specifically granted + under this License. You may not convey a covered work if you are a party to + an arrangement with a third party that is in the business of distributing + software, under which you make payment to the third party based on the + extent of your activity of conveying the work, and under which the third + party grants, to any of the parties who would receive the covered work from + you, a discriminatory patent license (a) in connection with copies of the + covered work conveyed by you (or copies made from those copies), or (b) + primarily for and in connection with specific products or compilations that + contain the covered work, unless you entered into that arrangement, or that + patent license was granted, prior to 28 March 2007. +

    + +

    + Nothing in this License shall be construed as excluding or limiting any + implied license or other defenses to infringement that may otherwise be + available to you under applicable patent law. +

    + +

    12. No Surrender of Others' Freedom.

    + +

    + If conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot convey a + covered work so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you may + not convey it at all. For example, if you agree to terms that obligate you + to collect a royalty for further conveying from those to whom you convey the + Program, the only way you could satisfy both those terms and this License + would be to refrain entirely from conveying the Program. +

    + +

    13. Use with the GNU Affero General Public License.

    + +

    + Notwithstanding any other provision of this License, you have permission to + link or combine any covered work with a work licensed under version 3 of the + GNU Affero General Public License into a single combined work, and to convey + the resulting work. The terms of this License will continue to apply to the + part which is the covered work, but the special requirements of the GNU + Affero General Public License, section 13, concerning interaction through a + network will apply to the combination as such. +

    + +

    14. Revised Versions of this License.

    + +

    + The Free Software Foundation may publish revised and/or new versions of the + GNU General Public License from time to time. Such new versions will be + similar in spirit to the present version, but may differ in detail to + address new problems or concerns. +

    + +

    + Each version is given a distinguishing version number. If the Program + specifies that a certain numbered version of the GNU General Public License + “or any later version” applies to it, you have the option of following the + terms and conditions either of that numbered version or of any later version + published by the Free Software Foundation. If the Program does not specify a + version number of the GNU General Public License, you may choose any version + ever published by the Free Software Foundation. +

    + +

    + If the Program specifies that a proxy can decide which future versions of + the GNU General Public License can be used, that proxy's public statement of + acceptance of a version permanently authorizes you to choose that version + for the Program. +

    + +

    + Later license versions may give you additional or different permissions. + However, no additional obligations are imposed on any author or copyright + holder as a result of your choosing to follow a later version. +

    + +

    15. Disclaimer of Warranty.

    + +

    + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE + LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, + EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. + SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY + SERVICING, REPAIR OR CORRECTION. +

    + +

    16. Limitation of Liability.

    + +

    + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL + ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE + PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY + GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE + OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA + OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD + PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), + EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF + SUCH DAMAGES. +

    + +

    17. Interpretation of Sections 15 and 16.

    + +

    + If the disclaimer of warranty and limitation of liability provided above + cannot be given local legal effect according to their terms, reviewing + courts shall apply local law that most closely approximates an absolute + waiver of all civil liability in connection with the Program, unless a + warranty or assumption of liability accompanies a copy of the Program in + return for a fee. +

    + +

    END OF TERMS AND CONDITIONS

    + +

    How to Apply These Terms to Your New Programs

    + +

    + If you develop a new program, and you want it to be of the greatest possible + use to the public, the best way to achieve this is to make it free software + which everyone can redistribute and change under these terms. +

    + +

    + To do so, attach the following notices to the program. It is safest to + attach them to the start of each source file to most effectively state the + exclusion of warranty; and each file should have at least the “copyright” + line and a pointer to where the full notice is found. +

    + +
    +      
    +        <one line to give the program's name and a brief idea of what it does.>
    +        Copyright (C) <year>  <name of author>
    +
    +        This program is free software: you can redistribute it and/or modify
    +        it under the terms of the GNU General Public License as published by
    +        the Free Software Foundation, either version 3 of the License, or
    +        (at your option) any later version.
    +
    +        This program is distributed in the hope that it will be useful,
    +        but WITHOUT ANY WARRANTY; without even the implied warranty of
    +        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    +        GNU General Public License for more details.
    +
    +        You should have received a copy of the GNU General Public License
    +        along with this program.  If not, see .
    +      
    +    
    + +

    + Also add information on how to contact you by electronic and paper mail. +

    + +

    + If the program does terminal interaction, make it output a short notice like + this when it starts in an interactive mode: +

    + +
    +    
    +      <program>  Copyright (C) <year>  <name of author>
    +      This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    +      This is free software, and you are welcome to redistribute it
    +      under certain conditions; type `show c' for details.
    +    
    +  
    + +

    + The hypothetical commands `show w' and `show c' should show the appropriate + parts of the General Public License. Of course, your program's commands + might be different; for a GUI interface, you would use an “about box”. +

    + +

    + You should also get your employer (if you work as a programmer) or school, + if any, to sign a “copyright disclaimer” for the program, if necessary. For + more information on this, and how to apply and follow the GNU GPL, see + <https://www.gnu.org/licenses/>. +

    + +

    + The GNU General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you may + consider it more useful to permit linking proprietary applications with the + library. If this is what you want to do, use the GNU Lesser General Public + License instead of this License. But first, please read <https://www.gnu.org/licenses/why-not-lgpl.html>. +

    +
    diff --git a/app/views/licenses/_ru.html.erb b/app/views/licenses/_ru.html.erb new file mode 100644 index 0000000..ba91bd5 --- /dev/null +++ b/app/views/licenses/_ru.html.erb @@ -0,0 +1,910 @@ +
    + + +
    +

    GNU ОБЩАЯ ОБЩЕСТВЕННАЯ ЛИЦЕНЗИЯ

    +

    Версия 3, 29 июня 2007 года

    +
    + +

    + Авторское право © 2007 + Free Software Foundation, Inc. +

    + +

    + Каждому разрешено копировать и распространять данный документ лицензии в его + первоначальной форме, но изменение его запрещено. +

    + +

    Преамбула

    +

    + GNU Общая Общественная Лицензия — это бесплатная лицензия с обязательным + сохранением авторских прав для программного обеспечения и других видов + произведений. +

    + +

    + Большинство лицензий на программное обеспечение и другие практические работы + созданы так, чтобы ограничить вашу свободу распространять и изменять эти + произведения. В отличие от них, GNU Общая Общественная Лицензия + предназначена для гарантирования вашей свободы распространять и изменять все + версии программы — чтобы убедиться, что она остается свободным программным + обеспечением для всех ее пользователей. Мы, Фонд свободного программного + обеспечения, используем GNU Общую Общественную Лицензию для большинства + нашего программного обеспечения; она также применима к любому другому + произведению, выпущенному авторами таким образом. Вы можете также применять + ее к своим программам. +

    + +

    + Когда мы говорим о свободном программном обеспечении, мы имеем в виду + свободу, а не цену. Наши Общие Публичные Лицензии созданы для обеспечения + вашей свободы распространять копии свободного программного обеспечения (и + взимать плату за них, если вы желаете), получать исходный код или получать + его, если вы хотите, изменять программное обеспечение или использовать его + части в новых свободных программах, и знать, что вы можете делать все это. +

    + +

    + Чтобы защитить ваши права, мы должны предотвратить попытки других лиц + ограничить ваши права или требовать от вас отказа от них. Поэтому, если вы + распространяете копии программного обеспечения или модифицируете его, вы + несете определенные обязательства по уважению свободы других. +

    + +

    + Например, если вы распространяете копии такой программы, будь то бесплатно + или за плату, вы обязаны передавать получателям те же свободы, которые вы + получили. Вы должны убедиться, что они также получат исходный код или смогут + его получить. И вы должны показать им эти условия, чтобы они знали свои + права. +

    + +

    + Разработчики, использующие GNU GPL, защищают ваши права двумя шагами: (1) + утверждают авторские права на программное обеспечение и (2) предоставляют + вам эту Лицензию, предоставляя вам юридическое разрешение на копирование, + распространение и/или модификацию программы. +

    + +

    + В интересах как разработчиков, так и авторов, GPL четко утверждает, что нет + никаких гарантий для этого бесплатного программного обеспечения. Для + пользователей и авторов GPL требует, чтобы модифицированные версии были + помечены как измененные, чтобы их проблемы не ошибочно приписывались авторам + предыдущих версий. +

    + +

    + Некоторые устройства спроектированы так, чтобы лишать пользователей доступа + к установке или запуску измененных версий программного обеспечения внутри + них, хотя производитель может это делать. Это фундаментально несовместимо с + целью защиты свободы пользователей изменять программное обеспечение. + Систематический характер такого злоупотребления происходит в области + продуктов для индивидуального использования, что является абсолютно + неприемлемым. Поэтому мы разработали эту версию GPL, чтобы запретить такую + ​​практику для этих продуктов. Если подобные проблемы возникнут в + существенной степени в других областях, мы готовы расширить это положение на + будущие версии GPL, если это будет необходимо для защиты свободы + пользователей. +

    + +

    + Наконец, каждая программа постоянно находится под угрозой со стороны + программных патентов. Государства не должны допускать патентов, + ограничивающих разработку и использование программного обеспечения на + компьютерах общего назначения, но в тех, что это делают, мы хотели бы + избежать специфической угрозы, что патенты, применяемые к свободной + программе, могли бы сделать ее эффективно проприетарной. Для предотвращения + этого GPL обеспечивает, что патенты не могут использоваться для лишения + программу статуса свободного. +

    + +

    + Прецизионные условия для копирования, распространения и модификации + приведены далее. +

    + +

    УСЛОВИЯ И ПОЛОЖЕНИЯ

    +

    0. Определения.

    + +

    “Эта Лицензия” относится к версии 3 GNU General Public License.

    + +

    + “Авторское право” также означает законы, подобные авторскому праву, которые + применяются к другим видам произведений, таким как маски полупроводников. +

    + +

    + “Программа” относится к любому произведению, на которое распространяется + авторское право в соответствии с этой Лицензией. Каждый лицензиат + обозначается как “вы”. “Лицензиаты” и “получатели” могут быть как + физическими лицами, так и организациями. +

    + +

    + “Модифицировать” произведение означает копировать из него или адаптировать + его полностью или частично таким образом, что требуется разрешение на + авторское право, за исключением создания точной копии. Результат называется + “модифицированной версией” ранее созданного произведения или произведением, + “основанным на” ранее созданном произведении. +

    + +

    + “Охваченное произведение” означает либо неизмененную Программу, либо + произведение, основанное на Программе. +

    + +

    + “Распространять” произведение означает совершать действия, которые, без + разрешения, могут сделать вас непосредственно или вторично ответственными за + нарушение применимого законодательства об авторском праве, за исключением + выполнения его на компьютере или модификации частной копии. Распространение + включает в себя копирование, распространение (с или без модификации), + предоставление обществу и, в некоторых странах, другие виды деятельности. +

    + +

    + “Передача” произведения означает любой вид распространения, который + позволяет другим сторонам создавать или получать копии. Простое + взаимодействие с пользователем через компьютерную сеть, без передачи копии, + не считается передачей. +

    + +

    + Интерактивный пользовательский интерфейс отображает "Адекватные Юридические + Уведомления" в той степени, в какой он включает удобную и ярко видимую + функцию, которая (1) отображает соответствующее уведомление об авторском + праве и (2) сообщает пользователю, что нет гарантий для произведения (за + исключением предоставленных гарантий), что лицензиаты могут распространять + произведение в соответствии с этой Лицензией, и как просматривать копию этой + Лицензии. Если интерфейс предоставляет список пользовательских команд или + опций, такой как меню, выделенный элемент в списке соответствует этому + критерию. +

    + +

    1. Исходный код.

    + +

    + “Исходный код” для произведения означает предпочтительную форму произведения + для его модификации. “Объектный код” означает любую не-исходную форму + произведения. +

    + +

    + “Стандартный Интерфейс” означает интерфейс, который либо является + официальным стандартом, определенным признанным органом по стандартизации, + либо, в случае интерфейсов, указанных для определенного языка + программирования, такой интерфейс, который широко используется среди + разработчиков, работающих на этом языке. +

    + +

    + “Системные библиотеки” исполняемого произведения включают в себя всё, кроме + самого произведения в целом, что (а) включено в нормальную форму упаковки + Основной Компоненты, но не является её частью, и (б) служит только для + обеспечения использования произведения вместе с этой Основной Компонентой + или реализации Стандартного Интерфейса, для которого реализация доступна + общественности в виде исходного кода. Под “Основной Компонентой” в данном + контексте понимается основной существенный компонент (ядро, оконная система + и так далее) конкретной операционной системы (если таковая имеется), на + которой исполняется исполняемое произведение, либо компилятор, используемый + для создания произведения, либо интерпретатор объектного кода, используемый + для его выполнения. +

    + +

    + “Соответствующий Исходный Код” для произведения в виде объектного кода + означает всё исходное кодирование, необходимое для создания, установки и + (для исполняемого произведения) выполнения объектного кода, а также для его + модификации, включая сценарии для управления этими действиями. Однако это не + включает в себя Системные библиотеки произведения или универсальные + инструменты или общедоступные свободные программы, которые используются без + изменений при выполнении этих действий, но не являются частью произведения. + Например, в Соответствующий Исходный Код входят файлы определения + интерфейса, связанные с исходными файлами произведения, и исходный код общих + библиотек и динамически связанных подпрограмм, которые специально + разработаны для требования работы произведения с использованием таких + подпрограмм, например, с использованием интимной передачи данных или + управления потоком между этими подпрограммами и другими частями + произведения. +

    + +

    + В Соответствующий Исходный Код не входит ничто, что пользователи могут + автоматически восстанавливать из других частей Соответствующего Исходного + Кода. +

    + +

    + Соответствующий Исходный Код для произведения в виде исходного кода — это то + же самое произведение. +

    + +

    2. Основные Полномочия.

    + +

    + Все права, предоставленные на основании этой Лицензии, предоставляются на + срок авторского права на Программу и непререкаемы, при условии, что + соблюдаются указанные условия. Эта Лицензия явно подтверждает ваше + неограниченное право запуска неизмененной Программы. Вывод, полученный при + запуске охваченного произведения, охватывается только этой Лицензией, если + вывод, с учетом его содержания, является охваченным произведением. Эта + Лицензия признает ваши права на честное использование или другие + эквивалентные права, предоставленные законодательством об авторском праве. +

    + +

    + Вы можете создавать, запускать и распространять охваченные произведения, + которые вы не передаете другим, без дополнительных условий, пока ваша + лицензия в остальном остается в силе. Вы можете передавать охваченные + произведения другим исключительно с целью их модификации исключительно для + вас или предоставления вам средств для выполнения этих произведений, при + условии, что вы соблюдаете условия этой Лицензии, передавая все материалы, + за которыми вы не контролируете авторское право. Те, кто создают или + запускают охваченные произведения для вас, должны делать это исключительно + от вашего имени, под вашим руководством и контролем, на условиях, которые + запрещают им создавать копии вашего авторского материала вне их отношений с + вами. +

    + +

    + Передача в любых других обстоятельствах разрешается исключительно при + соблюдении условий, указанных ниже. Подраздел 10 делает её ненужной. +

    + +

    3. Защита Прав Пользователей От Законов Против Обхода Защиты.

    + +

    + Ни одно охваченное произведение не будет считаться частью эффективного + технологического средства согласно применимому закону, выполняющему + обязательства на основе статьи 11 Договора ВОИС по авторскому праву, + принятого 20 декабря 1996 года, или аналогичным законам, запрещающим или + ограничивающим обход таких средств. +

    + +

    + Передавая охваченное произведение, вы отказываетесь от какого-либо + юридического права запрещать обход технологических средств в той мере, в + которой такой обход осуществляется путем осуществления прав на основе этой + Лицензии в отношении охваченного произведения, и вы отказываетесь от + каких-либо намерений ограничивать выполнение или изменение произведения в + качестве средства принуждения к запрету обхода технологических средств + против пользователя произведения, ваших прав или прав третьих лиц. +

    + +

    4. Передача Нетронутых Копий.

    + +

    + Вы можете передавать нетронутые копии исходного кода Программы так, как вы + получили его, в любой среде, при условии, что вы надежно и соответственно + публикуете на каждой копии соответствующее авторское уведомление; сохраняете + все уведомления, указывающие, что данная Лицензия и любые непропускающие + условия, добавленные в соответствии с разделом 7, применимы к коду; + сохраняете все уведомления об отсутствии каких-либо гарантий; и + предоставляете всем получателям копию этой Лицензии вместе с Программой. +

    + +

    + Вы можете устанавливать любую цену или не устанавливать цену за каждую + передаваемую вами копию, и вы можете предоставлять поддержку или гарантийную + защиту за плату. +

    + +

    5. Передача Измененных Версий Исходного Кода.

    + +

    + Вы можете передавать произведение, созданное на основе Программы, или + модификации, произведенные для его создания из Программы, в виде исходного + кода в соответствии с условиями раздела 4, при условии, что вы также + соответствуете всем следующим условиям: +

    + +
      +
    1. + Произведение должно нести заметные уведомления о том, что вы внесли в него + изменения, и указывать соответствующую дату. +
    2. +
    3. + Произведение должно нести заметные уведомления о том, что оно выпущено в + соответствии с этой Лицензией и любыми условиями, добавленными в + соответствии с разделом 7. Это условие изменяет требование раздела 4 о + "сохранении всех уведомлений". +
    4. +
    5. + Вы должны лицензировать всю работу в целом согласно этой Лицензии для + любого лица, которое получит копию. Следовательно, эта Лицензия будет + применяться, вместе с любыми дополнительными условиями раздела 7, ко всей + работе, и ко всем ее частям, независимо от того, как они упакованы. Эта + Лицензия не дает разрешения лицензировать работу иным образом, но она не + аннулирует такое разрешение, если вы получили его отдельно. +
    6. +
    7. + Если в работе есть интерактивные пользовательские интерфейсы, каждый из + них должен отображать соответствующие юридические уведомления; однако если + Программа имеет интерактивные интерфейсы, которые не отображают + соответствующие юридические уведомления, вашей работе не обязательно + делать это. +
    8. +
    + +

    + Сборка охваченной работы с другими отдельными и независимыми работами, + которые по своей природе не являются расширениями охваченной работы и + которые не комбинируются с ней так, чтобы образовать более крупную + программу, внутри или на носителе хранения или распространения, называется + "агрегатом", если сборка и ее результативное авторское право не используются + для ограничения доступа или юридических прав пользователей сборки за + пределами того, что позволяют отдельные работы. Включение охваченной работы + в агрегат не приводит к применению этой Лицензии к другим частям агрегата. +

    + +

    6. Передача Неисходных Форм.

    + +

    + Вы можете передавать охваченную работу в виде объектного кода в соответствии + с условиями разделов 4 и 5, при условии, что вы также передаете + машиночитаемый Соответствующий Исходный Код в соответствии с условиями этой + Лицензии, одним из следующих способов: +

    + +
      +
    1. + Передавать объектный код в физическом продукте (включая физический + носитель распространения), вмещенный в него, с сопроводительным + Соответствующим Исходным Кодом, закрепленным на долгосрочном физическом + носителе, обычно используемом для обмена программным обеспечением. +
    2. +
    3. + Передавать объектный код в физическом продукте (включая физический + носитель распространения), с сопроводительным письменным предложением, + действительным не менее трех лет и действительным так долго, как вы + предоставляете запасные части или поддержку клиентов для модели этого + продукта, давать любому, кто имеет объектный код, либо (1) копию + Соответствующего Исходного Кода для всего программного обеспечения в + продукте, охваченного этой Лицензией, на долгосрочном физическом носителе, + обычно используемом для обмена программным обеспечением, за цену, не + превышающую разумные расходы на физическое выполнение этой передачи + исходника, или (2) доступ к копированию Соответствующего Исходного Кода с + сервера сети бесплатно. +
    4. +
    5. + Передавать отдельные копии объектного кода с копией письменного + предложения предоставить Соответствующий Исходный Код. Этот вариант + разрешается только случайно и без коммерческой цели, и только если вы + получили объектный код с таким предложением, согласно пункту 6b. +
    6. +
    7. + Передавать объектный код, предоставляя доступ из указанного места + (бесплатно или за плату), и предоставлять эквивалентный доступ к + Соответствующему Исходному Коду таким же образом через то же место без + дополнительной оплаты. Вы не обязаны требовать от получателей копировать + Соответствующий Исходный Код вместе с объектным кодом. Если местом для + копирования объектного кода является сервер сети, Соответствующий Исходный + Код может находиться на другом сервере (управляемом вами или третьей + стороной), который поддерживает эквивалентные средства копирования, при + условии, что вы предоставляете четкие указания рядом с объектным кодом о + том, где найти Соответствующий Исходный Код. Независимо от того, какой + сервер хостит Соответствующий Исходный Код, вы обязаны обеспечивать его + доступность так долго, как это необходимо для выполнения этих требований. +
    8. +
    9. + Передавать объектный код с использованием передачи "peer-to-peer", + предоставляя информацию другим "peer-узлам" о том, где объектный код и + Соответствующий Исходный Код работы предлагаются широкой публике бесплатно + в соответствии с пунктом 6d. +
    10. +
    + +

    + Отдельная часть объектного кода, исходный код которой исключен из + Соответствующего Исходного Кода как Системная Библиотека, не должна + включаться в передачу объектного кода работы. +

    + +

    + «Пользовательский продукт» - это либо (1) «потребительский продукт», что + означает любое материальное личное имущество, которое обычно используется + для личных, семейных или домашних нужд, либо (2) что-либо, предназначенное + или продаваемое для внедрения в жилое помещение. При определении, является + ли продукт потребительским, сомнительные случаи разрешаются в пользу + применения. Для конкретного продукта, полученного конкретным пользователем, + "обычно используется" относится к типичному или общему использованию этого + класса продуктов, независимо от статуса конкретного пользователя или от + способа, которым конкретный пользователь фактически использует, ожидает + использовать или ожидает, что будет использовать продукт. Продукт считается + потребительским, независимо от того, имеет ли продукт существенное + коммерческое, промышленное или не потребительское использование, если такие + использования представляют собой единственный значительный способ + использования продукта. +

    + +

    + «Информация о установке» для Пользовательского Продукта означает любые + методы, процедуры, авторизационные ключи или другую информацию, необходимую + для установки и выполнения модифицированных версий охваченной работы в этом + Пользовательском Продукте из модифицированной версии ее Соответствующего + Исходного Кода. Информация должна быть достаточной для обеспечения того, + чтобы продолжительная работа модифицированного объектного кода в ни в коем + случае не была предотвращена или помешана исключительно из-за модификации. +

    + +

    + Если вы передаете объектный код работы в соответствии с этим разделом в, или + с, или специально для использования в Пользовательском Продукте, и передача + происходит в рамках сделки, в которой право владения и использования + Пользовательского Продукта передается получателю бессрочно или на + фиксированный срок (независимо от характеризации сделки), передаваемый в + соответствии с этим разделом Соответствующий Исходный Код должен + сопровождаться Информацией об Установке. Но это требование не применяется, + если ни вы, ни какая-либо третья сторона не сохраняет возможности установки + модифицированного объектного кода на Пользовательском Продукте (например, + работа была установлена в ПЗУ). +

    + +

    + Требование предоставления Информации о Установке не включает требование + предоставления услуги поддержки, гарантии или обновлений для работы, которая + была изменена или установлена получателем, или для Пользовательского + Продукта, в который она была изменена или установлена. Доступ к сети может + быть ограничен, когда сама модификация существенно и негативно влияет на + работу сети или нарушает правила и протоколы для обмена данными в сети. +

    + +

    + Соответствующий Исходный Код, переданный, и Информация об Установке, + предоставленная в соответствии с этим разделом, должны быть в формате, + который общедоступно документирован (и с реализацией, доступной + общественности в исходном коде), и не должны требовать специального пароля + или ключа для распаковки, чтения или копирования. +

    + +

    7. Дополнительные условия.

    + +

    + «Дополнительные разрешения» - это условия, дополняющие условия этой + Лицензии, предоставляя исключения из одного или нескольких ее условий. + Дополнительные разрешения, которые применимы ко всей Программе, должны + рассматриваться так, как если бы они были включены в эту Лицензию, в той + степени, в которой они действительны в соответствии с применимым + законодательством. Если дополнительные разрешения применяются только к части + Программы, то эта часть может использоваться отдельно в соответствии с этими + разрешениями, но вся Программа остается под управлением этой Лицензии + независимо от дополнительных разрешений. +

    + +

    + При передаче копии охваченной работы вы можете по своему усмотрению удалить + любые дополнительные разрешения из этой копии или из ее части. + (Дополнительные разрешения могут быть написаны так, чтобы их можно было + удалить в определенных случаях при модификации работы.) Вы можете добавлять + дополнительные разрешения к материалам, добавленным вами к охваченной + работе, для которых у вас есть или можете предоставить соответствующее + авторское разрешение. +

    + +

    + Независимо от любых других положений этой Лицензии, для материала, который + вы добавляете к охваченной работе, вы можете (если это разрешено + правообладателями этого материала) дополнять условия этой Лицензии такими + условиями: +

    + +
      +
    1. + Отказ от гарантии или ограничение ответственности по-другому, чем в + разделах 15 и 16 этой Лицензии; или +
    2. +
    3. + Требование сохранения указанных разумных юридических уведомлений или + авторских указаний в этом материале или в соответствующих юридических + уведомлениях, отображаемых в работах, содержащих его; или +
    4. +
    5. + Запрет на искажение происхождения этого материала или требование, чтобы + модифицированные версии такого материала были помечены разумным образом + как отличающиеся от оригинальной версии; или +
    6. +
    7. + Ограничение использования в рекламных целях наименований правообладателей + или авторов этого материала; или +
    8. +
    9. + Отказ от предоставления прав на использование названий товаров, товарных + знаков или знаков обслуживания в соответствии с законодательством о + товарных знаках для использования некоторых торговых наименований, + товарных знаков или знаков обслуживания; или +
    10. +
    11. + Требование предоставления компенсации правообладателям и авторам этого + материала от лиц, передающих материал (или модифицированные версии его) с + контрактными предположениями о возмещении ущерба получателю за любую + ответственность, непосредственно возлагаемую на этих правообладателей и + авторов на основании этих контрактных предположений. +
    12. +
    + +

    + Все остальные непермиссивные дополнительные условия считаются + «дополнительными ограничениями» в смысле раздела 10. Если Программа, которую + вы получили, или ее какая-либо часть содержит уведомление о том, что она + управляется этой Лицензией вместе с условием, которое является + дополнительным ограничением, вы можете удалить это условие. Если + лицензионный документ содержит дополнительное ограничение, но разрешает + лицензирование или передачу в соответствии с этой Лицензией, вы можете + добавить к охваченной работе материал, управляемый условиями этого + лицензионного документа, при условии, что дополнительное ограничение не + сохраняется после такого лицензирования или передачи. +

    + +

    + Если вы добавляете условия к охваченной работе в соответствии с этим + разделом, вы должны разместить в соответствующих исходных файлах заявление о + дополнительных условиях, применяемых к этим файлам, или уведомление о том, + где можно найти соответствующие условия. +

    + +

    + Дополнительные условия, пермиссивные или непермиссивные, могут быть + представлены в виде отдельной письменной лицензии или быть указанными как + исключения; вышеперечисленные требования применяются в любом случае. +

    + +

    8. Прекращение.

    + +

    + Вы не можете распространять или модифицировать охваченную работу, за + исключением случаев, когда это явно предусмотрено в этой Лицензии. Любая + попытка распространения или модификации, предпринятая иначе, будет + недействительной и автоматически приведет к прекращению ваших прав в + соответствии с этой Лицензией (включая любые лицензии на патенты, + предоставленные в соответствии с третьим абзацем раздела 11). +

    + +

    + Однако, если вы прекратите все нарушения этой Лицензии, то ваша лицензия от + определенного правообладателя восстанавливается (a) временно, если только и + до тех пор, пока правообладатель авторских прав не явно и окончательно не + прекратит вашу лицензию, и (b) постоянно, если правообладатель авторских + прав не уведомит вас о нарушении неким разумным способом до 60 дней после + прекращения. +

    + +

    + Более того, ваша лицензия от определенного правообладателя восстанавливается + постоянно, если правообладатель авторских прав уведомит вас о нарушении + неким разумным способом, если это первый раз, когда вы получили уведомление + о нарушении этой Лицензии (для какой-либо работы) от этого правообладателя, + и вы устраните нарушение в течение 30 дней с момента получения уведомления. +

    + +

    + Прекращение ваших прав в соответствии с этим разделом не прекращает лицензии + сторон, которые получили копии или права на эту работу от вас в соответствии + с этой Лицензией. Если ваши права были прекращены и не были постоянно + восстановлены, вы не имеете права на получение новых лицензий на тот же + материал в соответствии с разделом 10. +

    + +

    9. Не требуется согласие для наличия копий.

    + +

    + Для получения или запуска копии Программы вы не обязаны принимать эту + Лицензию. Побочное распространение охваченной работы, происходящее + исключительно как следствие использования передачи от одного пользователя к + другому, также не требует принятия. Однако ничто, кроме этой Лицензии, не + дает вам разрешения на распространение или модификацию любой охваченной + работы. Эти действия нарушают авторские права, если вы не примете эту + Лицензию. Таким образом, модифицируя или распространяя охваченную работу, вы + выражаете свое согласие на это в соответствии с этой Лицензией. +

    + +

    10. Автоматическое предоставление лицензий для конечных получателей.

    + +

    + Каждый раз, когда вы передаете охваченную работу, получатель автоматически + получает лицензию от оригинальных лицензиаров на запуск, модификацию и + распространение этой работы в соответствии с этой Лицензией. Вы не несете + ответственности за обеспечение соблюдения третьими лицами этой Лицензии. +

    + +

    + Сущностью сделки является сделка, переводящая контроль над организацией, или + практически всеми активами одной организации, или разделяющая организацию, + или объединяющая организации. Если распространение охваченной работы + является следствием сделки с сущностью, то каждая сторона этой сделки, + получившая копию работы, также получает все лицензии на работу, которые + имела или могла предоставить предшественник стороны в интересах, в + соответствии с предыдущим абзацем, плюс право на получение + Корреспондирующего Исходного кода работы от предшественника в интересах, + если предшественник имеет его или может получить его с разумными усилиями. +

    + +

    + Вы не можете налагать дополнительные ограничения на осуществление прав, + предоставленных или подтвержденных этой Лицензией. Например, вы не можете + взимать лицензионный сбор, роялти или иные платежи за осуществление прав, + предоставленных этой Лицензией, и вы не можете начинать судебные + разбирательства (включая поперечный иск или ответный иск в судебном + процессе), в которых утверждается нарушение патентных претензий путем + создания, использования, продажи, предложения к продаже или импорта + Программы или ее части. +

    + +

    11. Патенты.

    + +

    + “Сторонний автор” - это правообладатель авторских прав, который разрешает + использование Программы или работы, на основе которой создана Программа, в + соответствии с этой Лицензией. Таким образом лицензируемая таким образом + работа называется “вкладом стороннего автора”. +

    + +

    + “Существенные патентные требования стороннего автора” - это все патентные + требования, принадлежащие или контролируемые сторонним автором, будь то уже + приобретенные или будущие, которые были бы нарушены каким-либо образом, + разрешенным этой Лицензией, при создании, использовании или продаже его + вклада стороннего автора, но не включают требования, которые были бы + нарушены только в результате дальнейшей модификации вклада стороннего + автора. Для целей данного определения “контроль” включает в себя право + предоставления сублицензий на патент таким образом, чтобы это + соответствовало требованиям этой Лицензии. +

    + +

    + Каждый сторонний автор предоставляет вам неисключительную, мировую, + безвозмездную лицензию на патент на существенные патентные требования + стороннего автора для создания, использования, продажи, предложения к + продаже, импорта и в противном случае запуска, модификации и распространения + содержания своего вклада стороннего автора. +

    + +

    + В следующих трех абзацах “патентная лицензия” - это любое явное соглашение + или обязательство, как бы оно ни было названо, о неприменении патента + (например, явное разрешение на использование патента или гарантия не + предъявлять иски о нарушении патента). Предоставить такую патентную лицензию + сторонней стороне означает заключить такое соглашение или обязательство о + неприменении патента против этой стороны. +

    + +

    + Если вы передаете охваченную работу, обладая сведениями о существующей + патентной лицензии, и Корреспондирующий исходный код работы недоступен для + копирования любым лицам бесплатно и в соответствии с условиями этой Лицензии + через общедоступный сетевой сервер или другой легко доступный способ, то вы + должны либо (1) сделать Корреспондирующий Исходный код доступным, либо (2) + устроить так, чтобы вы лишались выгоды от патентной лицензии для этой + конкретной работы, либо (3) устроить так, чтобы продлить патентную лицензию + до получателей вниз по цепочке, соблюдая при этом требования этой Лицензии. + “Сведения о существующей патентной лицензии” означает, что у вас есть + реальные знания о том, что, несмотря на патентную лицензию, передача + охваченной работы в какой-либо стране или использование получателем + охваченной работы в какой-либо стране нарушает один или несколько + идентифицируемых вами патентов, которые, как вы полагаете, являются + действительными. +

    + +

    + Если, в соответствии с единичной сделкой или договоренностью, вы передаете + или распространяете, путем заключения сделки о передаче, охваченную работу и + предоставляете патентную лицензию некоторым сторонам, получающим охваченную + работу и авторизующим их использовать, распространять, модифицировать или + передавать конкретную копию охваченной работы, то патентная лицензия, + которую вы предоставляете, автоматически распространяется на всех + получателей охваченной работы и работ, основанных на ней. +

    + +

    + Патентная лицензия считается “дискриминационной”, если она не включает в + себя, запрещает осуществление или зависит от невыполнения одного или + нескольких из прав, специально предоставленных этой Лицензией. Вы не можете + передавать охваченную работу, если вы являетесь стороной в соглашении с + третьей стороной, которая занимается распространением программного + обеспечения и в рамках которого вы выплачиваете третьей стороне плату в + зависимости от объема ваших действий по передаче работы, и в рамках которого + третья сторона предоставляет какой-либо дискриминационную патентную лицензию + (а) в связи с копиями охваченной работы, переданными вами (или копиями, + созданными из этих копий), или (б) в основном для и в связи с конкретными + продуктами или компиляциями, содержащими охваченную работу, если вы вошли в + это соглашение, или эта патентная лицензия была предоставлена до 28 марта + 2007 года. +

    + +

    + Ничто в этой Лицензии не должно толковаться как исключение или ограничение + любой неявной лицензии или иных защитных мер от нарушения, которые в + противном случае могли бы быть доступны вам в соответствии с применимым + патентным правом. +

    + +

    12. Отказ от ограничения свободы других.

    + +

    + Если на вас налагаются условия (будь то по решению суда, соглашению или + как-либо иначе), которые противоречат условиям данной Лицензии, это не + освобождает вас от условий данной Лицензии. Если вы не можете передать + охваченную работу так, чтобы одновременно удовлетворить ваши обязательства + по данной Лицензии и любые другие соответствующие обязательства, то в + результате вы не можете ее передать вообще. Например, если вы соглашаетесь + на условия, которые обязывают вас собирать роялти за дальнейшее + распространение от тех, кому вы передаете Программу, единственным способом + удовлетворения как тех, так и других условий было бы воздержание полностью + от передачи Программы. +

    + +

    13. Использование с GNU Affero General Public License.

    + +

    + Несмотря на любое другое положение данной Лицензии, у вас есть разрешение + связывать или объединять любую охваченную работу с работой, лицензированной + в соответствии с версией 3 GNU Affero General Public License, в одну + объединенную работу и передавать получившуюся работу. Условия данной + Лицензии будут продолжать применяться к части, которая является охваченной + работой, но специальные требования GNU Affero General Public License, раздел + 13, касающиеся взаимодействия через сеть, будут применяться к самому + объединению. +

    + +

    14. Пересмотренные версии данной Лицензии.

    + +

    + Фонд свободного программного обеспечения может время от времени публиковать + пересмотренные и/или новые версии GNU General Public License. Такие новые + версии будут аналогичны по духу настоящей версии, но могут отличаться в + деталях для решения новых проблем или забот. +

    + +

    + Каждой версии присваивается уникальный номер версии. Если Программа + указывает, что к ней применяется определенная версия GNU General Public + License "или любая более поздняя версия", у вас есть возможность следовать + условиям и положениям как этой определенной версии, так и любой более + поздней версии, опубликованной Фондом свободного программного обеспечения. + Если Программа не указывает номер версии GNU General Public License, вы + можете выбрать любую версию, когда-либо опубликованную Фондом свободного + программного обеспечения. +

    + +

    + Если Программа указывает, что прокси-сервер может решать, какие будущие + версии GNU General Public License можно использовать, публичное заявление + этого прокси навсегда авторизует вас выбирать эту версию для Программы. +

    + +

    + Более поздние версии лицензии могут предоставлять вам дополнительные или + другие разрешения. Тем не менее, выбор последующей версии не влечет за собой + наложение дополнительных обязательств на любого автора или правообладателя. +

    + +

    15. Отказ от гарантии.

    + +

    + В ПРОГРАММЕ НЕТ ГАРАНТИЙ, НАСКОЛЬКО ЭТО ПРЕДУСМАТРИВАЕТСЯ ПРИМЕНИМЫМ ПРАВОМ. + ЗА ИСКЛЮЧЕНИЕМ СЛУЧАЕВ, КОГДА ИНАЧЕ ЗАЯВЛЕНО ПИСЬМЕННО, ПРАВООБЛАДАТЕЛИ + И/ИЛИ ДРУГИЕ СТОРОНЫ ПРЕДОСТАВЛЯЮТ ПРОГРАММУ "КАК ЕСТЬ" БЕЗ КАКИХ-ЛИБО + ГАРАНТИЙ, ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО, НЕ ОГРАНИЧИВАЯСЬ, + ПОДРАЗУМЕВАЕМЫМИ ГАРАНТИЯМИ КОММЕРЧЕСКОЙ ПРИГОДНОСТИ И ПРИГОДНОСТИ ДЛЯ + ОПРЕДЕЛЕННОЙ ЦЕЛИ. ВСЯ ОТВЕТСТВЕННОСТЬ ЗА КАЧЕСТВО И РАБОТОСПОСОБНОСТЬ + ПРОГРАММЫ НАСТУПАЕТ НА ВАС. В СЛУЧАЕ НЕИСПРАВНОСТИ ПРОГРАММЫ ВЫ БЕРЕТЕ НА + СЕБЯ ВСЕ НЕОБХОДИМЫЕ ЗАТРАТЫ НА ОБСЛУЖИВАНИЕ, РЕМОНТ ИЛИ ИСПРАВЛЕНИЕ. +

    + +

    16. Ограничение ответственности.

    + +

    + В НИКАКОМ СЛУЧАЕ, ЕСЛИ ЭТО НЕ ТРЕБУЕТСЯ ЗАКОНОМ, ИЛИ НЕ БЫЛО ДОГОВОРЕННО В + ПИСЬМЕ, НИ КАКОЙ ПРАВООБЛАДАТЕЛЬ, ИЛИ ЛЮБАЯ ДРУГАЯ СТОРОНА, МОДИФИЦИРУЮЩАЯ + И/ИЛИ ПЕРЕДАЮЩАЯ ПРОГРАММУ, КАК ЭТО РАЗРЕШЕНО ВЫШЕ, НЕ ПОНЕСЕТ + ОТВЕТСТВЕННОСТИ ПЕРЕД ВАМИ ЗА УЩЕРБ, ВКЛЮЧАЯ ЛЮБЫЕ ОБЩИЕ, СПЕЦИАЛЬНЫЕ, + СЛУЧАЙНЫЕ ИЛИ КОСВЕННЫЕ УБЫТКИ, ВОЗНИКШИЕ В РЕЗУЛЬТАТЕ ИСПОЛЬЗОВАНИЯ ИЛИ + НЕВОЗМОЖНОСТИ ИСПОЛЬЗОВАНИЯ ПРОГРАММЫ (ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ, ПОТЕРЕЙ + ДАННЫХ ИЛИ НЕТОЧНОСТЬ ДАННЫХ ИЛИ УБЫТКОВ, ПОНЕСЕННЫХ ВАМИ ИЛИ ТРЕТЬИМИ + СТОРОНАМИ ИЛИ НЕСПОСОБНОСТИ ПРОГРАММЫ ВЗАИМОДЕЙСТВОВАТЬ С ДРУГИМИ + ПРОГРАММАМИ), ДАЖЕ ЕСЛИ ЭТА СТОРОНА ИЛИ ДРУГАЯ СТОРОНА БЫЛА ПОПРЕДУ + ПРЕДУПРЕЖДЕНА О ВОЗМОЖНОСТИ ТАКИХ УБЫТКОВ. +

    + +

    17. Толкование разделов 15 и 16.

    + +

    + Если отказ от гарантии и ограничение ответственности, указанные выше, не + могут быть предоставлены местным законом в соответствии с их условиями, суды + рассматривающие дело должны применять местное право, которое наиболее близко + соответствует абсолютному отказу от любой гражданской ответственности в + связи с Программой, за исключением случаев, когда гарантия или + предполагаемая ответственность сопровождают копию Программы в обмен на + плату. +

    + +

    КОНЕЦ УСЛОВИЙ И ПОЛОЖЕНИЙ

    + +

    Как применить эти условия к вашим новым программам

    + +

    + Если вы разрабатываете новую программу и хотите, чтобы она была максимально + полезной для общества, лучший способ достичь этого - сделать ее свободным + программным обеспечением, которое каждый может распространять и изменять в + соответствии с этими условиями. +

    + +

    + Для этого прикрепите следующие уведомления к программе. Наиболее безопасно + прикреплять их в начало каждого исходного файла, чтобы наиболее эффективно + указать на отсутствие гарантии; и каждый файл должен содержать как минимум + строку "авторских прав" и ссылку на то, где можно найти полное уведомление. +

    + +
    +  
    +    <одной строкой укажите имя программы и краткую идею ее назначения.>
    +    Авторские права (C) <year> <имя автора>
    +
    +    Эта программа является свободным программным обеспечением: вы можете распространять ее и/или изменять
    +    ее в соответствии с условиями GNU General Public License, опубликованной
    +    Free Software Foundation, либо версией 3 этой лицензии, либо
    +    (на ваш выбор) любой последующей версией.
    +
    +    Эта программа распространяется с надеждой на то, что она будет полезной,
    +    но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже БЕЗ ПОДРАЗУМЕВАЕМЫХ ГАРАНТИЙ
    +    КОММЕРЧЕСКОЙ ПРИГОДНОСТИ ИЛИ ПРИГОДНОСТИ ДЛЯ КОНКРЕТНОЙ ЦЕЛИ.
    +    Дополнительные сведения см. в GNU General Public License.
    +
    +    Вы должны были получить копию GNU General Public License
    +    вместе с этой программой. Если нет, см. .
    +  
    +
    + +

    + Также добавьте информацию о том, как с вами можно связаться по электронной и + почтовой почте. +

    + +

    + Если программа взаимодействует с терминалом, убедитесь, что она выводит + краткое уведомление, похожее на следующее, при запуске в интерактивном + режиме: +

    + +
    +  
    +    <программа>  Авторские права (C) <год>  <имя автора>
    +    Эта программа поставляется БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; подробности можно узнать, введя `show w'.
    +    Это свободное программное обеспечение, и вы можете распространять его
    +    при соблюдении некоторых условий; подробности см. введите `show c'.
    +  
    +
    + +

    + Гипотетические команды `show w' и `show c' должны показать соответствующие + части GNU General Public License. Конечно, команды вашей программы могут + быть другими; для графического интерфейса вы бы использовали "окно с + информацией". +

    + +

    + Также убедитесь, что ваш работодатель (если вы работаете программистом) или + ваша школа, если таковая имеется, подписала "отказ от авторских прав" на + программу, если это необходимо. Дополнительные сведения об этом и о том, как + применять и соблюдать GNU GPL, см. по адресу <https://www.gnu.org/licenses/>. +

    + +

    + GNU General Public License не разрешает включение вашей программы в + собственные программы. Если ваша программа является библиотекой подпрограмм, + вы можете считать более полезным разрешить связывание собственных приложений + с библиотекой. Если это то, что вы хотите сделать, используйте GNU Lesser + General Public License вместо этой лицензии. Но сначала прочтите <https://www.gnu.org/licenses/why-not-lgpl.html>. +

    +
    diff --git a/app/views/licenses/show.html.erb b/app/views/licenses/show.html.erb new file mode 100644 index 0000000..5e95f07 --- /dev/null +++ b/app/views/licenses/show.html.erb @@ -0,0 +1,24 @@ + +<% provide :page_title, "Лицензия" %> + +
    +
    +
    + <%= link_to :back, class: "btn btn-light" do %> + <%= t("b_back") %> + <% end %> +
    +
    + <%= link_to "Руc", license_path(locale: 'ru'), class: "btn btn-light mx-2" %> + <%= link_to "Eng", license_path(locale: 'en'), class: "btn btn-light" %> +
    +
    + + <% if @locale == 'ru' %> + <%= render "ru" %> + <% elsif @locale == 'en' %> + <%= render "en" %> + <% else %> + + <% end %> +
    diff --git a/app/views/shared/_about.html.erb b/app/views/shared/_about.html.erb new file mode 100644 index 0000000..83f5c4f --- /dev/null +++ b/app/views/shared/_about.html.erb @@ -0,0 +1,42 @@ +
    +

    + ARMStrong (Automated Radiation Monitoring System) — платформа, объединяющая в себе: +

      +
    • + Автоматизированную систему радиационного контроля, (далее СРК) включающую в себя комплекс средств, призванных осуществлять радиационный контроль, в целях снижения травматизма, предупреждения предаварийных ситуация и недопущения развития аварийных сценариев; +
    • +
    • + Систему менеджмента средствами измерения (далее СИ), которая позволяет осуществлять управление информацией о СИ, собирать отчетность и + осуществлять проведение процедуры метрологических испытаний и поверки; +
    • +
    • + Информационный портал для персонала. +
    • +
    +

    +

    + Вся платформа и её компоненты распространяеются по лицензии <%= link_to "GNU GPLv3", license_path(:ru) %>, являеются open-source продуктом и разрабатывается силами сообщества. +

    +

    + Наш репозиторий digital-armstrong +

    +

    Разработчики:

    + +
    diff --git a/config/routes.rb b/config/routes.rb index 3beea2e..adfe030 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,6 +4,7 @@ root 'post#index' post 'device/download', to: 'device#download' + get '/licenses/:locale', to: 'licenses#show', as: 'license' namespace :api do namespace :v1, defaults: {format: 'json'} do @@ -27,7 +28,6 @@ resources :home resources :armstrong, only: [:index, :show] - resources :about, only: [:index] resources :device do post :create_inspection, :to => 'device#create_inspection' end diff --git a/test/controllers/about_controller_test.rb b/test/controllers/about_controller_test.rb deleted file mode 100644 index 3e4b2a0..0000000 --- a/test/controllers/about_controller_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'test_helper' - -class AboutControllerTest < ActionDispatch::IntegrationTest - # test 'should get index' do - # get :index - # assert_response :success - # end -end diff --git a/test/controllers/licenses_controller_test.rb b/test/controllers/licenses_controller_test.rb new file mode 100644 index 0000000..ea2b1c9 --- /dev/null +++ b/test/controllers/licenses_controller_test.rb @@ -0,0 +1,4 @@ +require "test_helper" + +class LicensesControllerTest < ActionController::TestCase +end From 8ed57580846baf1ab07c7ff0281694b9bf753c4b Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 21 Sep 2023 09:02:03 +0400 Subject: [PATCH 047/132] Added new api controller for Histories --- .../api/v1/histories_controller.rb | 20 +++++++++++++++++++ config/routes.rb | 1 + 2 files changed, 21 insertions(+) create mode 100644 app/controllers/api/v1/histories_controller.rb diff --git a/app/controllers/api/v1/histories_controller.rb b/app/controllers/api/v1/histories_controller.rb new file mode 100644 index 0000000..37d6ba6 --- /dev/null +++ b/app/controllers/api/v1/histories_controller.rb @@ -0,0 +1,20 @@ +module Api + class V1::HistoriesController < ApplicationController + before_action :set_channel, only: [:show] + + def show + histories = History.select { |hs| hs.channel_id == @channel_id } + + result = histories.sort { |a, b| a[:event_datetime] <=> b[:event_datetime] } + + render(json: result) + end + + private + + def set_channel + @channel = Channel.find(params[:id]) + @channel_id = @channel.id + end + end +end diff --git a/config/routes.rb b/config/routes.rb index adfe030..52a8c47 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,6 +9,7 @@ namespace :api do namespace :v1, defaults: {format: 'json'} do resources :filters, :armstrong, only: :index + resources :histories, only: :show end end From 8ffaecfacf2201f7bb5403a3102e5602bfebef79 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 17 Jan 2024 11:51:41 +0400 Subject: [PATCH 048/132] =?UTF-8?q?UPD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B7=D0=B0=D0=B3=D0=BB=D1=83=D1=88?= =?UTF-8?q?=D0=BA=D1=83=20=D0=BF=D0=BE=D0=B4=20=D0=B2=D1=8B=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=20=D1=81=D1=83=D1=82=D0=BE=D1=87=D0=BD=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B0,=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BD=D0=B5=20=D0=B4=D0=BE=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B0=D0=BD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 +- .../api/v1/armstrong_controller.rb | 8 + .../Components/Armstrong/Armstrong.jsx | 35 +- .../Components/Armstrong/Chart/Chart.jsx | 53 --- .../Armstrong/Chart/ChartComponent.jsx | 116 +++++++ .../Components/Armstrong/Modal/Modal.jsx | 84 ----- .../Armstrong/Modal/ModalComponent.jsx | 43 +++ .../Components/Armstrong/Table/TableBody.jsx | 9 +- app/javascript/Services/ApiHelper.js | 6 + bin/prod | 8 + docker-compose.yml | 4 +- package.json | 6 +- yarn.lock | 306 +++++++++++++++++- 13 files changed, 509 insertions(+), 173 deletions(-) delete mode 100644 app/javascript/Components/Armstrong/Chart/Chart.jsx create mode 100644 app/javascript/Components/Armstrong/Chart/ChartComponent.jsx delete mode 100644 app/javascript/Components/Armstrong/Modal/Modal.jsx create mode 100644 app/javascript/Components/Armstrong/Modal/ModalComponent.jsx create mode 100644 app/javascript/Services/ApiHelper.js create mode 100644 bin/prod diff --git a/Dockerfile b/Dockerfile index ddf2713..570028b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,8 @@ RUN gem install bundler:2.4.5 RUN mkdir $RAILS_ROOT WORKDIR $RAILS_ROOT +RUN rm -f tmp/pids/server.pid + COPY Gemfile Gemfile.lock ./ RUN bundle install --jobs $(nproc) @@ -26,4 +28,4 @@ ADD . $RAILS_ROOT ENV PATH=$RAILS_ROOT/bin:${PATH} EXPOSE 3000 -CMD bundle exec rails s -b '0.0.0.0' -p 3000 +CMD ["bin/prod"] diff --git a/app/controllers/api/v1/armstrong_controller.rb b/app/controllers/api/v1/armstrong_controller.rb index 00e4e10..e0dcc39 100644 --- a/app/controllers/api/v1/armstrong_controller.rb +++ b/app/controllers/api/v1/armstrong_controller.rb @@ -1,5 +1,7 @@ module Api class V1::ArmstrongController < ApplicationController + after_action :set_csp_header + def index channels = Channel.all result = channels.sort { |a, b| a[:server_id] <=> b[:server_id] } @@ -10,5 +12,11 @@ def index room: { only: [:name] }, ]) end + + private + + def set_csp_header + response.headers['Content-Security-Policy'] = "default-src 'self' http://0.0.0.0/api/v1/armstrong;" + end end end diff --git a/app/javascript/Components/Armstrong/Armstrong.jsx b/app/javascript/Components/Armstrong/Armstrong.jsx index 897c375..846eb02 100644 --- a/app/javascript/Components/Armstrong/Armstrong.jsx +++ b/app/javascript/Components/Armstrong/Armstrong.jsx @@ -5,30 +5,21 @@ import 'moment-timezone'; import { sortBy } from 'lodash'; import Table from './Table/Table'; import Filter from './Filter/Filter'; -import Modal from './Modal/Modal'; +import ModalComponent from './Modal/ModalComponent'; export default function Armstrong() { const [data, setData] = useState([]); const [filter, setFilter] = useState(''); const [timeZone, setTimeZone] = useState(''); - const [isModalOpen, setModalOpen] = useState(false); - const [isChartOpen, setChartOpen] = useState(false); - const [selectedChannelId, setSelectedChannelId] = useState(0); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedId, setSelectedId] = useState(0); + const [selectedPointName, setSelectedPointName] = useState(''); const openModal = (event) => { - const channelId = event.target.dataset.channelid; - setSelectedChannelId(channelId); - setModalOpen(true); + setSelectedId(event.currentTarget.dataset.id); + setSelectedPointName(event.currentTarget.dataset.pointname); + setIsModalOpen(true); }; - const closeModal = () => { - setModalOpen(false); - setChartOpen(true); - }; - - const hindleConfirm = () => { - setChartOpen(true); - }; - const closeChart = () => setChartOpen(false); const setChannelColor = (state) => { switch (state) { @@ -106,9 +97,15 @@ export default function Armstrong() {
    - - {isModalOpen && setModalOpen(false)} onConfirm={hindleConfirm} />} - {isChartOpen && setChartOpen(false)} />} +
    + {isModalOpen && ( + setIsModalOpen(false)} + /> + )} ); diff --git a/app/javascript/Components/Armstrong/Chart/Chart.jsx b/app/javascript/Components/Armstrong/Chart/Chart.jsx deleted file mode 100644 index e199ac3..0000000 --- a/app/javascript/Components/Armstrong/Chart/Chart.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -export default function Chart({ closeModal, openChart }) { - return ( -
    -
    -
    -
    -
    -
    -

    Select date interval:

    -
    -
    - -
    -
    -
    -
    -
    - ); -} - -Chart.propTypes = { - closeModal: PropTypes.func.isRequired, - openChart: PropTypes.func.isRequired, -}; diff --git a/app/javascript/Components/Armstrong/Chart/ChartComponent.jsx b/app/javascript/Components/Armstrong/Chart/ChartComponent.jsx new file mode 100644 index 0000000..1a6cd74 --- /dev/null +++ b/app/javascript/Components/Armstrong/Chart/ChartComponent.jsx @@ -0,0 +1,116 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ReactEChartsCore from 'echarts-for-react/lib/core'; +import * as echarts from 'echarts/core'; +import { LineChart } from 'echarts/charts'; +import { GridComponent, TooltipComponent, TitleComponent, ToolboxComponent, DataZoomComponent } from 'echarts/components'; +import { CanvasRenderer } from 'echarts/renderers'; +import 'bootstrap/dist/css/bootstrap.min.css'; + +export default function ChartComponent({ chartData, pointName }) { + echarts.use([ + TitleComponent, + TooltipComponent, + ToolboxComponent, + DataZoomComponent, + GridComponent, + LineChart, + CanvasRenderer, + ]); + + const dataoptions = { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + }; + + const options = { + title: { + left: 'center', + text: pointName, + }, + tooltip: { + trigger: 'axis', + position(pt) { + return [pt[10], '10%']; + }, + }, + xAxis: { + type: 'category', + data: chartData.map((data) => new Date(data.source_time).toLocaleDateString('en-GB', dataoptions)), + }, + yAxis: { + type: 'value', + }, + series: [ + { + name: pointName, + type: 'line', + smooth: true, + sampling: 'lttb', + itemStyle: { + color: 'rgb(255, 70, 131)', + }, + areaStyle: { + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: 'rgb(255, 158, 68)', + }, + { + offset: 1, + color: 'rgb(255, 70, 131)', + }, + ]), + }, + data: chartData.map((data) => data.value), + }, + ], + toolbox: { + feature: { + dataZoom: { + yAxisIndex: 'none', + }, + restore: {}, + saveAsImage: {}, + }, + }, + dataZoom: [ + { + type: 'inside', + start: 70, + end: 100, + }, + { + start: 70, + end: 100, + }, + ], + }; + + return ( +
    + +
    + ); +} + +ChartComponent.propTypes = { + chartData: PropTypes.arrayOf( + PropTypes.shape({ + key1: PropTypes.Date, + key2: PropTypes.number, + }), + ).isRequired, + pointName: PropTypes.string.isRequired, +}; diff --git a/app/javascript/Components/Armstrong/Modal/Modal.jsx b/app/javascript/Components/Armstrong/Modal/Modal.jsx deleted file mode 100644 index be39fcd..0000000 --- a/app/javascript/Components/Armstrong/Modal/Modal.jsx +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; - -export default function Modal({ closeModal, onConfirm }) { - const [firstDateTime, setFirstDateTime] = useState(''); - const [secondDateTime, setSecondDateTime] = useState(''); - - const handleChangeFirstDatetime = (event) => { - setFirstDateTime(event.target.value); - }; - - const handleChangeSecondDatetime = (event) => { - setSecondDateTime(event.target.value); - }; - - const handleConfirm = () => { - onConfirm(); - }; - - return ( -
    -
    -
    -
    -
    -
    -

    Select date interval:

    -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    - -
    -
    -
    -
    - ); -} - -Modal.propTypes = { - closeModal: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, -}; diff --git a/app/javascript/Components/Armstrong/Modal/ModalComponent.jsx b/app/javascript/Components/Armstrong/Modal/ModalComponent.jsx new file mode 100644 index 0000000..081b48c --- /dev/null +++ b/app/javascript/Components/Armstrong/Modal/ModalComponent.jsx @@ -0,0 +1,43 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { Modal } from 'react-bootstrap'; +import ChartComponent from '../Chart/ChartComponent'; +import ApiHelper from '../../../Services/ApiHelper'; + +export default function ModalComponent({ selectedId, show, handleClose }) { + const [chartData, setChartData] = useState([]); + + useEffect(() => { + ApiHelper(`api/v1/armstrong?history_selected_id=${selectedId}`).then((result) => { + setChartData(result); + }); + }, [selectedId]); + + return ( + // Заглушка + + + График за сутки + + +

    Заглушка под компонент графика. График пока еще не готов.

    +
    +
    + // Заглушка + + // + // + // График за сутки + // + // + // + // + // + ); +} + +ModalComponent.propTypes = { + selectedId: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + handleClose: PropTypes.func.isRequired, +}; diff --git a/app/javascript/Components/Armstrong/Table/TableBody.jsx b/app/javascript/Components/Armstrong/Table/TableBody.jsx index 9b89576..06aca78 100644 --- a/app/javascript/Components/Armstrong/Table/TableBody.jsx +++ b/app/javascript/Components/Armstrong/Table/TableBody.jsx @@ -1,5 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { Button } from 'react-bootstrap'; +import * as Icon from 'react-bootstrap-icons'; +import 'bootstrap/dist/css/bootstrap.min.css'; export default function TableBody({ columns, data, openModal }) { return ( @@ -10,9 +13,9 @@ export default function TableBody({ columns, data, openModal }) { let tData = row[accessor] ? row[accessor] : '——'; if (accessor === 'chart') { tData = ( - + ); } return
    ; diff --git a/app/javascript/Services/ApiHelper.js b/app/javascript/Services/ApiHelper.js new file mode 100644 index 0000000..e6e963b --- /dev/null +++ b/app/javascript/Services/ApiHelper.js @@ -0,0 +1,6 @@ +import ky from 'ky'; + +export default async function ApiHelper(route) { + const data = ky.get(route).json(); + return data; +} diff --git a/bin/prod b/bin/prod new file mode 100644 index 0000000..2054419 --- /dev/null +++ b/bin/prod @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +export RAILS_ENV=production +bundle install +yarn install +yarn build +yarn build:css +bin/rails assets:precompile +bin/rails server -b 0.0.0.0 diff --git a/docker-compose.yml b/docker-compose.yml index 7b12235..ec16da9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,9 +9,7 @@ services: - ~/.bash_history:/root/.bash_history - &bundle-cache-volume bundle_cache:/bundle_cache ports: - - 3000:3000 - - 3001:3001 - - 3002:3002 + - 80:3000 depends_on: - db - redis diff --git a/package.json b/package.json index 5b97749..d836324 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,10 @@ "@popperjs/core": "^2.11.6", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", - "bootstrap": "^5.2.3", + "bootstrap": "^5.3.2", "bootstrap-icons": "^1.10.3", + "echarts": "^5.4.3", + "echarts-for-react": "^3.0.2", "esbuild": "^0.17.10", "eslint": "^8.40.0", "eslint-config-airbnb": "^19.0.4", @@ -25,6 +27,8 @@ "moment-timezone": "^0.5.43", "prettier": "^2.8.8", "react": "^18.2.0", + "react-bootstrap": "^2.9.2", + "react-bootstrap-icons": "^1.10.3", "react-dom": "^18.2.0", "sass": "^1.58.3", "typescript": "^4.9.5" diff --git a/yarn.lock b/yarn.lock index 19c0b17..5cf6607 100644 --- a/yarn.lock +++ b/yarn.lock @@ -317,6 +317,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.7": + version: 7.23.8 + resolution: "@babel/runtime@npm:7.23.8" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 0bd5543c26811153822a9f382fd39886f66825ff2a397a19008011376533747cd05c33a91f6248c0b8b0edf0448d7c167ebfba34786088f1b7eb11c65be7dfc3 + languageName: node + linkType: hard + "@babel/template@npm:^7.20.7": version: 7.20.7 resolution: "@babel/template@npm:7.20.7" @@ -728,6 +737,57 @@ __metadata: languageName: node linkType: hard +"@react-aria/ssr@npm:^3.5.0": + version: 3.9.1 + resolution: "@react-aria/ssr@npm:3.9.1" + dependencies: + "@swc/helpers": ^0.5.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 23bd76a7963af31169396bc821eac2e58fbe1dbb81af3e5d54ec76667a8ec616b2a79e15aaa49b2270f0c5b35a6b9e46fdda20056ee0f4fda84ebf9433665355 + languageName: node + linkType: hard + +"@restart/hooks@npm:^0.4.9": + version: 0.4.15 + resolution: "@restart/hooks@npm:0.4.15" + dependencies: + dequal: ^2.0.3 + peerDependencies: + react: ">=16.8.0" + checksum: 26787aa7e824999921d8a33e5969137cf0473c6d30f938566fa2497d1135ac9410ea88303bc6082726981561aa439e388d1d72e4c3cd846847e2efb483e9500b + languageName: node + linkType: hard + +"@restart/ui@npm:^1.6.6": + version: 1.6.6 + resolution: "@restart/ui@npm:1.6.6" + dependencies: + "@babel/runtime": ^7.21.0 + "@popperjs/core": ^2.11.6 + "@react-aria/ssr": ^3.5.0 + "@restart/hooks": ^0.4.9 + "@types/warning": ^3.0.0 + dequal: ^2.0.3 + dom-helpers: ^5.2.0 + uncontrollable: ^8.0.1 + warning: ^4.0.3 + peerDependencies: + react: ">=16.14.0" + react-dom: ">=16.14.0" + checksum: c2d1b56a0a6c3afadd98f1adf3cf16897b86752464d1f17f81f3611c99b2aada0c6701944807bcc997f1095dd2c3f4dac8b83ef515bac560b40a8060663871cf + languageName: node + linkType: hard + +"@swc/helpers@npm:^0.5.0": + version: 0.5.3 + resolution: "@swc/helpers@npm:0.5.3" + dependencies: + tslib: ^2.4.0 + checksum: 61c3f7ccd47fc70ad91437df88be6b458cdc11e311cb331288827d7c50befffc72aa18fe913ec2a9e70fbf44e4b818bed38bfd7c329d689e1ff3c198d084cd02 + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -758,6 +818,15 @@ __metadata: languageName: node linkType: hard +"@types/react-transition-group@npm:^4.4.6": + version: 4.4.10 + resolution: "@types/react-transition-group@npm:4.4.10" + dependencies: + "@types/react": "*" + checksum: fe2ea11f70251e9f79f368e198c18fd469b1d4f1e1d44e4365845b44e15974b0ec925100036f449b023b0ca3480a82725c5f0a73040e282ad32ec7b0def9b57c + languageName: node + linkType: hard + "@types/react@npm:*, @types/react@npm:^18.0.28": version: 18.2.6 resolution: "@types/react@npm:18.2.6" @@ -769,6 +838,17 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:>=16.9.11": + version: 18.2.48 + resolution: "@types/react@npm:18.2.48" + dependencies: + "@types/prop-types": "*" + "@types/scheduler": "*" + csstype: ^3.0.2 + checksum: c9ca43ed2995389b7e09492c24e6f911a8439bb8276dd17cc66a2fbebbf0b42daf7b2ad177043256533607c2ca644d7d928fdfce37a67af1f8646d2bac988900 + languageName: node + linkType: hard + "@types/scheduler@npm:*": version: 0.16.3 resolution: "@types/scheduler@npm:0.16.3" @@ -776,6 +856,13 @@ __metadata: languageName: node linkType: hard +"@types/warning@npm:^3.0.0": + version: 3.0.3 + resolution: "@types/warning@npm:3.0.3" + checksum: 862b71c918283d2ace5cab4e9f0167507a15ee9cf4d46035c858bdd4bf1ee83cbfb42bcfd4da6e7e254a2efa32200b6521f3719c729e39e88e336309d53bb4c4 + languageName: node + linkType: hard + "abbrev@npm:^1.0.0": version: 1.1.1 resolution: "abbrev@npm:1.1.1" @@ -890,8 +977,10 @@ __metadata: "@popperjs/core": ^2.11.6 "@types/react": ^18.0.28 "@types/react-dom": ^18.0.11 - bootstrap: ^5.2.3 + bootstrap: ^5.3.2 bootstrap-icons: ^1.10.3 + echarts: ^5.4.3 + echarts-for-react: ^3.0.2 esbuild: ^0.17.10 eslint: ^8.40.0 eslint-config-airbnb: ^19.0.4 @@ -909,6 +998,8 @@ __metadata: moment-timezone: ^0.5.43 prettier: ^2.8.8 react: ^18.2.0 + react-bootstrap: ^2.9.2 + react-bootstrap-icons: ^1.10.3 react-dom: ^18.2.0 sass: ^1.58.3 typescript: ^4.9.5 @@ -1059,12 +1150,12 @@ __metadata: languageName: node linkType: hard -"bootstrap@npm:^5.2.3": - version: 5.2.3 - resolution: "bootstrap@npm:5.2.3" +"bootstrap@npm:^5.3.2": + version: 5.3.2 + resolution: "bootstrap@npm:5.3.2" peerDependencies: - "@popperjs/core": ^2.11.6 - checksum: 0211805dec6a190c0911d142966df30fdb4b4139a04cc6c23dd83c6045ea3cb0a966b360ab2e701e7b3ad96ff01e05fdc0914be97b41bd876b11e457a8bdc6a3 + "@popperjs/core": ^2.11.8 + checksum: d5580b253d121ffc137388d41da58dce8d15f1ccd574e12f28d4a08e7649ca15e95db645b2b677cb8025bccd446bff04138fc0fe64f8cba0ccc5dc004a8644cf languageName: node linkType: hard @@ -1207,6 +1298,13 @@ __metadata: languageName: node linkType: hard +"classnames@npm:^2.3.2": + version: 2.5.1 + resolution: "classnames@npm:2.5.1" + checksum: da424a8a6f3a96a2e87d01a432ba19315503294ac7e025f9fece656db6b6a0f7b5003bb1fbb51cbb0d9624d964f1b9bb35a51c73af9b2434c7b292c42231c1e5 + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -1386,6 +1484,13 @@ __metadata: languageName: node linkType: hard +"dequal@npm:^2.0.3": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90 + languageName: node + linkType: hard + "doctrine@npm:^2.1.0": version: 2.1.0 resolution: "doctrine@npm:2.1.0" @@ -1404,6 +1509,39 @@ __metadata: languageName: node linkType: hard +"dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.2.0, dom-helpers@npm:^5.2.1": + version: 5.2.1 + resolution: "dom-helpers@npm:5.2.1" + dependencies: + "@babel/runtime": ^7.8.7 + csstype: ^3.0.2 + checksum: 863ba9e086f7093df3376b43e74ce4422571d404fc9828bf2c56140963d5edf0e56160f9b2f3bb61b282c07f8fc8134f023c98fd684bddcb12daf7b0f14d951c + languageName: node + linkType: hard + +"echarts-for-react@npm:^3.0.2": + version: 3.0.2 + resolution: "echarts-for-react@npm:3.0.2" + dependencies: + fast-deep-equal: ^3.1.3 + size-sensor: ^1.0.1 + peerDependencies: + echarts: ^3.0.0 || ^4.0.0 || ^5.0.0 + react: ^15.0.0 || >=16.0.0 + checksum: d3b16325befb1294d99f6f089462415be739c1654370945eef2172efd5868596f10e4cd021e0ff65b89a6f9de5e9c331ccf3765d9167ccb12d573f9632b5b7a6 + languageName: node + linkType: hard + +"echarts@npm:^5.4.3": + version: 5.4.3 + resolution: "echarts@npm:5.4.3" + dependencies: + tslib: 2.3.0 + zrender: 5.4.4 + checksum: f4f69becf1cf8f546f9488ffa3bffaa971dcfbd49f5d635f288cbc8c5177839154bd6c325d6ed72c2b822c89c9bba4947ac73400614fd23c6f2f7ace3c939132 + languageName: node + linkType: hard + "electron-to-chromium@npm:^1.4.284": version: 1.4.401 resolution: "electron-to-chromium@npm:1.4.401" @@ -2428,6 +2566,15 @@ __metadata: languageName: node linkType: hard +"invariant@npm:^2.2.4": + version: 2.2.4 + resolution: "invariant@npm:2.2.4" + dependencies: + loose-envify: ^1.0.0 + checksum: cc3182d793aad82a8d1f0af697b462939cb46066ec48bbf1707c150ad5fad6406137e91a262022c269702e01621f35ef60269f6c0d7fd178487959809acdfb14 + languageName: node + linkType: hard + "ip@npm:^2.0.0": version: 2.0.0 resolution: "ip@npm:2.0.0" @@ -2849,7 +2996,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -3350,7 +3497,19 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.8.1": +"prop-types-extra@npm:^1.1.0": + version: 1.1.1 + resolution: "prop-types-extra@npm:1.1.1" + dependencies: + react-is: ^16.3.2 + warning: ^4.0.0 + peerDependencies: + react: ">=0.14.0" + checksum: ebf1c048687bb538457f91a3610abb36ca0f50587a6afae80443a9e65b9db96882d18c3511175a8967fad4ca5dcd804913bbc241d7b5160c74cf69aacdd054f0 + languageName: node + linkType: hard + +"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -3375,6 +3534,44 @@ __metadata: languageName: node linkType: hard +"react-bootstrap-icons@npm:^1.10.3": + version: 1.10.3 + resolution: "react-bootstrap-icons@npm:1.10.3" + dependencies: + prop-types: ^15.7.2 + peerDependencies: + react: ">=16.8.6" + checksum: afc9a4a558217c9e4abf77734d902fa658be38f61eb99a0c6dd574eaf8289517ff17fe1e2cd62735901975a8da8f2dc363d3f964a322377726b4d825a09febfa + languageName: node + linkType: hard + +"react-bootstrap@npm:^2.9.2": + version: 2.9.2 + resolution: "react-bootstrap@npm:2.9.2" + dependencies: + "@babel/runtime": ^7.22.5 + "@restart/hooks": ^0.4.9 + "@restart/ui": ^1.6.6 + "@types/react-transition-group": ^4.4.6 + classnames: ^2.3.2 + dom-helpers: ^5.2.1 + invariant: ^2.2.4 + prop-types: ^15.8.1 + prop-types-extra: ^1.1.0 + react-transition-group: ^4.4.5 + uncontrollable: ^7.2.1 + warning: ^4.0.3 + peerDependencies: + "@types/react": ">=16.14.8" + react: ">=16.14.0" + react-dom: ">=16.14.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: ffa8e0d9e4a6414cbcbefb657f0f427302ab0c059ec618a86fffeccfe669dd3b3d9003f9e9803dc4499d4831aba44aaafd29e9c70bf850138ce5a3e4091b8fb3 + languageName: node + linkType: hard + "react-dom@npm:^18.2.0": version: 18.2.0 resolution: "react-dom@npm:18.2.0" @@ -3387,13 +3584,35 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1": +"react-is@npm:^16.13.1, react-is@npm:^16.3.2": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f languageName: node linkType: hard +"react-lifecycles-compat@npm:^3.0.4": + version: 3.0.4 + resolution: "react-lifecycles-compat@npm:3.0.4" + checksum: a904b0fc0a8eeb15a148c9feb7bc17cec7ef96e71188280061fc340043fd6d8ee3ff233381f0e8f95c1cf926210b2c4a31f38182c8f35ac55057e453d6df204f + languageName: node + linkType: hard + +"react-transition-group@npm:^4.4.5": + version: 4.4.5 + resolution: "react-transition-group@npm:4.4.5" + dependencies: + "@babel/runtime": ^7.5.5 + dom-helpers: ^5.0.1 + loose-envify: ^1.4.0 + prop-types: ^15.6.2 + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" + checksum: 75602840106aa9c6545149d6d7ae1502fb7b7abadcce70a6954c4b64a438ff1cd16fc77a0a1e5197cdd72da398f39eb929ea06f9005c45b132ed34e056ebdeb1 + languageName: node + linkType: hard + "react@npm:^18.2.0": version: 18.2.0 resolution: "react@npm:18.2.0" @@ -3430,6 +3649,13 @@ __metadata: languageName: node linkType: hard +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 9f57c93277b5585d3c83b0cf76be47b473ae8c6d9142a46ce8b0291a04bb2cf902059f0f8445dcabb3fb7378e5fe4bb4ea1e008876343d42e46d3b484534ce38 + languageName: node + linkType: hard + "regexp.prototype.flags@npm:^1.4.3, regexp.prototype.flags@npm:^1.5.0": version: 1.5.0 resolution: "regexp.prototype.flags@npm:1.5.0" @@ -3642,6 +3868,13 @@ __metadata: languageName: node linkType: hard +"size-sensor@npm:^1.0.1": + version: 1.0.2 + resolution: "size-sensor@npm:1.0.2" + checksum: de7050178ae9afee3388eb9191af0902b30ef83c26e8c9d9c203e1b560e270b947d978e4f56d211802112d09ef296931fa612f69155a483900f3b4717a0750d7 + languageName: node + linkType: hard + "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -3861,6 +4094,20 @@ __metadata: languageName: node linkType: hard +"tslib@npm:2.3.0": + version: 2.3.0 + resolution: "tslib@npm:2.3.0" + checksum: 8869694c26e4a7b56d449662fd54a4f9ba872c889d991202c74462bd99f10e61d5bd63199566c4284c0f742277736292a969642cc7b590f98727a7cae9529122 + languageName: node + linkType: hard + +"tslib@npm:^2.4.0": + version: 2.6.2 + resolution: "tslib@npm:2.6.2" + checksum: 329ea56123005922f39642318e3d1f0f8265d1e7fcb92c633e0809521da75eeaca28d2cf96d7248229deb40e5c19adf408259f4b9640afd20d13aecc1430f3ad + languageName: node + linkType: hard + "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0" @@ -3920,6 +4167,29 @@ __metadata: languageName: node linkType: hard +"uncontrollable@npm:^7.2.1": + version: 7.2.1 + resolution: "uncontrollable@npm:7.2.1" + dependencies: + "@babel/runtime": ^7.6.3 + "@types/react": ">=16.9.11" + invariant: ^2.2.4 + react-lifecycles-compat: ^3.0.4 + peerDependencies: + react: ">=15.0.0" + checksum: 3345c0c1916193ddb9cc6f2b78711dc9f22b919d780485e15b95690722e9d1797fc702c4ebb30c0acaae6a772b865d0a9ddc83fa1da44958f089aee78f2f5eab + languageName: node + linkType: hard + +"uncontrollable@npm:^8.0.1": + version: 8.0.4 + resolution: "uncontrollable@npm:8.0.4" + peerDependencies: + react: ">=16.14.0" + checksum: b685af148e29372ac336c95a7562094c5375d14807b3bc85861708363cb64b29a487f55f9d6eafda8003f21f1ca61c9cce8c83bcea5d94a0ba32bca519ac3be7 + languageName: node + linkType: hard + "unique-filename@npm:^2.0.0": version: 2.0.1 resolution: "unique-filename@npm:2.0.1" @@ -3968,6 +4238,15 @@ __metadata: languageName: node linkType: hard +"warning@npm:^4.0.0, warning@npm:^4.0.3": + version: 4.0.3 + resolution: "warning@npm:4.0.3" + dependencies: + loose-envify: ^1.0.0 + checksum: 4f2cb6a9575e4faf71ddad9ad1ae7a00d0a75d24521c193fa464f30e6b04027bd97aa5d9546b0e13d3a150ab402eda216d59c1d0f2d6ca60124d96cd40dfa35c + languageName: node + linkType: hard + "which-boxed-primitive@npm:^1.0.2": version: 1.0.2 resolution: "which-boxed-primitive@npm:1.0.2" @@ -4061,3 +4340,12 @@ __metadata: checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard + +"zrender@npm:5.4.4": + version: 5.4.4 + resolution: "zrender@npm:5.4.4" + dependencies: + tslib: 2.3.0 + checksum: 4b317346af8eca38e62ba029239c3a13e97eac4fa15b3ddadbae23442d8b373f0e937c255dee8080d6bb2fc79c9da54f1106415586ed8942bd8bc684b3890ea9 + languageName: node + linkType: hard From 36511b36434b021239b50582c8b11a8fdd7d950b Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 17 Jan 2024 14:04:12 +0400 Subject: [PATCH 049/132] =?UTF-8?q?UPD:=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80?= =?UTF-8?q?=D0=B0=D1=89=D0=B0=D0=B5=D1=82=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5?= =?UTF-8?q?=D0=B4=D0=BD=D0=B8=D0=B5=20100=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B5=D0=B9=20=D0=B2=20=D0=B8=D1=81=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BB=D1=8F=20{id}=20=D0=BA=D0=B0=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/histories_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/histories_controller.rb b/app/controllers/api/v1/histories_controller.rb index 37d6ba6..df8d719 100644 --- a/app/controllers/api/v1/histories_controller.rb +++ b/app/controllers/api/v1/histories_controller.rb @@ -3,7 +3,7 @@ class V1::HistoriesController < ApplicationController before_action :set_channel, only: [:show] def show - histories = History.select { |hs| hs.channel_id == @channel_id } + histories = History.select { |hs| hs.channel_id == @channel_id }.last(100) result = histories.sort { |a, b| a[:event_datetime] <=> b[:event_datetime] } From e1d8cb4705a0b9deee02bc23e36f1d081ad9a764 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 17 Jan 2024 14:04:55 +0400 Subject: [PATCH 050/132] =?UTF-8?q?UPD:=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B0=D0=B5=D1=82=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=82=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Armstrong/Chart/ChartComponent.jsx | 13 ++++++++---- .../Armstrong/Modal/ModalComponent.jsx | 20 +++++-------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/app/javascript/Components/Armstrong/Chart/ChartComponent.jsx b/app/javascript/Components/Armstrong/Chart/ChartComponent.jsx index 1a6cd74..90dedae 100644 --- a/app/javascript/Components/Armstrong/Chart/ChartComponent.jsx +++ b/app/javascript/Components/Armstrong/Chart/ChartComponent.jsx @@ -40,7 +40,7 @@ export default function ChartComponent({ chartData, pointName }) { }, xAxis: { type: 'category', - data: chartData.map((data) => new Date(data.source_time).toLocaleDateString('en-GB', dataoptions)), + data: chartData.map((data) => new Date(data.event_datetime).toLocaleDateString('en-GB', dataoptions)), }, yAxis: { type: 'value', @@ -66,7 +66,7 @@ export default function ChartComponent({ chartData, pointName }) { }, ]), }, - data: chartData.map((data) => data.value), + data: chartData.map((data) => data.event_system_value), }, ], toolbox: { @@ -108,8 +108,13 @@ export default function ChartComponent({ chartData, pointName }) { ChartComponent.propTypes = { chartData: PropTypes.arrayOf( PropTypes.shape({ - key1: PropTypes.Date, - key2: PropTypes.number, + channel_id: PropTypes.number, + event_impulse_value: PropTypes.number, + event_system_value: PropTypes.number, + event_not_system_value: PropTypes.number, + event_datetime: PropTypes.string, + created_at: PropTypes.string, + updated_at: PropTypes.string, }), ).isRequired, pointName: PropTypes.string.isRequired, diff --git a/app/javascript/Components/Armstrong/Modal/ModalComponent.jsx b/app/javascript/Components/Armstrong/Modal/ModalComponent.jsx index 081b48c..583e8fc 100644 --- a/app/javascript/Components/Armstrong/Modal/ModalComponent.jsx +++ b/app/javascript/Components/Armstrong/Modal/ModalComponent.jsx @@ -4,40 +4,30 @@ import { Modal } from 'react-bootstrap'; import ChartComponent from '../Chart/ChartComponent'; import ApiHelper from '../../../Services/ApiHelper'; -export default function ModalComponent({ selectedId, show, handleClose }) { +export default function ModalComponent({ selectedId, show, pointName, handleClose }) { const [chartData, setChartData] = useState([]); useEffect(() => { - ApiHelper(`api/v1/armstrong?history_selected_id=${selectedId}`).then((result) => { + ApiHelper(`http://0.0.0.0/api/v1/histories/${selectedId}`).then((result) => { setChartData(result); }); }, [selectedId]); return ( - // Заглушка - График за сутки + График за сутки для {pointName} -

    Заглушка под компонент графика. График пока еще не готов.

    +
    - // Заглушка - - // - // - // График за сутки - // - // - // - // - // ); } ModalComponent.propTypes = { selectedId: PropTypes.string.isRequired, show: PropTypes.bool.isRequired, + pointName: PropTypes.string.isRequired, handleClose: PropTypes.func.isRequired, }; From 16bd55dc8dad6fb54464db79a0c7a65f6a9df5ae Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 17 Jan 2024 14:45:41 +0400 Subject: [PATCH 051/132] =?UTF-8?q?FIX:=20=D0=98=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=20=D1=82=D0=B5=D0=BA=D1=81=D1=82=D0=B5,=20?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=84=D0=BE=D1=80=D0=BC=D1=83?= =?UTF-8?q?=D0=BB=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B8=20=D0=B8=20=D0=B4?= =?UTF-8?q?=D1=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/shared/_about.html.erb | 4 ++-- app/views/shared/index/_inspection.html.erb | 10 +++++----- config/locales/devise.en.yml | 2 +- config/locales/devise.ru.yml | 4 ++-- config/locales/ru.yml | 9 +++++---- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/app/views/shared/_about.html.erb b/app/views/shared/_about.html.erb index 83f5c4f..c6efe8a 100644 --- a/app/views/shared/_about.html.erb +++ b/app/views/shared/_about.html.erb @@ -6,7 +6,7 @@ Автоматизированную систему радиационного контроля, (далее СРК) включающую в себя комплекс средств, призванных осуществлять радиационный контроль, в целях снижения травматизма, предупреждения предаварийных ситуация и недопущения развития аварийных сценариев;
  • - Систему менеджмента средствами измерения (далее СИ), которая позволяет осуществлять управление информацией о СИ, собирать отчетность и + Систему менеджмента средств измерения (далее СИ), которая позволяет осуществлять управление информацией о СИ, собирать отчетность и осуществлять проведение процедуры метрологических испытаний и поверки;
  • @@ -15,7 +15,7 @@

    - Вся платформа и её компоненты распространяеются по лицензии <%= link_to "GNU GPLv3", license_path(:ru) %>, являеются open-source продуктом и разрабатывается силами сообщества. + Вся платформа и её компоненты распространяется по лицензии <%= link_to "GNU GPLv3", license_path(:ru) %>, являеются open-source продуктом и разрабатывается силами сообщества.

    Наш репозиторий digital-armstrong diff --git a/app/views/shared/index/_inspection.html.erb b/app/views/shared/index/_inspection.html.erb index 53843d6..5b6932c 100644 --- a/app/views/shared/index/_inspection.html.erb +++ b/app/views/shared/index/_inspection.html.erb @@ -113,20 +113,20 @@ <% unless path == all_tasks_inspection_index_path %> <% case inspection.state %> <% when "task_created"%> -

    +
    <%= button_to accept_task_inspection_path(inspection), method: :post, class: 'btn p-0' do %> <% end%>
    <% when "task_accepted"%> -
    +
    <%= button_to complete_verification_inspection_path(inspection), method: :post, class: 'btn p-0' do %> <% end %>
    -
    +
    <%= button_to fail_verification_inspection_path(inspection), method: :post, class: 'btn p-0' do %> @@ -153,7 +153,7 @@ <% end %>
    <% when "returned_from_repair"%> -
    +
    <%= button_to send_from_repair_to_verification_inspection_path(inspection), method: :post, class: 'btn p-0' do %> @@ -208,4 +208,4 @@ select.value = "" }) } - \ No newline at end of file + diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index d58b3f4..dc25be5 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -14,7 +14,7 @@ en: last_attempt: "You have one more attempt before your account is locked." not_found_in_database: "Invalid %{authentication_keys} or password." timeout: "Your session expired. Please sign in again to continue." - unauthenticated: "You need to sign in or sign up before continuing." + unauthenticated: "Sign in or sign up." unconfirmed: "You have to confirm your email address before continuing." mailer: confirmation_instructions: diff --git a/config/locales/devise.ru.yml b/config/locales/devise.ru.yml index 56a54bf..441d060 100644 --- a/config/locales/devise.ru.yml +++ b/config/locales/devise.ru.yml @@ -12,8 +12,8 @@ ru: last_attempt: "У Вас осталась еще одна попытка ввести пароль до блокировки." not_found_in_database: "Неверный %{authentication_keys} или пароль." timeout: "Ваш сеанс закончился. Пожалуйста, войдите в систему снова." - unauthenticated: "Вам необходимо войти в систему или зарегистрироваться." - unconfirmed: "Вы должны подтвердить вашу учётную запись." + unauthenticated: "Войдите или зарегистрируйтесь." + unconfirmed: "Подтвердите вашу учётную запись." mailer: confirmation_instructions: subject: "Инструкции по подтверждению учётной записи." diff --git a/config/locales/ru.yml b/config/locales/ru.yml index aec4cc1..a3922da 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -212,6 +212,7 @@ ru: verified: Поверен prepare_to_inspection: Подготовить expired: Просрочен + sent_to_omit: Отправлен в ЦСМИТ service_id: Служба device_model: name: Название @@ -289,13 +290,13 @@ ru: conclusion: Заключение conclusion_date: Дата заключения type_target: Цель - verification_successful: Проверка пройдена + verification_successful: Поверка выполнена task_created: Задача создана - task_accepted: Задача принята - verification_failed: Проверка провалена + task_accepted: Задача в работе + verification_failed: Поверка не выполнена sent_to_repair: Отправлено на ремонт returned_from_repair: Возвращено с ремонта - closed: Закрыто + closed: Поверка прервана incoming: Вх. контроль repair: Ремонт regular: Поверка From 6dec1348526ee2b5c47aeb1a8b23a340b6482770 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 18 Jan 2024 10:17:28 +0400 Subject: [PATCH 052/132] =?UTF-8?q?FIX:=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=20=D1=82=D0=B5=D0=BA=D1=81=D1=82=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=B1=20?= =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B5=20403?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/en.yml | 4 ++-- config/locales/ru.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index e26bd05..ca0602f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -165,5 +165,5 @@ en: updated_at: Updated at action: Actions filtration: Filtration - access_denied: Access Denied - access_denied_message: Sorry, but you don't have permission to access this page. + access_denied: "403: Access Denied" + access_denied_message: You don't have permission to access this page. diff --git a/config/locales/ru.yml b/config/locales/ru.yml index a3922da..caff3f5 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -434,5 +434,5 @@ ru: updated_at: Обновлен action: Действия filtration: Фильтрация - access_denied: Доступ запрещен - access_denied_message: Извините, но у вас нет прав для просмотра данной страницы. + access_denied: "403: Доступ запрещен" + access_denied_message: У вас нет прав для просмотра данной страницы. From 2d689ee82960f56445d7c8bbba4986547beb7057 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 18 Jan 2024 10:18:05 +0400 Subject: [PATCH 053/132] =?UTF-8?q?UPD:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BA=D1=80=D0=B0=D1=81=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=B0=D0=BD=D0=B8=D0=BC=D0=B0=D1=86=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20?= =?UTF-8?q?403?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/shared/_403.html.erb | 98 +++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/app/views/shared/_403.html.erb b/app/views/shared/_403.html.erb index 1b49e45..c9b02a6 100644 --- a/app/views/shared/_403.html.erb +++ b/app/views/shared/_403.html.erb @@ -14,12 +14,108 @@

    <%= t('access_denied') %>

    <%= t('access_denied_message') %>

    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    From ac1c4bf9609ba13aef7569c9b0959ca85fddb22c Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 18 Jan 2024 14:33:18 +0400 Subject: [PATCH 054/132] =?UTF-8?q?UPD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=BE=D0=B2=D0=B0=D1=8F=20?= =?UTF-8?q?=D1=80=D0=BE=D0=BB=D1=8C=20=D0=B8=D0=BD=D0=B6=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D1=80-=D0=BD=D0=B0=D0=B1=D0=BB=D1=8E=D0=B4=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C,=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=81=D0=BE=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D1=83=D1=8E=D1=89=D0=B8=D0=B5=20=D0=B2=D1=8C=D1=8E?= =?UTF-8?q?=D1=88=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/ability.rb | 5 +++ app/models/user.rb | 39 ++++++++++++++--- app/views/devise/registrations/new.html.erb | 48 ++++++++++++++------- app/views/post/index.html.erb | 6 ++- app/views/post/show.html.erb | 16 ++++--- app/views/shared/index/_device.html.erb | 6 ++- app/views/shared/show/_device.html.erb | 2 +- 7 files changed, 88 insertions(+), 34 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index a6976a8..a458e99 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -40,6 +40,11 @@ def initialize(user) can([:read, :service_tasks], Inspection) cannot(:manage, :armstrong) end + + if user.engineer_observer? + can(:read, :armstrong) + can([:read], Device, service_id: user.service_id) + end end def inspector(user) diff --git a/app/models/user.rb b/app/models/user.rb index b46b482..522dbe2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -14,6 +14,8 @@ class User < ApplicationRecord admin: 'admin', default: 'default', engineer: 'engineer', + engineer_observer: 'engineer_observer', + responsible_for_measuring_instruments: 'responsible_for_measuring_instruments', inspector: 'inspector', dosimetrist: 'dosimetrist', }.freeze @@ -27,8 +29,23 @@ class User < ApplicationRecord validate :validate_email def self.ransackable_attributes(_auth_object = nil) - ['avatar_url', 'created_at', 'email', 'encrypted_password', 'first_name', 'id', 'last_name', 'phone', 'remember_created_at', - 'reset_password_sent_at', 'reset_password_token', 'role', 'second_name', 'tabel_id', 'updated_at', 'service_id'] + [ + 'avatar_url', + 'created_at', + 'email', + 'encrypted_password', + 'first_name', + 'id', + 'last_name', + 'phone', + 'remember_created_at', + 'reset_password_sent_at', + 'reset_password_token', + 'role', + 'second_name', + 'tabel_id', + 'updated_at', + 'service_id'] end def self.ransackable_associations(_auth_object = nil) @@ -36,23 +53,31 @@ def self.ransackable_associations(_auth_object = nil) end def admin? - role == 'admin' + role == ROLES[:admin] end def default? - role == 'default' + role == ROLES[:default] end def engineer? - role == 'engineer' + role == ROLES[:engineer] + end + + def engineer_observer? + role == ROLES[:engineer_observer] + end + + def responsible_for_measuring_instruments? + role == ROLES[:responsible_for_measuring_instruments] end def inspector? - role == 'inspector' + role == ROLES[:inspector] end def dosimetrist? - role == 'dosimetrist' + role == ROLES[:dosimetrist] end protected diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index 6997795..283be80 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -13,22 +13,40 @@ required: true %>
    - <%= f.association :service, include_blank: false %>
    - <%= f.input :tabel_id, - required: true %> - <%= f.input :email, - autofocus: true, - input_html: { autocomplete: "email" }%> - <%= f.input :password, - required: true, - hint: ("#{@minimum_password_length} characters minimum" if @minimum_password_length), - input_html: { autocomplete: "new-password" } %> - <%= f.input :password_confirmation, - required: true, - input_html: { autocomplete: "new-password" } %> - <%= f.input :role, label: false, input_html: {hidden: true, value: "default"} %> - <%= render 'shared/timezone_field', f:f, is_new: true%> + <%= f.association :service, include_blank: false %> +
    +
    +
    + <%= f.input :tabel_id, + required: true %> +
    +
    + <%= f.input :email, + autofocus: true, + input_html: { autocomplete: "email" }%> +
    +
    +
    +
    + <%= f.input :password, + required: true, + hint: ("#{@minimum_password_length} characters minimum" if @minimum_password_length), + input_html: { autocomplete: "new-password" } %> +
    +
    + <%= f.input :password_confirmation, + required: true, + input_html: { autocomplete: "new-password" } %> +
    +
    +
    +
    + <%= f.input :role, :collection => User::ROLES.reject { |key, _value| key == :admin }.sort.map {|k,v| [v,k]}, include_blank: false, class:'form-select form-select' %> +
    +
    + <%= render 'shared/timezone_field', f:f, is_new: true%> +
    diff --git a/app/views/post/index.html.erb b/app/views/post/index.html.erb index 4c6b4d3..2f9cfaf 100644 --- a/app/views/post/index.html.erb +++ b/app/views/post/index.html.erb @@ -1,6 +1,8 @@ <% if user_signed_in? %> - <%= link_to new_post_path, class: "btn btn-primary my-3 w-100" do %> - <%= t('.b_new_post') %> + <% if can? :manage, Post %> + <%= link_to new_post_path, class: "btn btn-primary my-3 w-100" do %> + <%= t('.b_new_post') %> + <% end %> <% end %> <% end %> <% @posts.each do |post| %> diff --git a/app/views/post/show.html.erb b/app/views/post/show.html.erb index 1f1642f..ba0327e 100644 --- a/app/views/post/show.html.erb +++ b/app/views/post/show.html.erb @@ -16,11 +16,13 @@ <%= "#{@post.user.first_name} #{@post.user.last_name}" %>

    <%= @post.body %>

    -
    -
    - <%= link_to t('b_change'), edit_post_path(@post), class: "btn btn-primary"%> -
    -
    - <%= button_to t('b_delete'), post_path(@post), method: :delete, class: "btn btn-danger"%> -
    +<% if can? :manage, Post %> +
    +
    + <%= link_to t('b_change'), edit_post_path(@post), class: "btn btn-primary"%> +
    +
    + <%= button_to t('b_delete'), post_path(@post), method: :delete, class: "btn btn-danger"%> +
    + <% end %>
    diff --git a/app/views/shared/index/_device.html.erb b/app/views/shared/index/_device.html.erb index 6b7e0d5..9013d35 100644 --- a/app/views/shared/index/_device.html.erb +++ b/app/views/shared/index/_device.html.erb @@ -62,8 +62,10 @@
    - <%= render 'shared/modal_button_add', path: new_path, classes: "btn btn-primary w-100", text: t(".b_add_device") %> - <%= render 'shared/modal_button_add', path: new_device_component_path, classes: "btn btn-primary mt-2 w-100", text: t(".b_add_device_component") %> + <% if can? :manage, Device %> + <%= render 'shared/modal_button_add', path: new_path, classes: "btn btn-primary w-100", text: t(".b_add_device") %> + <%= render 'shared/modal_button_add', path: new_device_component_path, classes: "btn btn-primary mt-2 w-100", text: t(".b_add_device_component") %> + <% end %> <%= button_to device_download_path(request.params.merge(format: :pdf)), data: {turbo: :false}, class: "btn btn-primary my-2 w-100 shadow" do %> <%=t('b_download_pdf')%> <% end %> diff --git a/app/views/shared/show/_device.html.erb b/app/views/shared/show/_device.html.erb index 906c0c0..17c8609 100644 --- a/app/views/shared/show/_device.html.erb +++ b/app/views/shared/show/_device.html.erb @@ -186,7 +186,7 @@

    -
    +

    <%=t('activerecord.models.manufacturer')%>

    From 9a98422c924efdb8728c2030503821ef06bc037b Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 19 Jan 2024 08:25:28 +0400 Subject: [PATCH 055/132] =?UTF-8?q?FIX:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B8=20=D0=BE=D1=82=20=D0=B2=D0=BD=D0=B5=D1=88?= =?UTF-8?q?=D0=BD=D0=B8=D1=85=20=D1=80=D0=B5=D1=81=D1=83=D1=80=D1=81=D0=BE?= =?UTF-8?q?=D0=B2=20(=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20403=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BB=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/shared/_403.html.erb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/views/shared/_403.html.erb b/app/views/shared/_403.html.erb index c9b02a6..00bcda0 100644 --- a/app/views/shared/_403.html.erb +++ b/app/views/shared/_403.html.erb @@ -5,9 +5,10 @@ - - - + <%# + %> + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %> <%= t('access_denied') %> @@ -117,8 +118,5 @@
    - - - From 394810288a8442c964d5957fa5317621c380485b Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 19 Jan 2024 10:26:46 +0400 Subject: [PATCH 056/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB?= =?UTF-8?q?=D0=B5=D0=BC=D1=83=20=D1=81=20=D0=BD=D0=B5=D0=BD=D0=B0=D0=B7?= =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC=20=D1=80=D0=BE?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=BF=D1=80=D0=B8=20=D1=80=D0=B5=D0=B3=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D1=86=D0=B8=D0=B8,=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=20=D0=BC=D0=BE=D0=B6=D0=B5?= =?UTF-8?q?=D1=82=20=D0=B2=D1=8B=D0=B1=D1=80=D0=B0=D1=82=D1=8C=20=D1=81?= =?UTF-8?q?=D0=B0=D0=BC=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=20=D1=81=D0=B2=D0=BE=D1=8E=20=D1=80=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=20=D0=B8=20=D0=BE=D0=BD=D0=B0=20=D0=B1=D1=83=D0=B4?= =?UTF-8?q?=D0=B5=D1=82=20=D1=81=D1=80=D0=B0=D0=B7=D1=83=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B0=20=D0=BA=20=D0=B5?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=83=D1=87=D0=B5=D1=82=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 50f6875..67f7da5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -27,7 +27,7 @@ def set_time_zone(&block) protected def configure_permitted_parameters - attributes_sign_up = [:first_name, :last_name, :tabel_id, :service_id, :timezone] + attributes_sign_up = [:first_name, :last_name, :tabel_id, :role, :email,:service_id, :timezone] attributes_update = [*attributes_sign_up.excluding(:service_id), :second_name, :email, :phone] devise_parameter_sanitizer.permit(:sign_up, keys: attributes_sign_up) devise_parameter_sanitizer.permit(:account_update, keys: attributes_update) From cd4e6a71b90962f181f185cb23c89076613b8790 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 19 Jan 2024 15:07:43 +0400 Subject: [PATCH 057/132] =?UTF-8?q?UPD:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=BD=D0=B5=D0=B4=D0=BE=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=D1=89=D0=B8=D0=B9=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA=D0=B8=20=D1=81=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B2=D0=B5=D1=80=D0=B5=D0=BD=D0=BD=D1=8B=D0=BC?= =?UTF-8?q?=D0=B8=20=D0=A1=D0=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/en.yml | 1 + config/locales/ru.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index ca0602f..9a4abfb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -91,6 +91,7 @@ en: my_tasks: My tasks completed_tasks: Completed tasks all_tasks: All tasks + service_tasks: Service tasks index: inspection: device: Device diff --git a/config/locales/ru.yml b/config/locales/ru.yml index caff3f5..51f5b04 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -333,6 +333,7 @@ ru: my_tasks: Мои задачи completed_tasks: Завершенные задачи all_tasks: Все задачи + service_tasks: Задачи службы index: inspection: device: Прибор From 730320ea72c2e3b94c1b1be93728feb214b870ab Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 22 Jan 2024 10:46:35 +0400 Subject: [PATCH 058/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BD=D0=B5=D0=BA=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D1=8B=D0=B5=20=D1=80=D0=BE=D0=BB=D0=B8,=20=D0=B0?= =?UTF-8?q?=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D1=82=D0=B0=D0=BA=D0=B0?= =?UTF-8?q?=D1=8F,=20=D0=BA=D0=B0=D0=BA=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=B2=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/ability.rb | 11 +++++++- app/views/shared/navbar/_inspection.html.erb | 27 +++++++++++--------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index a458e99..39cee79 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -38,13 +38,22 @@ def initialize(user) can(:create, [DeviceModel, SupplementaryKit, DeviceRegGroup, MeasurementClass, MeasurementGroup, Manufacturer, DeviceComponent]) can([:read, :service_tasks], Inspection) - cannot(:manage, :armstrong) + can(:manage, :armstrong) end if user.engineer_observer? can(:read, :armstrong) can([:read], Device, service_id: user.service_id) end + + if user.responsible_for_measuring_instruments? + can([:manage, :create_inspection], Device, service_id: user.service_id) + cannot(:destroy, Device) + can(:create, [DeviceModel, SupplementaryKit, DeviceRegGroup, MeasurementClass, MeasurementGroup, + Manufacturer, DeviceComponent]) + can([:read, :service_tasks], Inspection) + cannot(:manage, :armstrong) + end end def inspector(user) diff --git a/app/views/shared/navbar/_inspection.html.erb b/app/views/shared/navbar/_inspection.html.erb index dd67be2..3b4f609 100644 --- a/app/views/shared/navbar/_inspection.html.erb +++ b/app/views/shared/navbar/_inspection.html.erb @@ -1,25 +1,28 @@ From ba9f641906e65c4975263fc911881199daddc6c4 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 22 Jan 2024 10:53:44 +0400 Subject: [PATCH 059/132] =?UTF-8?q?UPD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA?= =?UTF-8?q?=D0=B8=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80=D0=BE=D1=84=D0=B8?= =?UTF-8?q?=D0=BB=D1=8C=20=D0=B4=D0=BB=D1=8F=20=D0=B0=D0=B4=D0=BC=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/layouts/application.html.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 0d8ca57..397b2c0 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -48,6 +48,7 @@
    -
  • {tData}
    - - - - - - - - - - - - - - - - - <% @users.each do |user| %> +
    +
    <%=t('activerecord.attributes.user.tabel_id')%><%=t('activerecord.attributes.user.last_name')%><%=t('activerecord.attributes.user.first_name')%><%=t('activerecord.attributes.user.second_name')%><%=t('activerecord.attributes.user.role')%><%=t('activerecord.attributes.user.email')%><%=t('activerecord.attributes.user.phone')%><%=t('activerecord.attributes.user.service')%><%=t('created_at')%><%=t('updated_at')%><%=t('action')%>
    + - - - - - - - - - - - + + + + + + + + + + + - <% end %> - -
    <%= user.tabel_id %><%= user.last_name %><%= user.first_name %><%= user.second_name %><%= user.role %><%= user.email %><%= user.phone %><%= user.service.name %><%= formatted_date(user.created_at, :short_full) %><%= formatted_date(user.updated_at, :short_full) %> -
    - <%= link_to edit_admin_user_path(user), class: "btn" do %> - - <% end %> - <% unless user.id == current_user.id %> - <%= button_to admin_user_path(user), method: :delete, class: "btn" do %> - - <% end %> - <% end %> -
    -
    <%=t('activerecord.attributes.user.tabel_id')%><%=t('activerecord.attributes.user.last_name')%><%=t('activerecord.attributes.user.first_name')%><%=t('activerecord.attributes.user.second_name')%><%=t('activerecord.attributes.user.role')%><%=t('activerecord.attributes.user.email')%><%=t('activerecord.attributes.user.phone')%><%=t('activerecord.attributes.user.service')%><%=t('created_at')%><%=t('updated_at')%><%=t('action')%>
    + + + <% @users.each do |user| %> + + <%= user.tabel_id %> + <%= user.last_name %> + <%= user.first_name %> + <%= user.second_name %> + <%= user.role %> + <%= user.email %> + <%= user.phone %> + <%= user.service.name %> + <%= formatted_date(user.created_at, :short_full) %> + <%= formatted_date(user.updated_at, :short_full) %> + +
    + <%= link_to edit_admin_user_path(user), class: "btn" do %> + + <% end %> + <% unless user.id == current_user.id %> + <%= button_to admin_user_path(user), method: :delete, class: "btn" do %> + + <% end %> + <% end %> +
    + + + <% end %> + + +
    From eb5ab05196c4bb5237bda672a4e9781b8bb4f389 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 24 Jan 2024 10:05:16 +0400 Subject: [PATCH 061/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BD=D0=BE=D0=B2=D1=83=D1=8E=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=BE=D0=BD=D0=BA=D1=83=20level=20=D0=B2=20r?= =?UTF-8?q?oom=20=D0=B8=20=D0=BD=D0=BE=D0=B2=D1=83=D1=8E=20=D1=82=D0=B0?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=86=D1=83=20control=5Fpoints,=20=D1=81=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BD=D0=BE=D1=88=D0=B5=D0=BD=D0=B8=D1=8F=D0=BC?= =?UTF-8?q?=D0=B8=20=D0=BA=20room,=20device,=20channel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/channel.rb | 2 ++ app/models/control_point.rb | 7 +++++++ ...20240124055240_add_level_column_to_room.rb | 5 +++++ .../20240124055906_create_control_points.rb | 13 +++++++++++++ db/schema.rb | 19 ++++++++++++++++++- test/factories/control_points.rb | 9 +++++++++ test/models/control_point_test.rb | 7 +++++++ 7 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 app/models/control_point.rb create mode 100644 db/migrate/20240124055240_add_level_column_to_room.rb create mode 100644 db/migrate/20240124055906_create_control_points.rb create mode 100644 test/factories/control_points.rb create mode 100644 test/models/control_point_test.rb diff --git a/app/models/channel.rb b/app/models/channel.rb index fb89b7e..f345d29 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -3,5 +3,7 @@ class Channel < ApplicationRecord belongs_to :room belongs_to :server belongs_to :service + has_many :history + has_many :control_points end diff --git a/app/models/control_point.rb b/app/models/control_point.rb new file mode 100644 index 0000000..84f3c56 --- /dev/null +++ b/app/models/control_point.rb @@ -0,0 +1,7 @@ +class ControlPoint < ApplicationRecord + belongs_to :room + belongs_to :channel + belongs_to :device + + validates :name, presence: true +end diff --git a/db/migrate/20240124055240_add_level_column_to_room.rb b/db/migrate/20240124055240_add_level_column_to_room.rb new file mode 100644 index 0000000..8ea1cce --- /dev/null +++ b/db/migrate/20240124055240_add_level_column_to_room.rb @@ -0,0 +1,5 @@ +class AddLevelColumnToRoom < ActiveRecord::Migration[7.0] + def change + add_column :rooms, :level, :string + end +end diff --git a/db/migrate/20240124055906_create_control_points.rb b/db/migrate/20240124055906_create_control_points.rb new file mode 100644 index 0000000..c2ee43a --- /dev/null +++ b/db/migrate/20240124055906_create_control_points.rb @@ -0,0 +1,13 @@ +class CreateControlPoints < ActiveRecord::Migration[7.0] + def change + create_table :control_points do |t| + t.string :name + t.string :description + t.references :room, null: false, foreign_key: true + t.references :channel, null: false, foreign_key: true + t.references :device, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index faa389c..649c6e4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_09_15_043035) do +ActiveRecord::Schema[7.0].define(version: 2024_01_24_055906) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -52,6 +52,19 @@ t.index ["service_id"], name: "index_channels_on_service_id" end + create_table "control_points", force: :cascade do |t| + t.string "name" + t.string "description" + t.bigint "room_id", null: false + t.bigint "channel_id", null: false + t.bigint "device_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["channel_id"], name: "index_control_points_on_channel_id" + t.index ["device_id"], name: "index_control_points_on_device_id" + t.index ["room_id"], name: "index_control_points_on_room_id" + end + create_table "device_components", force: :cascade do |t| t.bigint "supplementary_kit_id" t.string "serial_id" @@ -205,6 +218,7 @@ t.bigint "building_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "level" t.index ["building_id"], name: "index_rooms_on_building_id" end @@ -267,6 +281,9 @@ add_foreign_key "channels", "rooms" add_foreign_key "channels", "servers" add_foreign_key "channels", "services" + add_foreign_key "control_points", "channels" + add_foreign_key "control_points", "devices" + add_foreign_key "control_points", "rooms" add_foreign_key "device_components", "supplementary_kits" add_foreign_key "device_models", "manufacturers" add_foreign_key "device_models", "measurement_classes" diff --git a/test/factories/control_points.rb b/test/factories/control_points.rb new file mode 100644 index 0000000..c3b7bad --- /dev/null +++ b/test/factories/control_points.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :control_point do + name { "MyString" } + description { "MyString" } + room { nil } + channel { nil } + device { nil } + end +end diff --git a/test/models/control_point_test.rb b/test/models/control_point_test.rb new file mode 100644 index 0000000..7f49f60 --- /dev/null +++ b/test/models/control_point_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class ControlPointTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From fb550be0cc21e473db9b273344f1602a5a1bf86c Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 24 Jan 2024 10:06:36 +0400 Subject: [PATCH 062/132] =?UTF-8?q?UPD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B8=20?= =?UTF-8?q?=D0=B2=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B8=20room=20=D0=B8?= =?UTF-8?q?=20device?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/device.rb | 1 + app/models/room.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/app/models/device.rb b/app/models/device.rb index 52d9e39..a62fdad 100644 --- a/app/models/device.rb +++ b/app/models/device.rb @@ -13,6 +13,7 @@ class Device < ApplicationRecord belongs_to :service has_many :inspections + has_many :control_points has_one :channel validates :inventory_id, numericality: { less_than_or_equal_to: 2147483647 }, uniqueness: true, allow_nil: true diff --git a/app/models/room.rb b/app/models/room.rb index 4c36553..38f6c2b 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -3,6 +3,7 @@ class Room < ApplicationRecord has_many :channels has_many :devices + has_many :control_points validates :name, presence: true end From c35b3d3e8cef0e7bf3170a024d1ce47c999230fa Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 24 Jan 2024 14:22:56 +0400 Subject: [PATCH 063/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D1=87=D0=B0=D1=81=D1=82=D1=8C=20?= =?UTF-8?q?=D1=84=D1=83=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8=20=D1=81=20=D1=82=D0=BE=D1=87=D0=BA=D0=B0=D0=BC=D0=B8=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D1=8F,=20=D0=BD?= =?UTF-8?q?=D0=B5=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D1=8B=20=D0=B8=20=D1=82=D0=B4.=20=D0=92=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BC=D0=B8=D1=82=D0=B5=20=D0=BC=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=B7=D0=B0=D0=B3=D0=BB=D1=83=D1=88=D0=B5=D0=BA=20?= =?UTF-8?q?=D0=B8=20=D0=BC=D0=BE=D0=B3=D1=83=D1=82=20=D0=B1=D1=8B=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BA=D1=80=D0=B8=D1=82=D0=B8=D1=87=D0=BD=D1=8B=D0=B5?= =?UTF-8?q?=20=D0=B1=D0=B0=D0=B3=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/control_point_controller.rb | 58 ++++++++++ .../concerns/control_point_concern.rb | 23 ++++ app/controllers/control_point_controller.rb | 19 +++ app/models/control_point.rb | 14 ++- .../control_point/create.turbo_stream.erb | 1 + .../admin/control_point/edit.turbo_stream.erb | 3 + app/views/admin/control_point/index.html.erb | 109 ++++++++++++++++++ .../admin/control_point/new.turbo_stream.erb | 3 + .../control_point/update.rutbo_stream.erb | 1 + app/views/admin/users/index.html.erb | 1 + .../shared/admin/form/_control_point.html.erb | 38 ++++++ app/views/shared/admin/navbar/_admin.html.erb | 3 + .../admin/navbar/_location_navbar.html.erb | 6 + .../shared/admin/navbar/_user_navbar.html.erb | 7 ++ config/routes.rb | 4 +- ...hange_null_constraint_on_control_points.rb | 7 ++ db/schema.rb | 8 +- .../control_point_controller_test.rb | 7 ++ 18 files changed, 303 insertions(+), 9 deletions(-) create mode 100644 app/controllers/admin/control_point_controller.rb create mode 100644 app/controllers/concerns/control_point_concern.rb create mode 100644 app/controllers/control_point_controller.rb create mode 100644 app/views/admin/control_point/create.turbo_stream.erb create mode 100644 app/views/admin/control_point/edit.turbo_stream.erb create mode 100644 app/views/admin/control_point/index.html.erb create mode 100644 app/views/admin/control_point/new.turbo_stream.erb create mode 100644 app/views/admin/control_point/update.rutbo_stream.erb create mode 100644 app/views/shared/admin/form/_control_point.html.erb create mode 100644 app/views/shared/admin/navbar/_location_navbar.html.erb create mode 100644 app/views/shared/admin/navbar/_user_navbar.html.erb create mode 100644 db/migrate/20240124095819_change_null_constraint_on_control_points.rb create mode 100644 test/controllers/control_point_controller_test.rb diff --git a/app/controllers/admin/control_point_controller.rb b/app/controllers/admin/control_point_controller.rb new file mode 100644 index 0000000..51f270c --- /dev/null +++ b/app/controllers/admin/control_point_controller.rb @@ -0,0 +1,58 @@ +class Admin::ControlPointController < ApplicationController + include ControlPointConcern + load_and_authorize_resource + before_action :set_control_point, only: [:show, :edit, :update, :destroy] + + def index + @query = ControlPoint.ransack(params[:q]) + @pagy, @control_points = pagy(@query.result. + order(:name)) + end + + def new + authorize!(:new, :control_point_admin) + @control_point = ControlPoint.new + end + + def create + authorize!(:create, :control_point_admin) + control_point_create + end + + def update + if @control_point.update(control_point_params) + redirect_back(fallback_location: root_path) + else + render(:edit, status: :unprocessable_entity) + end + end + + def destroy + assigned_models_count = + Room.where(control_point_id: params[:id]).count + + Device.where(control_point_id: params[:id]).count + + Channel.where(control_point_id: params[:id]).count + + if assigned_models_count.zero? + @control_point.destroy + else + flash[:error] = t('message.control_point.delete.error') + end + redirect_to(admin_control_point_index_path) + end + + private + + def set_control_point + @control_point = ControlPoint.find(params[:id]) + end + + def control_point_params + params.require(:control_point).permit( + :name, + :room_id, + :device_id, + :channel_id, + ) + end +end diff --git a/app/controllers/concerns/control_point_concern.rb b/app/controllers/concerns/control_point_concern.rb new file mode 100644 index 0000000..bd32ec6 --- /dev/null +++ b/app/controllers/concerns/control_point_concern.rb @@ -0,0 +1,23 @@ +module ControlPointConcern + extend ActiveSupport::Concern + + included do + def control_point_create + @control_point = ControlPoint.new(control_point_params) + if @control_point.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def control_point_params + params.require(:control_point).permit( + :name, + :device_id, + :room_id, + :channel_id, + ) + end + end +end diff --git a/app/controllers/control_point_controller.rb b/app/controllers/control_point_controller.rb new file mode 100644 index 0000000..861cf6d --- /dev/null +++ b/app/controllers/control_point_controller.rb @@ -0,0 +1,19 @@ +class ControlPointController < ApplicationController + include ControlPointConcern + + load_and_authorize_resource + + def index + @query = ControlPoint.ransack(params[:q]) + @query.sorts = ['name asc'] + @pagy, @control_points = pagy(@query.result) + end + + def new + @control_point = ControlPoint.new + end + + def create + control_point_create + end +end diff --git a/app/models/control_point.rb b/app/models/control_point.rb index 84f3c56..11d45f4 100644 --- a/app/models/control_point.rb +++ b/app/models/control_point.rb @@ -1,7 +1,15 @@ class ControlPoint < ApplicationRecord - belongs_to :room - belongs_to :channel - belongs_to :device + belongs_to :room, optional: true + belongs_to :channel,optional: true + belongs_to :device, optional: true validates :name, presence: true + + def self.ransackable_attributes(auth_object = nil) + ["channel_id", "created_at", "description", "device_id", "id", "name", "room_id", "updated_at"] + end + + def self.ransackable_associations(auth_object = nil) + ["channel", "device", "room"] + end end diff --git a/app/views/admin/control_point/create.turbo_stream.erb b/app/views/admin/control_point/create.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/control_point/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/admin/control_point/edit.turbo_stream.erb b/app/views/admin/control_point/edit.turbo_stream.erb new file mode 100644 index 0000000..000bc6f --- /dev/null +++ b/app/views/admin/control_point/edit.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('.edit_control_point') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/control_point", + locals: { path: admin_control_point_path(@control_point), control_point: @control_point } %> diff --git a/app/views/admin/control_point/index.html.erb b/app/views/admin/control_point/index.html.erb new file mode 100644 index 0000000..c99f9d9 --- /dev/null +++ b/app/views/admin/control_point/index.html.erb @@ -0,0 +1,109 @@ +<% provide :page_title, "Точки контроля" %> +<%= render 'shared/admin/navbar/admin', selected: "location" %> +<%= render 'shared/admin/navbar/location_navbar', selected: "control_point" %> +<%= render 'shared/flash_alert' %> +
    +
    +
    +
    +

    + +

    +
    + <%= search_form_for(@query, url: admin_control_point_index_path, method: :get, class: "rounded accordion-body") do |f| %> +
    + <%= f.label :name, class: "mb-2" %> + <%= f.search_field :name_cont, class:"form-control", placeholder:"Название" %> + <%= f.label :room_id, class:"mb-1" %> + <%= f.collection_select(:room_id_eq, Room.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select'}) %> +
    + <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%> +
    + <%= t("b_clear")%> +
    + <% end %> +
    +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_control_point_path, classes: "btn btn-primary w-100", text: t("b_add") %> +
    +
    +
    + + + + + + + + + + + + <% @control_points.each do |control_point| %> + + + <% if control_point.room.nil? %> + + <% else %> + + <% end %> + <% if control_point.device.nil? %> + + <% else %> + + <% end %> + <% if control_point.channel.nil? %> + + <% else %> + + <% end %> + + + <% end %> + +
    <%= t('activerecord.attributes.control_point.name') %><%= t('activerecord.attributes.control_point.room') %><%= t('activerecord.attributes.control_point.device') %><%= t('activerecord.attributes.control_point.channel') %><%= t('action') %>
    <%= control_point.name %><%= "——" %><%= control_point.room.name %><%= "——" %> + <%= link_to "#{control_point.device.device_model.name}", admin_device_path(control_point.device.id) %> + <%= "——" %><%= control_point.channel.name %> +
    + <%= link_to edit_admin_control_point_path(control_point.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to admin_control_point_path(control_point.id), method: :delete, class: "btn" do %> + + <% end %> +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + <%= pagination @pagy %> +
    +
    +
    +
    +
    + diff --git a/app/views/admin/control_point/new.turbo_stream.erb b/app/views/admin/control_point/new.turbo_stream.erb new file mode 100644 index 0000000..8fd5b87 --- /dev/null +++ b/app/views/admin/control_point/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('.add_control_point') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/control_point", + locals: {path: admin_control_point_index_path, control_point: @control_point } %> diff --git a/app/views/admin/control_point/update.rutbo_stream.erb b/app/views/admin/control_point/update.rutbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/control_point/update.rutbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb index fc3545e..a0a15f1 100644 --- a/app/views/admin/users/index.html.erb +++ b/app/views/admin/users/index.html.erb @@ -1,5 +1,6 @@ <% provide :page_title, "Панель администрирования" %> <%= render 'shared/admin/navbar/admin', selected: "user" %> +<%= render 'shared/admin/navbar/user_navbar', selected: "user" %> <%= render 'shared/flash_alert' %>
    diff --git a/app/views/shared/admin/form/_control_point.html.erb b/app/views/shared/admin/form/_control_point.html.erb new file mode 100644 index 0000000..2532f68 --- /dev/null +++ b/app/views/shared/admin/form/_control_point.html.erb @@ -0,0 +1,38 @@ +<%= simple_form_for control_point, as: :control_point, url: path, + class: "form-group row" do |f| %> +
    +
    + <%= f.input :name, placeholder: 'Название точки контроля', input_html: {:tabindex => 1}%> + <%= f.label :room, class: "mb-2" %> +
    +
    + <%= f.association :room, label: false, input_html: {:tabindex => 2} %> +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_control_point_path, classes: "btn btn-ligth", text: nil %> +
    +
    + <%= f.label :channel, class: "mb-2" %> +
    +
    + <%= f.association :channel, label: false, input_html: {:tabindex => 2} %> +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_control_point_path, classes: "btn btn-ligth", text: nil %> +
    +
    + <%= f.label :device, class: "mb-2" %> +
    +
    + <%= f.association :device, label: false, input_html: {:tabindex => 2} %> +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_control_point_path, classes: "btn btn-ligth", text: nil %> +
    +
    + +
    +
    + + <%= render 'shared/form/actions', f:f %> +<% end %> diff --git a/app/views/shared/admin/navbar/_admin.html.erb b/app/views/shared/admin/navbar/_admin.html.erb index 91e624a..c4b4588 100644 --- a/app/views/shared/admin/navbar/_admin.html.erb +++ b/app/views/shared/admin/navbar/_admin.html.erb @@ -6,4 +6,7 @@ + diff --git a/app/views/shared/admin/navbar/_location_navbar.html.erb b/app/views/shared/admin/navbar/_location_navbar.html.erb new file mode 100644 index 0000000..06225b9 --- /dev/null +++ b/app/views/shared/admin/navbar/_location_navbar.html.erb @@ -0,0 +1,6 @@ +
    + <% selected = local_assigns[:selected] %> + <%= link_to 'Точки контроля', admin_control_point_index_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'control_point' }" %> + <%= link_to 'Помещения', admin_control_point_index_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'room' }" %> + <%= link_to 'Здания', admin_control_point_index_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'building' }" %> +
    diff --git a/app/views/shared/admin/navbar/_user_navbar.html.erb b/app/views/shared/admin/navbar/_user_navbar.html.erb new file mode 100644 index 0000000..a43b06e --- /dev/null +++ b/app/views/shared/admin/navbar/_user_navbar.html.erb @@ -0,0 +1,7 @@ +
    + <% selected = local_assigns[:selected] %> + <%= link_to 'Пользователи', admin_users_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'user' }" %> + <%= link_to 'Службы', admin_users_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'service' }" %> + <%= link_to 'Подразделения', admin_users_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'division' }" %> + <%= link_to 'Организации', admin_users_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'organization' }" %> +
    diff --git a/config/routes.rb b/config/routes.rb index 52a8c47..31820e1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,7 +20,7 @@ post :create_inspection, :to => 'device#create_inspection' end resources :device_model, :measurement_class - resources :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, except: [:show] + resources :control_point, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, except: [:show] end devise_for :users, controllers: { @@ -32,7 +32,7 @@ resources :device do post :create_inspection, :to => 'device#create_inspection' end - resources :device_model, :measurement_class, + resources :control_point, :device_model, :measurement_class, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, only: [:create, :new] resources :inspection, except: [:index] do diff --git a/db/migrate/20240124095819_change_null_constraint_on_control_points.rb b/db/migrate/20240124095819_change_null_constraint_on_control_points.rb new file mode 100644 index 0000000..d3fb288 --- /dev/null +++ b/db/migrate/20240124095819_change_null_constraint_on_control_points.rb @@ -0,0 +1,7 @@ +class ChangeNullConstraintOnControlPoints < ActiveRecord::Migration[7.0] + def change + change_column_null :control_points, :room_id, true + change_column_null :control_points, :channel_id, true + change_column_null :control_points, :device_id, true + end +end diff --git a/db/schema.rb b/db/schema.rb index 649c6e4..c1e0a20 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_01_24_055906) do +ActiveRecord::Schema[7.0].define(version: 2024_01_24_095819) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -55,9 +55,9 @@ create_table "control_points", force: :cascade do |t| t.string "name" t.string "description" - t.bigint "room_id", null: false - t.bigint "channel_id", null: false - t.bigint "device_id", null: false + t.bigint "room_id" + t.bigint "channel_id" + t.bigint "device_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["channel_id"], name: "index_control_points_on_channel_id" diff --git a/test/controllers/control_point_controller_test.rb b/test/controllers/control_point_controller_test.rb new file mode 100644 index 0000000..01a773c --- /dev/null +++ b/test/controllers/control_point_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class ControlPointControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end From 3a7f825cfbe13acf99b34d4538fc292ec06adc77 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 25 Jan 2024 15:23:00 +0400 Subject: [PATCH 064/132] =?UTF-8?q?UPD:=20=D0=BF=D1=80=D0=BE=D0=B4=D0=BE?= =?UTF-8?q?=D0=BB=D0=B6=D0=B0=D0=B5=D1=82=20=D0=B2=D0=BD=D0=BE=D1=81=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BE=D1=82=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82?= =?UTF-8?q?=D0=B0=20c35b3d3e,=20=D1=83=D0=B4=D0=B0=D0=BB=D1=8F=D0=B5=D1=82?= =?UTF-8?q?=20=D0=B8=D0=B7=20channel=20=D0=BA=D0=BE=D0=BB=D0=BE=D0=BD?= =?UTF-8?q?=D0=BA=D0=B8=20room=20=D0=B8=20device,=20=D1=82=D0=B5=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D1=8C=20=D1=8D=D1=82=D0=B8=20=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D1=85=D1=80=D0=B0=D0=BD=D1=8F=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D0=B2=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D0=B5=20?= =?UTF-8?q?control=5Fpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/v1/armstrong_controller.rb | 13 ++++++++---- .../Components/Armstrong/Armstrong.jsx | 10 +++++----- app/models/channel.rb | 4 +--- app/models/control_point.rb | 3 ++- app/models/device.rb | 3 +-- app/models/room.rb | 3 +-- app/views/admin/control_point/index.html.erb | 16 +++++++-------- .../admin/control_point/new.turbo_stream.erb | 2 +- app/views/layouts/application.html.erb | 5 +++++ app/views/shared/_modal.html.erb | 4 ++-- .../shared/admin/form/_control_point.html.erb | 13 +++--------- .../admin/navbar/_location_navbar.html.erb | 20 +++++++++++++++---- .../shared/admin/navbar/_user_navbar.html.erb | 16 +++++++++++---- config/locales/ru.yml | 12 ++++++++++- ...nge_control_point_and_channel_relations.rb | 5 +++++ ...240125055503_delete_column_from_channel.rb | 6 ++++++ ...5074727_remove_name_column_from_channel.rb | 5 +++++ ...emove_channel_column_from_control_point.rb | 5 +++++ db/schema.rb | 15 ++++---------- db/seeds.rb | 17 ++++++++++++---- 20 files changed, 115 insertions(+), 62 deletions(-) create mode 100644 db/migrate/20240124103459_change_control_point_and_channel_relations.rb create mode 100644 db/migrate/20240125055503_delete_column_from_channel.rb create mode 100644 db/migrate/20240125074727_remove_name_column_from_channel.rb create mode 100644 db/migrate/20240125082316_remove_channel_column_from_control_point.rb diff --git a/app/controllers/api/v1/armstrong_controller.rb b/app/controllers/api/v1/armstrong_controller.rb index e0dcc39..a455fee 100644 --- a/app/controllers/api/v1/armstrong_controller.rb +++ b/app/controllers/api/v1/armstrong_controller.rb @@ -3,14 +3,19 @@ class V1::ArmstrongController < ApplicationController after_action :set_csp_header def index - channels = Channel.all + channels = Channel.joins(:control_point, :server, :service) result = channels.sort { |a, b| a[:server_id] <=> b[:server_id] } render(json: result, include: [ - device: { include: [device_model: { only: [:name] }] }, - room: { only: [:name] }, - ]) + :server, + :service, + control_point: { + include: [ + :room, + device: { include: :device_model } + ]}, + ]) end private diff --git a/app/javascript/Components/Armstrong/Armstrong.jsx b/app/javascript/Components/Armstrong/Armstrong.jsx index 846eb02..d236003 100644 --- a/app/javascript/Components/Armstrong/Armstrong.jsx +++ b/app/javascript/Components/Armstrong/Armstrong.jsx @@ -42,12 +42,12 @@ export default function Armstrong() { state: setChannelColor(channel.state), specialControl: , id: channel.id, - serverId: channel.server_id, + serverId: channel.server.id, channelId: channel.channel_id, - name: channel.name, - deviceModel: channel.device.device_model.name, - location: channel.room.name, - locationDescription: channel.location_description, + name: channel.control_point.name, + deviceModel: channel.control_point.device.device_model.name, + location: channel.control_point.room.name, + locationDescription: channel.control_point.description, eventSystemValue: channel.event_system_value.toExponential(3), eventNotSystemValue: channel.event_not_system_value.toExponential(3), eventDatetime: moment.tz(channel.event_datetime, timeZone).format('HH:mm:SS'), diff --git a/app/models/channel.rb b/app/models/channel.rb index f345d29..4f57cc2 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -1,9 +1,7 @@ class Channel < ApplicationRecord - belongs_to :device - belongs_to :room belongs_to :server belongs_to :service has_many :history - has_many :control_points + belongs_to :control_point end diff --git a/app/models/control_point.rb b/app/models/control_point.rb index 11d45f4..eba8e89 100644 --- a/app/models/control_point.rb +++ b/app/models/control_point.rb @@ -1,8 +1,9 @@ class ControlPoint < ApplicationRecord belongs_to :room, optional: true - belongs_to :channel,optional: true belongs_to :device, optional: true + has_one :channel + validates :name, presence: true def self.ransackable_attributes(auth_object = nil) diff --git a/app/models/device.rb b/app/models/device.rb index a62fdad..e97aefe 100644 --- a/app/models/device.rb +++ b/app/models/device.rb @@ -13,8 +13,7 @@ class Device < ApplicationRecord belongs_to :service has_many :inspections - has_many :control_points - has_one :channel + has_one :control_point validates :inventory_id, numericality: { less_than_or_equal_to: 2147483647 }, uniqueness: true, allow_nil: true validates :serial_id, :tabel_id, presence: true, uniqueness: true diff --git a/app/models/room.rb b/app/models/room.rb index 38f6c2b..0ae9be9 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -1,9 +1,8 @@ class Room < ApplicationRecord belongs_to :building - has_many :channels has_many :devices - has_many :control_points + has_many :control_point validates :name, presence: true end diff --git a/app/views/admin/control_point/index.html.erb b/app/views/admin/control_point/index.html.erb index c99f9d9..95aae73 100644 --- a/app/views/admin/control_point/index.html.erb +++ b/app/views/admin/control_point/index.html.erb @@ -1,9 +1,9 @@ <% provide :page_title, "Точки контроля" %> <%= render 'shared/admin/navbar/admin', selected: "location" %> -<%= render 'shared/admin/navbar/location_navbar', selected: "control_point" %> <%= render 'shared/flash_alert' %>
    -
    +
    + <%= render 'shared/admin/navbar/location_navbar', selected: "control_point" %>

    @@ -30,7 +30,7 @@

    <%= render 'shared/modal_button_add', path: new_admin_control_point_path, classes: "btn btn-primary w-100", text: t("b_add") %>
    -
    +
    @@ -38,13 +38,13 @@ - + <% @control_points.each do |control_point| %> - + <% if control_point.room.nil? %> @@ -55,13 +55,13 @@ <% else %> <% end %> - <% if control_point.channel.nil? %> + <% if control_point.description.nil? %> <% else %> - + <% end %> <% else %> <% end %> <% if control_point.description.nil? %> diff --git a/app/views/admin/device/index.html.erb b/app/views/admin/device/index.html.erb index 48f6dc4..c2bf60f 100644 --- a/app/views/admin/device/index.html.erb +++ b/app/views/admin/device/index.html.erb @@ -1,3 +1,2 @@ <%= render 'shared/admin/navbar/admin', selected: "device" %> -<%= render 'shared/admin/navbar/device_bread', selected: "device" %> <%= render 'shared/index/device', search_path: admin_device_index_path, show_path: admin_device_path(0), new_path: new_admin_device_path %> diff --git a/app/views/admin/device_component/index.html.erb b/app/views/admin/device_component/index.html.erb index cb918c3..78632b2 100644 --- a/app/views/admin/device_component/index.html.erb +++ b/app/views/admin/device_component/index.html.erb @@ -1,9 +1,9 @@ <% provide :page_title, "Компоненты" %> <%= render 'shared/admin/navbar/admin', selected: "device" %> -<%= render 'shared/admin/navbar/device_bread', selected: "device_component" %> <%= render 'shared/flash_alert' %> -
    -
    +
    +
    + <%= render 'shared/admin/navbar/device_bread', selected: "device_component" %>

    @@ -51,9 +51,7 @@

    <%= render 'shared/modal_button_add', path: new_admin_device_component_path, classes: "btn btn-primary w-100 mb-3", text: t("b_add") %>
    -
    -
    -
    +
    <%= t('activerecord.attributes.control_point.name') %> <%= t('activerecord.attributes.control_point.room') %> <%= t('activerecord.attributes.control_point.device') %><%= t('activerecord.attributes.control_point.channel') %><%= t('activerecord.attributes.control_point.description') %> <%= t('action') %>
    <%= control_point.name %><%= "——" %><%= "——" %> - <%= link_to "#{control_point.device.device_model.name}", admin_device_path(control_point.device.id) %> + <%= button_to "(#{sprintf("%05d", control_point.device.tabel_id)}) #{control_point.device.device_model.name}", admin_device_path(control_point.device.id), method: :get, class: "btn btn-light" %> <%= "——" %><%= control_point.channel.name %><%= control_point.description %>
    diff --git a/app/views/admin/control_point/new.turbo_stream.erb b/app/views/admin/control_point/new.turbo_stream.erb index 8fd5b87..1c12ae4 100644 --- a/app/views/admin/control_point/new.turbo_stream.erb +++ b/app/views/admin/control_point/new.turbo_stream.erb @@ -1,3 +1,3 @@ -<%= turbo_stream.update "modal-title", t('.add_control_point') %> +<%= turbo_stream.update "modal-title", t('admin.control_points.new.add_control_point') %> <%= turbo_stream.update "modal-body", partial: "shared/admin/form/control_point", locals: {path: admin_control_point_index_path, control_point: @control_point } %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 397b2c0..7a804c5 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -39,6 +39,11 @@ <% if user_signed_in? %> + <% if current_user.admin? %> + <%= button_to admin_users_path, method: :get, class: "btn btn-light me-2" do %> + <%= t('.admin_panel') %> + <% end %> + <% end %>
    <%= "——" %> - <%= button_to "(#{sprintf("%05d", control_point.device.tabel_id)}) #{control_point.device.device_model.name}", admin_device_path(control_point.device.id), method: :get, class: "btn btn-light" %> + <%= button_to "(#{sprintf("%05d", control_point.device.tabel_id)}) #{control_point.device.device_model.name}", admin_device_path(control_point.device.id), method: :get, class: "btn btn-light btn-sm" %>
    @@ -99,7 +97,6 @@ -
    diff --git a/app/views/admin/device_model/index.html.erb b/app/views/admin/device_model/index.html.erb index 7902a0b..bc30512 100644 --- a/app/views/admin/device_model/index.html.erb +++ b/app/views/admin/device_model/index.html.erb @@ -1,8 +1,8 @@ <% provide :page_title, "Модели" %> <%= render 'shared/admin/navbar/admin', selected: "device" %> -<%= render 'shared/admin/navbar/device_bread', selected: "device_model" %> -
    -
    +
    +
    + <%= render 'shared/admin/navbar/device_bread', selected: "device_model" %>

    @@ -76,7 +76,7 @@

    - <%= f.submit "Применить", class: 'col btn btn-primary w-100'%> + <%= f.submit t('b_accept'), class: 'col btn btn-primary w-100'%>
    <%= t("b_clear")%>
    @@ -88,47 +88,45 @@
    <%= render 'shared/modal_button_add', path: new_admin_device_model_path, classes: "btn btn-primary w-100 mb-3", text: t("b_add") %>
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - <% @device_models.each do |device_model| %> - - - - - - - - - - - - - +
    +
    +
    <%= t('activerecord.attributes.device_model.name')%><%= t('activerecord.attributes.device_model.manufacturer')%><%= t('activerecord.attributes.device_model.measurement_group')%><%= t('activerecord.attributes.device_model.measurement_class')%><%= t('activerecord.attributes.device_model.measuring_unit')%><%= t('activerecord.attributes.device_model.measurement_sensitivity')%>.<%= t('activerecord.attributes.device_model.measurement_min')%><%= t('activerecord.attributes.device_model.measurement_max')%><%= t('activerecord.attributes.device_model.calibration_min')%><%= t('activerecord.attributes.device_model.calibration_max')%><%= t('activerecord.attributes.device_model.safety_class')%><%= t('activerecord.attributes.device_model.accuracy_class')%>
    <%= device_model.name %><%= device_model.manufacturer.name %><%= device_model.measurement_group.name %><%= device_model.measurement_class.name %><%= device_model.measuring_unit %><%= device_model.measurement_sensitivity %><%= device_model.measurement_min %><%= device_model.measurement_max %><%= device_model.calibration_min %><%= device_model.calibration_max %><%= device_model.safety_class %><%= device_model.accuracy_class %>
    + + + + + + + + + + + + + + - <% end %> - -
    <%= t('activerecord.attributes.device_model.name')%><%= t('activerecord.attributes.device_model.manufacturer')%><%= t('activerecord.attributes.device_model.measurement_group')%><%= t('activerecord.attributes.device_model.measurement_class')%><%= t('activerecord.attributes.device_model.measuring_unit')%><%= t('activerecord.attributes.device_model.measurement_sensitivity')%>.<%= t('activerecord.attributes.device_model.measurement_min')%><%= t('activerecord.attributes.device_model.measurement_max')%><%= t('activerecord.attributes.device_model.calibration_min')%><%= t('activerecord.attributes.device_model.calibration_max')%><%= t('activerecord.attributes.device_model.safety_class')%><%= t('activerecord.attributes.device_model.accuracy_class')%>
    -
    + + + <% @device_models.each do |device_model| %> + + <%= device_model.name %> + <%= device_model.manufacturer.name %> + <%= device_model.measurement_group.name %> + <%= device_model.measurement_class.name %> + <%= device_model.measuring_unit %> + <%= device_model.measurement_sensitivity %> + <%= device_model.measurement_min %> + <%= device_model.measurement_max %> + <%= device_model.calibration_min %> + <%= device_model.calibration_max %> + <%= device_model.safety_class %> + <%= device_model.accuracy_class %> + + <% end %> + + +
    diff --git a/app/views/admin/device_reg_group/index.html.erb b/app/views/admin/device_reg_group/index.html.erb index ccc283a..908c8f2 100644 --- a/app/views/admin/device_reg_group/index.html.erb +++ b/app/views/admin/device_reg_group/index.html.erb @@ -1,9 +1,9 @@ <% provide :page_title, "Рег.группы" %> <%= render 'shared/admin/navbar/admin', selected: "device" %> -<%= render 'shared/admin/navbar/device_bread', selected: "device_reg_group" %> <%= render 'shared/flash_alert' %>
    -
    +
    + <%= render 'shared/admin/navbar/device_bread', selected: "device_reg_group" %>

    @@ -29,7 +29,7 @@ <%= render 'shared/modal_button_add', path: new_admin_device_reg_group_path, classes: "btn btn-primary w-100", text: t("b_add") %>

    -
    +
    diff --git a/app/views/admin/manufacturer/index.html.erb b/app/views/admin/manufacturer/index.html.erb index 6400a25..9448d4f 100644 --- a/app/views/admin/manufacturer/index.html.erb +++ b/app/views/admin/manufacturer/index.html.erb @@ -1,9 +1,9 @@ <% provide :page_title, "Производители" %> <%= render 'shared/admin/navbar/admin', selected: "device" %> -<%= render 'shared/admin/navbar/device_bread', selected: "manufacturer" %> <%= render 'shared/flash_alert' %> -
    -
    +
    +
    + <%= render 'shared/admin/navbar/device_bread', selected: "manufacturer" %>

    @@ -51,9 +51,7 @@

    <%= render 'shared/modal_button_add', path: new_admin_manufacturer_path, classes: "btn btn-primary w-100 mb-3", text: t("b_add") %>
    -
    -
    -
    +
    diff --git a/app/views/admin/measurement_class/index.html.erb b/app/views/admin/measurement_class/index.html.erb index a7ec6ab..f144f18 100644 --- a/app/views/admin/measurement_class/index.html.erb +++ b/app/views/admin/measurement_class/index.html.erb @@ -1,9 +1,9 @@ <% provide :page_title, "Классы измерения" %> <%= render 'shared/admin/navbar/admin', selected: "device" %> -<%= render 'shared/admin/navbar/device_bread', selected: "measurement_class" %> <%= render 'shared/flash_alert' %>
    -
    +
    + <%= render 'shared/admin/navbar/device_bread', selected: "measurement_class" %>

    @@ -32,7 +32,7 @@

    <%= render 'shared/modal_button_add', path: new_admin_measurement_class_path, classes: "btn btn-primary w-100", text: t("b_add") %>
    -
    +
    diff --git a/app/views/admin/measurement_group/index.html.erb b/app/views/admin/measurement_group/index.html.erb index 16af796..7869bed 100644 --- a/app/views/admin/measurement_group/index.html.erb +++ b/app/views/admin/measurement_group/index.html.erb @@ -1,9 +1,9 @@ <% provide :page_title, "Группы измерения" %> <%= render 'shared/admin/navbar/admin', selected: "device" %> -<%= render 'shared/admin/navbar/device_bread', selected: "measurement_group" %> <%= render 'shared/flash_alert' %>
    -
    +
    + <%= render 'shared/admin/navbar/device_bread', selected: "measurement_group" %>

    @@ -27,7 +27,7 @@

    <%= render 'shared/modal_button_add', path: new_admin_measurement_group_path, classes: "btn btn-primary w-100", text: t("b_add") %>
    -
    +
    diff --git a/app/views/admin/supplementary_kit/index.html.erb b/app/views/admin/supplementary_kit/index.html.erb index c9a8434..6613f88 100644 --- a/app/views/admin/supplementary_kit/index.html.erb +++ b/app/views/admin/supplementary_kit/index.html.erb @@ -1,9 +1,9 @@ <% provide :page_title, "Наборы" %> <%= render 'shared/admin/navbar/admin', selected: "device" %> -<%= render 'shared/admin/navbar/device_bread', selected: "supplementary_kit" %> <%= render 'shared/flash_alert' %> -
    -
    +
    +
    + <%= render 'shared/admin/navbar/device_bread', selected: "supplementary_kit" %>

    @@ -28,7 +28,7 @@ <%= f.search_field :description_cont, class:"form-control", placeholder:"Набор для..." %>

    -
    +
    <%= f.submit t('b_accept'), class: 'col btn btn-primary w-100'%>
    @@ -43,8 +43,9 @@
    <%= render 'shared/modal_button_add', path: new_admin_supplementary_kit_path, classes: "btn btn-primary w-100 mb-3", text: t("b_add") %>
    -
    -
    + +
    +
    <% @supplementary_kits.each do |supplementary_kit| %> <% sk_text = "Kit#{supplementary_kit.id}" %>
    @@ -77,6 +78,7 @@
    <% end %> +
    diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb index a0a15f1..6e7c32d 100644 --- a/app/views/admin/users/index.html.erb +++ b/app/views/admin/users/index.html.erb @@ -1,10 +1,10 @@ <% provide :page_title, "Панель администрирования" %> <%= render 'shared/admin/navbar/admin', selected: "user" %> -<%= render 'shared/admin/navbar/user_navbar', selected: "user" %> <%= render 'shared/flash_alert' %> -
    -
    -
    +
    +
    + <%= render 'shared/admin/navbar/user_navbar', selected: "user" %> +

    <%= search_form_for(@query, url:admin_users_path, method: :get, data:{controller: "filter"}, class: "rounded accordion-body") do |f| %> -
    +
    <%= f.label :tabel_id, class:"mb-1" %> <%= f.search_field :tabel_id_eq, class: 'form-control', placeholder: '1' %> @@ -78,12 +78,9 @@ <%= f.date_field :updated_at_lteq, class: 'form-control' %>
    -
    -
    -
    - <%= f.submit "Применить", class: 'col btn btn-primary w-100'%> -
    -
    + <%= f.submit t("b_accept"), class: 'btn btn-primary w-100 my-2'%> +
    + <%= t("b_clear")%>
    <% end %>
    @@ -93,9 +90,7 @@ <%= t('b_add')%> <% end %>
    -
    -
    -
    +
    @@ -159,3 +154,16 @@ + diff --git a/app/views/shared/admin/navbar/_device_bread.html.erb b/app/views/shared/admin/navbar/_device_bread.html.erb index 37299d4..2388dc5 100644 --- a/app/views/shared/admin/navbar/_device_bread.html.erb +++ b/app/views/shared/admin/navbar/_device_bread.html.erb @@ -1,11 +1,43 @@ -
    +
    <% selected = local_assigns[:selected] %> - <%= link_to 'Приборы', admin_device_index_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'device' }" %> - <%= link_to 'Модели', admin_device_model_index_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'device_model' }" %> - <%= link_to 'Компоненты', admin_device_component_index_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'device_component' }" %> - <%= link_to 'Наборы', admin_supplementary_kit_index_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'supplementary_kit' }" %> - <%= link_to 'Рег.группы', admin_device_reg_group_index_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'device_reg_group' }" %> - <%= link_to 'Производители', admin_manufacturer_index_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'manufacturer' }" %> - <%= link_to 'Классы измерений', admin_measurement_class_index_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'measurement_class' }" %> - <%= link_to 'Группы измерений', admin_measurement_group_index_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'measurement_group' }" %> +
    + <%= link_to admin_device_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'device' } text-start" do %> + <%= 'Приборы' %> + <% end %> +
    +
    + <%= link_to admin_device_model_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'device_model' } text-start" do %> + <%= 'Модели' %> + <% end %> +
    +
    + <%= link_to admin_device_component_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'device_component' } text-start" do %> + <%= 'Компоненты' %> + <% end %> +
    +
    + <%= link_to admin_supplementary_kit_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'supplementary_kit' } text-start" do %> + <%= 'Наборы' %> + <% end %> +
    +
    + <%= link_to admin_device_reg_group_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'device_reg_group' } text-start" do %> + <%= 'Рег.группы' %> + <% end %> +
    +
    + <%= link_to admin_manufacturer_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'manufacturer' } text-start" do %> + <%= 'Производители' %> + <% end %> +
    +
    + <%= link_to admin_measurement_class_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'measurement_class' } text-start" do %> + <%= 'Классы измерений' %> + <% end %> +
    +
    + <%= link_to admin_measurement_group_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'measurement_group' } text-start" do %> + <%= 'Группы измерений' %> + <% end %> +
    diff --git a/app/views/shared/admin/navbar/_location_navbar.html.erb b/app/views/shared/admin/navbar/_location_navbar.html.erb index 0c5103e..8bf3dd1 100644 --- a/app/views/shared/admin/navbar/_location_navbar.html.erb +++ b/app/views/shared/admin/navbar/_location_navbar.html.erb @@ -1,17 +1,17 @@
    <% selected = local_assigns[:selected] %>
    - <%= link_to admin_control_point_index_path, class: "btn w-100 #{ 'btn-primary' if selected == 'control_point' }" do %> + <%= link_to admin_control_point_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'control_point' } text-start" do %> <%= 'Точки контроля' %> <% end %>
    -
    - <%= link_to admin_control_point_index_path, class: "btn w-100 #{ 'btn-primary' if selected == 'room' }" do %> +
    + <%= link_to admin_control_point_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'room' } text-start" do %> <%= 'Помещения' %> <% end %>
    - <%= link_to admin_control_point_index_path, class: "btn w-100 #{ 'btn-primary' if selected == 'building' }" do %> + <%= link_to admin_control_point_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'building' } text-start" do %> <%= 'Здания' %> <% end %>
    diff --git a/app/views/shared/admin/navbar/_user_navbar.html.erb b/app/views/shared/admin/navbar/_user_navbar.html.erb index 39d8850..3f22cca 100644 --- a/app/views/shared/admin/navbar/_user_navbar.html.erb +++ b/app/views/shared/admin/navbar/_user_navbar.html.erb @@ -1,15 +1,23 @@ -
    +
    <% selected = local_assigns[:selected] %> - <%= link_to admin_users_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'user' }" do %> - <%= 'Пользователи' %> - <% end %> - <%= link_to admin_users_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'service' }" do %> - <%= 'Службы' %> - <% end %> - <%= link_to admin_users_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'division' }" do %> - <%= 'Подразделения' %> - <% end %> - <%= link_to admin_users_path, class: "px-0 col btn #{ 'btn-primary' if selected == 'organization' }" do %> - <%= 'Организации' %> - <% end %> +
    + <%= link_to admin_users_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'user' } text-start" do %> + <%= 'Пользователи' %> + <% end %> +
    +
    + <%= link_to admin_users_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'service' } text-start" do %> + <%= 'Службы' %> + <% end %> +
    +
    + <%= link_to admin_users_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'division' } text-start" do %> + <%= 'Подразделения' %> + <% end %> +
    +
    + <%= link_to admin_users_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'organization' } text-start" do %> + <%= 'Организации' %> + <% end %> +
    diff --git a/app/views/shared/index/_device.html.erb b/app/views/shared/index/_device.html.erb index 9013d35..d9af8d5 100644 --- a/app/views/shared/index/_device.html.erb +++ b/app/views/shared/index/_device.html.erb @@ -1,7 +1,10 @@ <% provide :page_title, "Приборы" %> <% show_path = show_path.from(0).to(show_path.length-2) %>
    -
    +
    + <% if current_page?(admin_device_index_path) %> + <%= render 'shared/admin/navbar/device_bread', selected: "device" %> + <% end %>

    @@ -70,7 +73,7 @@ <%=t('b_download_pdf')%> <% end %>

    -
    +
    From 10a6bc79edb02f19bb3e928b58786e43028f363a Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 29 Jan 2024 10:21:10 +0400 Subject: [PATCH 069/132] =?UTF-8?q?UPD:=20=D0=BD=D0=B5=D0=BC=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D1=8F=D0=B5?= =?UTF-8?q?=D1=82=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=83=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D0=BD=D0=B0,=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D1=81=D1=81=D1=8B=D0=BB?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=B7=D0=B5=D1=80=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D0=BE,=20=D1=83=D0=B1=D0=B8=D1=80=D0=B0=D0=B5=D1=82=20=D0=BA?= =?UTF-8?q?=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20=D1=83=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=B2=D0=B2=D0=BE=D0=B4=D0=B0=20=D1=82=D0=B0=D0=B1?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BD=D0=BE=D0=BC?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=20(=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20?= =?UTF-8?q?=D0=BA=D1=80=D0=B0=D1=81=D0=B8=D0=B2=D0=BE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/devise/sessions/new.html.erb | 10 ++++++++++ app/views/shared/_about.html.erb | 20 +++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 1bdecb3..5408152 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -1,4 +1,14 @@
    +
    diff --git a/app/views/shared/_about.html.erb b/app/views/shared/_about.html.erb index c6efe8a..4c3a2cb 100644 --- a/app/views/shared/_about.html.erb +++ b/app/views/shared/_about.html.erb @@ -15,17 +15,26 @@

    - Вся платформа и её компоненты распространяется по лицензии <%= link_to "GNU GPLv3", license_path(:ru) %>, являеются open-source продуктом и разрабатывается силами сообщества. + Наши репозитории: внешний, локальное зеркало

    - Наш репозиторий digital-armstrong + Баги и преложения сюда: + внешний, + локальное зеркало.

    Разработчики:

    +
    +

    + Вся платформа и её компоненты распространяется по лицензии <%= link_to "GNU GPLv3", license_path(:ru) %>, являеются open-source продуктом и разрабатывается силами сообщества. +

    +
    From d775ce9d38d12a9a01f5f69412872181ff20aacc Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 29 Jan 2024 11:54:26 +0400 Subject: [PATCH 070/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=D0=B5=D1=80=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D1=83=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=84?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=A1=D0=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/admin/device_model/index.html.erb | 89 +++++++-------------- config/locales/ru.yml | 24 +++--- 2 files changed, 40 insertions(+), 73 deletions(-) diff --git a/app/views/admin/device_model/index.html.erb b/app/views/admin/device_model/index.html.erb index bc30512..4b83826 100644 --- a/app/views/admin/device_model/index.html.erb +++ b/app/views/admin/device_model/index.html.erb @@ -14,73 +14,40 @@ <%= search_form_for(@query, url:admin_device_model_index_path, method: :get, data:{controller: "filter", 'filtrator_class': 'filtrator-class-index'}, class: "rounded accordion-body") do |f| %>
    -
    -
    - <%= f.label :name, class:"mb-1"%> - <%= f.search_field :name_cont, class: 'form-control', placeholder: 'Название' %> -
    -
    - <%= f.label :manufacturer_id, class:"mb-1" %> - <%= f.collection_select(:manufacturer_id_eq, Manufacturer.all, + <%= f.label :name, class:"mb-2"%> + <%= f.search_field :name_cont, class: 'form-control', placeholder: 'Название' %> + <%= f.label :manufacturer_id, class:"mb-2" %> + <%= f.collection_select(:manufacturer_id_eq, Manufacturer.all, :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select'}) %> -
    -
    - <%= f.label :measurement_group_id, class:"mb-1" %> - <%= f.collection_select(:measurement_group_id_eq, MeasurementGroup.all, + <%= f.label :measurement_group_id, class:"mb-2" %> + <%= f.collection_select(:measurement_group_id_eq, MeasurementGroup.all, :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select', id:"filtrator-class-index", data: {'action': 'onchange -> filter#filter', 'to_filter_class': 'filter-class-index', 'filtrator_class': 'filtrator-class-index'}}) %> -
    -
    - <%= f.label :measurement_class_id, class:"mb-1" %> - <%= f.collection_select(:measurement_class_id_eq, MeasurementClass.all, + <%= f.label :measurement_class_id, class:"mb-2" %> + <%= f.collection_select(:measurement_class_id_eq, MeasurementClass.all, :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select', id: "filter-class-index"}) %> -
    -
    - <%= f.label :measuring_unit, class:"mb-1" %> - <%= f.search_field :measuring_unit_cont, class: 'form-control', placeholder: 'мЗв/ч' %> -
    -
    -
    -
    - <%= f.label :measurement_sensitivity, class:"mb-1" %> - <%= f.search_field :measurement_sensitivity_eq, class: 'form-control', placeholder: '1.0' %> -
    -
    - <%= f.label :measurement_min, class:"mb-1" %> - <%= f.search_field :measurement_min_eq, class: 'form-control', placeholder: '1.0' %> -
    -
    - <%= f.label :measurement_max, class:"mb-1" %> - <%= f.search_field :measurement_max_eq, class: 'form-control', placeholder: '1.0' %> -
    -
    - <%= f.label :safety_class, class:"mb-1" %> - <%= f.search_field :safety_class_cont, class: 'form-control', placeholder: '3Н' %> -
    -
    -
    -
    - <%= f.label :accuracy_class, class:"mb-1" %> - <%= f.search_field :accuracy_class_eq, class: 'form-control', placeholder: '1.0' %> -
    -
    - <%= f.label :is_complete_device, class:"mb-1" %> - <%= f.select(:is_complete_device_eq, [['Да', true], ['Нет', false]], + <%= f.label :measuring_unit, class:"mb-2" %> + <%= f.search_field :measuring_unit_cont, class: 'form-control', placeholder: 'мЗв/ч' %> + <%= f.label :measurement_sensitivity, class:"mb-2" %> + <%= f.search_field :measurement_sensitivity_eq, class: 'form-control', placeholder: '1.0' %> + <%= f.label :measurement_min, class:"mb-2" %> + <%= f.search_field :measurement_min_eq, class: 'form-control', placeholder: '1.0' %> + <%= f.label :measurement_max, class:"mb-2" %> + <%= f.search_field :measurement_max_eq, class: 'form-control', placeholder: '1.0' %> + <%= f.label :safety_class, class:"mb-2" %> + <%= f.search_field :safety_class_cont, class: 'form-control', placeholder: '3Н' %> + <%= f.label :accuracy_class, class:"mb-2" %> + <%= f.search_field :accuracy_class_eq, class: 'form-control', placeholder: '1.0' %> + <%= f.label :is_complete_device, class:"mb-2" %> + <%= f.select(:is_complete_device_eq, [['Да', true], ['Нет', false]], {:include_blank => t('combobox_blank')}, {:class =>'form-select form-select'})%> -
    -
    - <%= f.label :is_tape_rolling_mechanism, class:"mb-1" %> + <%= f.label :is_tape_rolling_mechanism, class:"mb-2" %> <%= f.select(:is_tape_rolling_mechanism_eq, [['Да', true], ['Нет', false]], - {:include_blank => t('combobox_blank')}, {:class =>'form-select form-select'})%> -
    -
    -
    -
    -
    - <%= f.submit t('b_accept'), class: 'col btn btn-primary w-100'%> -
    + {:include_blank => t('combobox_blank')}, {:class =>'form-select form-select mb-2'})%> + + <%= f.submit t('b_accept'), class: 'col btn btn-primary w-100 my-2'%> +
    <%= t("b_clear")%>
    -
    <% end %>
    @@ -92,7 +59,7 @@
    - + diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 349bb90..0392064 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -223,19 +223,19 @@ ru: name: Название manufacturer: Производитель manufacturer_id: Производитель - measurement_group: Группа измерений - measurement_group_id: Группа измерений - measurement_class: Класс измерений - measurement_class_id: Класс измерений - measuring_unit: Ед. измерений - measurement_sensitivity: Чувств. измерений - measurement_min: Мин. величина измерений - measurement_max: Макс. величина измерений - safety_class: Класс безопасности - accuracy_class: Класс точности + measurement_group: Группа изм. + measurement_group_id: Группа изм. + measurement_class: Класс изм. + measurement_class_id: Класс изм. + measuring_unit: Ед. изм. + measurement_sensitivity: Чувств. изм. + measurement_min: Мин. вел. изм. + measurement_max: Макс. вел. изм. + safety_class: Класс без-ти + accuracy_class: Класс точ-ти is_complete_device: Полный набор? - is_tape_rolling_mechanism: Имеет лентопротяжный механизм? - measurement_range: Диапазон измерений + is_tape_rolling_mechanism: ЛП механизм? + measurement_range: Диапазон изм. calibration_max: Макс. калибр. calibration_min: Мин. калибр. device_reg_group: From f0a1c0a499d2cedb4718aa689fd6e246c9c6a1e8 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 29 Jan 2024 12:05:59 +0400 Subject: [PATCH 071/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=D0=B5=D1=80=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D1=83=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=84?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20=D0=A1?= =?UTF-8?q?=D0=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/device_component/index.html.erb | 43 ++++++------------- config/locales/ru.yml | 8 ++-- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/app/views/admin/device_component/index.html.erb b/app/views/admin/device_component/index.html.erb index 78632b2..6494867 100644 --- a/app/views/admin/device_component/index.html.erb +++ b/app/views/admin/device_component/index.html.erb @@ -14,33 +14,18 @@
    <%= search_form_for(@query, url:admin_device_component_index_path, method: :get, class: "rounded accordion-body") do |f| %>
    -
    -
    - <%= f.label :serial_id %> - <%= f.search_field :serial_id_cont, class:"form-control", placeholder:"0-123-N" %> -
    -
    - <%= f.label :name %> - <%= f.search_field :name_cont, class:"form-control", placeholder:"БДЗБ" %> -
    -
    - <%= f.label :measurement_min %> - <%= f.search_field :measurement_min_eq, class:"form-control", placeholder:"1.0" %> -
    -
    - <%= f.label :measurement_max %> - <%= f.search_field :measurement_max_eq, class:"form-control", placeholder:"2.0" %> -
    -
    - <%= f.label :measuring_unit %> - <%= f.search_field :measuring_unit_cont, class:"form-control", placeholder:"мЗв/ч" %> -
    -
    -
    -
    -
    - <%= f.submit t('b_accept'), class: 'col btn btn-primary w-100'%> -
    + <%= f.label :serial_id %> + <%= f.search_field :serial_id_cont, class:"form-control mb-2", placeholder:"0-123-N" %> + <%= f.label :name %> + <%= f.search_field :name_cont, class:"form-control mb-2", placeholder:"БДЗБ" %> + <%= f.label :measurement_min %> + <%= f.search_field :measurement_min_eq, class:"form-control mb-2", placeholder:"1.0" %> + <%= f.label :measurement_max %> + <%= f.search_field :measurement_max_eq, class:"form-control mb-2", placeholder:"2.0" %> + <%= f.label :measuring_unit %> + <%= f.search_field :measuring_unit_cont, class:"form-control mb-2", placeholder:"мЗв/ч" %> + <%= f.submit t('b_accept'), class: 'my-2 btn btn-primary w-100'%> +
    <%= t("b_clear")%>
    @@ -55,13 +40,13 @@
    <%= t('activerecord.attributes.device_model.name')%> <%= t('activerecord.attributes.device_model.manufacturer')%> <%= t('activerecord.attributes.device_model.measurement_group')%>
    - + - + diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 0392064..6d9bea4 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -254,11 +254,11 @@ ru: measurement_group: name: Название device_component: - serial_id: Серийный номер + serial_id: Сер. номер name: Название - measurement_min: Мин. величина измерений - measurement_max: Макс. величина измерений - measuring_unit: Единица измерений + measurement_min: Мин. вел. изм. + measurement_max: Макс. величина изм. + measuring_unit: Ед. изм. description: Описание supplementary_kit: Набор supplementary_kit: From a9fa11f69e3d86ee23a2f17b06add3b6fb55de78 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 29 Jan 2024 12:13:43 +0400 Subject: [PATCH 072/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=D0=B5=D1=80=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D1=83=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=84?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D0=BE=D0=B2=20=D0=A1=D0=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/supplementary_kit/index.html.erb | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/app/views/admin/supplementary_kit/index.html.erb b/app/views/admin/supplementary_kit/index.html.erb index 6613f88..f3dd2df 100644 --- a/app/views/admin/supplementary_kit/index.html.erb +++ b/app/views/admin/supplementary_kit/index.html.erb @@ -14,28 +14,16 @@
    <%= search_form_for(@query, url:admin_supplementary_kit_index_path, method: :get, class: "rounded accordion-body") do |f| %>
    -
    -
    - <%= f.label :name %> - <%= f.search_field :name_cont, class:"form-control", placeholder:"БДЗБ" %> -
    -
    - <%= f.label :serial_id %> - <%= f.search_field :serial_id_cont, class:"form-control", placeholder:"0-123-N" %> -
    -
    - <%= f.label :description %> - <%= f.search_field :description_cont, class:"form-control", placeholder:"Набор для..." %> -
    + <%= f.label :name %> + <%= f.search_field :name_cont, class:"form-control mb-2", placeholder:"БДЗБ" %> + <%= f.label :serial_id %> + <%= f.search_field :serial_id_cont, class:"form-control mb-2", placeholder:"0-123-N" %> + <%= f.label :description %> + <%= f.search_field :description_cont, class:"form-control mb-2", placeholder:"Набор для..." %> + <%= f.submit t('b_accept'), class: 'my-2 btn btn-primary w-100'%> +
    + <%= t("b_clear")%>
    -
    -
    - <%= f.submit t('b_accept'), class: 'col btn btn-primary w-100'%> -
    - <%= t("b_clear")%> -
    -
    -
    <% end %>
    From 9ed26fdfa382d1f078531c8586670adb84a0fb2f Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 29 Jan 2024 12:15:46 +0400 Subject: [PATCH 073/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=D0=B5=D1=80=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D1=83=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=84?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=86=D0=B8=D0=BE=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=20=D0=A1?= =?UTF-8?q?=D0=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/admin/device_reg_group/index.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/admin/device_reg_group/index.html.erb b/app/views/admin/device_reg_group/index.html.erb index 908c8f2..b713ec5 100644 --- a/app/views/admin/device_reg_group/index.html.erb +++ b/app/views/admin/device_reg_group/index.html.erb @@ -14,10 +14,10 @@
    <%= search_form_for(@query, url:admin_device_reg_group_index_path, method: :get, class: "rounded accordion-body") do |f| %>
    - <%= f.label :name, class: "mb-2" %> - <%= f.search_field :name_cont, class:"form-control", placeholder:"Название" %> + <%= f.label :name %> + <%= f.search_field :name_cont, class:"form-control mb-2", placeholder:"Название" %>
    - <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%> + <%= f.submit t('b_accept'), class: 'my-2 btn btn-primary w-100 my-2'%>
    <%= t("b_clear")%>
    From 94c2cc8b7f1ebf488139ab484e6b549c6de61d15 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 29 Jan 2024 12:23:25 +0400 Subject: [PATCH 074/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=D0=B5=D1=80=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D1=83=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=84?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B8=D0=B7=D0=B2=D0=BE=D0=B4=D0=B8=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=B9=20=D0=A1=D0=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/admin/manufacturer/index.html.erb | 44 +++++++-------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/app/views/admin/manufacturer/index.html.erb b/app/views/admin/manufacturer/index.html.erb index 9448d4f..fc946f3 100644 --- a/app/views/admin/manufacturer/index.html.erb +++ b/app/views/admin/manufacturer/index.html.erb @@ -14,36 +14,20 @@
    <%= search_form_for(@query, url:admin_manufacturer_index_path, method: :get, class: "rounded accordion-body") do |f| %>
    -
    -
    - <%= f.label :name, class: "mb-2" %> - <%= f.search_field :name_cont, class:"form-control", placeholder:"Название" %> -
    -
    - <%= f.label :adress, class: "my-2" %> - <%= f.search_field :adress_cont, class:"form-control", placeholder:"г. Димитровград, Победы 12" %> -
    -
    - <%= f.label :phone, class: "my-2" %> - <%= f.search_field :phone_cont, class:"form-control", placeholder:"+7 (999) 123-01-02" %> -
    -
    - <%= f.label :email, class: "my-2" %> - <%= f.search_field :email_cont, class:"form-control", placeholder:"niiar@example.ru" %> -
    -
    - <%= f.label :site_url, class: "my-2" %> - <%= f.search_field :site_url_cont, class:"form-control", placeholder:"example.ru" %> -
    -
    -
    -
    -
    - <%= f.submit "Применить", class: 'col btn btn-primary w-100'%> -
    + <%= f.label :name %> + <%= f.search_field :name_cont, class:"form-control mb-2", placeholder:"Название" %> + <%= f.label :adress %> + <%= f.search_field :adress_cont, class:"form-control mb-2", placeholder:"г. Димитровград, Победы 12" %> + <%= f.label :phone %> + <%= f.search_field :phone_cont, class:"form-control mb-2", placeholder:"+7 (999) 123-01-02" %> + <%= f.label :email %> + <%= f.search_field :email_cont, class:"form-control mb-2", placeholder:"niiar@example.ru" %> + <%= f.label :site_url %> + <%= f.search_field :site_url_cont, class:"form-control mb-2", placeholder:"example.ru" %> + <%= f.submit "Применить", class: 'my-2 btn btn-primary w-100'%> +
    <%= t("b_clear")%>
    -
    <% end %>
    @@ -69,8 +53,8 @@
    - - + + - + diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 6d9bea4..c0c3239 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -248,8 +248,8 @@ ru: site_url: Ссылка на сайт measurement_class: name: Название - measurement_group: Группа измерений - measurement_group_id: Группа измерений + measurement_group: Группа изм. + measurement_group_id: Группа изм. arms_device_type: ARMS-тип measurement_group: name: Название From 8082d2724ff3b547109f8cc96e2b0425f5a78f36 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 29 Jan 2024 12:51:09 +0400 Subject: [PATCH 076/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=D0=B5=D1=80=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D1=83=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=84?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=9F=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/admin/users/index.html.erb | 110 ++++++++++----------------- 1 file changed, 41 insertions(+), 69 deletions(-) diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb index 6e7c32d..b537902 100644 --- a/app/views/admin/users/index.html.erb +++ b/app/views/admin/users/index.html.erb @@ -14,73 +14,45 @@
    <%= search_form_for(@query, url:admin_users_path, method: :get, data:{controller: "filter"}, class: "rounded accordion-body") do |f| %>
    -
    - <%= f.label :tabel_id, class:"mb-1" %> - <%= f.search_field :tabel_id_eq, class: 'form-control', placeholder: '1' %> -
    -
    - <%= f.label :first_name, class:"mb-1"%> - <%= f.search_field :first_name_cont, class: 'form-control', placeholder: 'Иван' %> -
    -
    - <%= f.label :last_name, class:"mb-1" %> - <%= f.search_field :last_name_cont, class: 'form-control', placeholder: 'Иванов' %> -
    -
    - <%= f.label :second_name, class:"mb-1" %> - <%= f.search_field :second_name_cont, class: 'form-control', placeholder: 'Иванович' %> -
    -
    -
    -
    - <%= f.label :role, class:"mb-1" %> - + + <% User::ROLES.each do |key,value| %> + <% if key == @selected_role %> +
    -
    - <%= f.label :email, class:"mb-1" %> - <%= f.search_field :email_cont, class: 'form-control', placeholder: 'Иванович' %> -
    -
    - <%= f.label :phone, class:"mb-1" %> - <%= f.search_field :phone_cont, class: 'form-control', placeholder: '+79021234578' %> + <%= (value) %> + <% end %> + + <%= f.label :email %> + <%= f.search_field :email_cont, class: 'form-control mb-2', placeholder: 'Иванович' %> + <%= f.label :phone %> + <%= f.search_field :phone_cont, class: 'form-control mb-2', placeholder: '+79021234578' %> + <%= f.label :service %> + <%= f.collection_select(:service_id_eq, Service.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select'}) %> + <%= f.label t('.created_at_from') %> + <%= f.date_field :created_at_gteq, class: 'form-control mb-2 ' %> + <%= f.label t('.created_at_to') %> + <%= f.date_field :created_at_lteq, class: 'form-control mb-2' %> + <%= f.label t('.updated_at_from') %> + <%= f.date_field :updated_at_gteq, class: 'form-control mb-2' %> + <%= f.label t('.updated_at_to') %> + <%= f.date_field :updated_at_lteq, class: 'form-control mb-2' %> + <%= f.submit t("b_accept"), class: 'btn btn-primary w-100 my-2'%> +
    + <%= t("b_clear")%>
    -
    - <%= f.label :service, class:"mb-1" %> - <%= f.collection_select(:service_id_eq, Service.all, - :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select'}) %> -
    -
    -
    -
    - <%= f.label t('.created_at_from'), class:"mb-1" %> - <%= f.date_field :created_at_gteq, class: 'form-control ' %> -
    -
    - <%= f.label t('.created_at_to'), class:"mb-1" %> - <%= f.date_field :created_at_lteq, class: 'form-control' %> -
    -
    - <%= f.label t('.updated_at_from'), class:"mb-1" %> - <%= f.date_field :updated_at_gteq, class: 'form-control' %> -
    -
    - <%= f.label t('.updated_at_to'), class:"mb-1" %> - <%= f.date_field :updated_at_lteq, class: 'form-control' %> -
    -
    - <%= f.submit t("b_accept"), class: 'btn btn-primary w-100 my-2'%> -
    - <%= t("b_clear")%>
    <% end %>
    @@ -94,7 +66,7 @@
    <%= t('activerecord.attributes.device_component.serial_id')%> <%= t('activerecord.attributes.device_component.name')%> <%= t('activerecord.attributes.device_component.supplementary_kit')%> <%= t('activerecord.attributes.device_component.measurement_min')%> <%= t('activerecord.attributes.device_component.measurement_max')%><%= t('activerecord.attributes.device_component.measurement_unit')%><%= t('activerecord.attributes.device_component.measuring_unit')%> <%= t('activerecord.attributes.device_component.description')%> <%= t('action')%>
    <%= manufacturer.name %> <%= manufacturer.adress %><%= manufacturer.phone %><%= manufacturer.email %><%= manufacturer.phone %><%= manufacturer.email %> <%= manufacturer.site_url %>
    From cca9a18af90fe671972b91667bc7e74990a58e53 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 29 Jan 2024 12:30:44 +0400 Subject: [PATCH 075/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=D0=B5=D1=80=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D1=83=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D1=84?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=81=D0=BE=D0=B2=20=D0=B8=D0=B7=D0=BC=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=A1=D0=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/admin/measurement_class/index.html.erb | 16 ++++++++-------- config/locales/ru.yml | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/views/admin/measurement_class/index.html.erb b/app/views/admin/measurement_class/index.html.erb index f144f18..6364567 100644 --- a/app/views/admin/measurement_class/index.html.erb +++ b/app/views/admin/measurement_class/index.html.erb @@ -14,13 +14,13 @@
    <%= search_form_for(@query, url: admin_measurement_class_index_path, method: :get, class: "rounded accordion-body") do |f| %>
    - <%= f.label :name, class: "mb-2" %> - <%= f.search_field :name_cont, class:"form-control", placeholder:"Название" %> - <%= f.label :measurement_group_id, class:"mb-1" %> - <%= f.collection_select(:measurement_group_id_eq, MeasurementGroup.all, - :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select'}) %> - <%= f.label :arms_device_type, class: "my-2" %> - <%= f.search_field :arms_device_type_eq, class:"form-control", placeholder:"1" %> + <%= f.label :name %> + <%= f.search_field :name_cont, class:"form-control mb-2", placeholder:"Название" %> + <%= f.label :measurement_group_id %> + <%= f.collection_select(:measurement_group_id_eq, MeasurementGroup.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select mb-2'}) %> + <%= f.label :arms_device_type %> + <%= f.search_field :arms_device_type_eq, class:"form-control mb-2", placeholder:"1" %>
    <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%>
    @@ -39,7 +39,7 @@
    <%= t('activerecord.attributes.measurement_class.name') %> <%= t('activerecord.attributes.measurement_class.arms_device_type') %><%= t('activerecord.attributes.measurement_class.measuremnt_group') %><%= t('activerecord.attributes.measurement_class.measurement_group') %> <%= t('action') %>
    - + @@ -116,9 +88,9 @@ - - - + + + + @@ -51,6 +52,11 @@ <% else %> <% end %> + <% if building.description.nil? %> + + <% else %> + + <% end %> + @@ -55,6 +56,11 @@ <% else %> <% end %> + <% if room.description.nil? %> + + <% else %> + + <% end %> + <% @buildings.each do |building| %> + + + + <% if building.organization.nil? %> + + <% else %> + + <% end %> + <% if building.description.nil? %> + + <% else %> + + <% end %> + + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_building_path(building.id), + destroy_path: admin_building_path(building.id) %> + + <% end %> + +
    <%=t('activerecord.attributes.user.tabel_id')%> <%=t('activerecord.attributes.user.last_name')%> <%=t('activerecord.attributes.user.first_name')%><%= user.first_name %> <%= user.second_name %> <%= user.role %><%= user.email %><%= user.phone %><%= user.service.name %><%= user.email %><%= user.phone %><%= user.service.name %> <%= formatted_date(user.created_at, :short_full) %> <%= formatted_date(user.updated_at, :short_full) %> From ae6cd94ffe015c754629fbe12cc088cd5e01023a Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 29 Jan 2024 15:07:34 +0400 Subject: [PATCH 077/132] =?UTF-8?q?UPD:=20=D0=B4=D0=BB=D1=8F=20=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=B5=D0=BA=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5?= =?UTF-8?q?=D1=82=20=D1=81=D0=B5=D0=BB=D0=B5=D0=BA=D1=82=D0=BE=D1=80=20?= =?UTF-8?q?=D1=81=D0=BB=D1=83=D0=B6=D0=B1=D1=8B=20=D0=BD=D0=B0=20=D0=B2?= =?UTF-8?q?=D1=81=D0=B5=20=D0=BD=D1=83=D0=B6=D0=BD=D1=8B=D0=B5=20=D0=B2?= =?UTF-8?q?=D1=8C=D1=8E=D1=88=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/control_point_controller.rb | 2 ++ app/controllers/concerns/control_point_concern.rb | 3 +++ app/models/control_point.rb | 5 +++-- app/models/service.rb | 1 + app/views/shared/admin/form/_control_point.html.erb | 7 +++++++ db/migrate/20240129094419_add_columnt_to_control_point.rb | 5 +++++ db/schema.rb | 5 ++++- db/seeds.rb | 1 + 8 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20240129094419_add_columnt_to_control_point.rb diff --git a/app/controllers/admin/control_point_controller.rb b/app/controllers/admin/control_point_controller.rb index 51f270c..2ea2021 100644 --- a/app/controllers/admin/control_point_controller.rb +++ b/app/controllers/admin/control_point_controller.rb @@ -53,6 +53,8 @@ def control_point_params :room_id, :device_id, :channel_id, + :description, + :service_id, ) end end diff --git a/app/controllers/concerns/control_point_concern.rb b/app/controllers/concerns/control_point_concern.rb index bd32ec6..17d551f 100644 --- a/app/controllers/concerns/control_point_concern.rb +++ b/app/controllers/concerns/control_point_concern.rb @@ -4,6 +4,7 @@ module ControlPointConcern included do def control_point_create @control_point = ControlPoint.new(control_point_params) + if @control_point.save redirect_back(fallback_location: root_path) else @@ -17,6 +18,8 @@ def control_point_params :device_id, :room_id, :channel_id, + :description, + :service_id, ) end end diff --git a/app/models/control_point.rb b/app/models/control_point.rb index d211315..a0bd034 100644 --- a/app/models/control_point.rb +++ b/app/models/control_point.rb @@ -1,16 +1,17 @@ class ControlPoint < ApplicationRecord belongs_to :room, optional: true belongs_to :device, optional: true + belongs_to :service has_one :channel validates :name, presence: true def self.ransackable_attributes(_auth_object = nil) - ['channel_id', 'created_at', 'description', 'device_id', 'id', 'name', 'room_id', 'updated_at'] + ['channel_id', 'created_at', 'description', 'device_id', 'id', 'name', 'room_id', 'service_id', 'updated_at'] end def self.ransackable_associations(_auth_object = nil) - ['channel', 'device', 'room'] + ['channel', 'device', 'room', 'service'] end end diff --git a/app/models/service.rb b/app/models/service.rb index 6a3daf8..c8f9d68 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -6,4 +6,5 @@ class Service < ApplicationRecord has_many :devices has_many :users has_many :servers + has_many :control_points end diff --git a/app/views/shared/admin/form/_control_point.html.erb b/app/views/shared/admin/form/_control_point.html.erb index 94b428b..2c41434 100644 --- a/app/views/shared/admin/form/_control_point.html.erb +++ b/app/views/shared/admin/form/_control_point.html.erb @@ -23,6 +23,13 @@ <%= f.input :description, placeholder: 'Описание точки контроля', input_html: {:tabindex => 4}%> + <% if current_user.admin? %> +
    + <%= f.association :service, input_html: {:tabindex => 9}, include_blank: false %> +
    + <% else %> + <%= f.input :service_id, label: false, input_html: {hidden: true, value: current_user.service_id} %> + <% end %> diff --git a/db/migrate/20240129094419_add_columnt_to_control_point.rb b/db/migrate/20240129094419_add_columnt_to_control_point.rb new file mode 100644 index 0000000..9742ac0 --- /dev/null +++ b/db/migrate/20240129094419_add_columnt_to_control_point.rb @@ -0,0 +1,5 @@ +class AddColumntToControlPoint < ActiveRecord::Migration[7.0] + def change + add_reference :control_points, :service, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 42cf7b6..009c86d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_01_25_082316) do +ActiveRecord::Schema[7.0].define(version: 2024_01_29_094419) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -56,8 +56,10 @@ t.bigint "device_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "service_id" t.index ["device_id"], name: "index_control_points_on_device_id" t.index ["room_id"], name: "index_control_points_on_room_id" + t.index ["service_id"], name: "index_control_points_on_service_id" end create_table "device_components", force: :cascade do |t| @@ -277,6 +279,7 @@ add_foreign_key "channels", "services" add_foreign_key "control_points", "devices" add_foreign_key "control_points", "rooms" + add_foreign_key "control_points", "services" add_foreign_key "device_components", "supplementary_kits" add_foreign_key "device_models", "manufacturers" add_foreign_key "device_models", "measurement_classes" diff --git a/db/seeds.rb b/db/seeds.rb index e32f667..4c48dbd 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -358,6 +358,7 @@ description: "Возможное описание", room: Room.find_by(id: rand(1..99)), device: Device.find_by(id: rand(1..100)), + service: Service.find_by(id: rand(1..10)), ) end From dfb484d274b0f577e2864f1c57b1e418121578c1 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 30 Jan 2024 14:46:38 +0400 Subject: [PATCH 078/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BB=D1=83?= =?UTF-8?q?=D0=B6=D0=B1=20=D0=B2=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA?= =?UTF-8?q?=D0=B5=20(=D0=B1=D0=B5=D0=B7=20=D1=80=D1=83=D1=81=D0=B8=D1=84?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/service_controller.rb | 64 ++++++++++ app/controllers/concerns/service_concern.rb | 24 ++++ app/models/building.rb | 4 + app/models/division.rb | 4 + app/models/service.rb | 11 ++ .../admin/service/create.turbo_stream.erb | 1 + app/views/admin/service/edit.turbo_stream.erb | 3 + app/views/admin/service/index.html.erb | 117 ++++++++++++++++++ app/views/admin/service/new.turbo_stream.erb | 3 + .../admin/service/update.turbo_stream.erb | 1 + app/views/shared/admin/form/_service.html.erb | 12 ++ .../shared/admin/navbar/_user_navbar.html.erb | 2 +- config/routes.rb | 5 +- 13 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 app/controllers/admin/service_controller.rb create mode 100644 app/controllers/concerns/service_concern.rb create mode 100644 app/views/admin/service/create.turbo_stream.erb create mode 100644 app/views/admin/service/edit.turbo_stream.erb create mode 100644 app/views/admin/service/index.html.erb create mode 100644 app/views/admin/service/new.turbo_stream.erb create mode 100644 app/views/admin/service/update.turbo_stream.erb create mode 100644 app/views/shared/admin/form/_service.html.erb diff --git a/app/controllers/admin/service_controller.rb b/app/controllers/admin/service_controller.rb new file mode 100644 index 0000000..d9660d4 --- /dev/null +++ b/app/controllers/admin/service_controller.rb @@ -0,0 +1,64 @@ +class Admin::ServiceController < ApplicationController + include ServiceConcern + + load_and_authorize_resource + before_action :set_service, only: [:show, :edit, :update, :destroy] + + def index + @query = Service.ransack(params[:q]) + @pagy, @services = pagy(@query.result.order(:name)) + end + + def new + authorize!(:new, :service_admin) + @service = Service.new + end + + def create + authorize!(:create, :service_admin) + service_create + end + + def update + authorize!(:update, :service_admin) + + if @service.update(service_params) + redirect_back(fallback_location: root_path) + else + render(:edit, status: :unprocessable_entity) + end + end + + def destroy + authorize!(:destroy, :service_admin) + + assigned_models_count = + Device.where(service_id: params[:id]).count + + User.where(service_id: params[:id]).count + + Server.where(service_id: params[:id]).count + + ControlPoint.where(service_id: params[:id]).count + + if assigned_models_count.zero? + @service.destroy + else + flash[:error] = t('message.service.delete.error') + end + redirect_to(admin_service_index_path) + end + + + private + + def set_service + @service = Service.find(params[:id]) + end + + def service_params + params.require(:service).permit( + :name, + :division_id, + :organization_id, + :building_id, + ) + end +end diff --git a/app/controllers/concerns/service_concern.rb b/app/controllers/concerns/service_concern.rb new file mode 100644 index 0000000..42c5528 --- /dev/null +++ b/app/controllers/concerns/service_concern.rb @@ -0,0 +1,24 @@ +module ServiceConcern + extend ActiveSupport::Concern + + included do + def service_create + @service = Service.new(service_params) + + if @service.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def service_params + params.require(:service).permit( + :name, + :division_id, + :organization_id, + :building_id, + ) + end + end +end diff --git a/app/models/building.rb b/app/models/building.rb index 5cc3a87..660876d 100644 --- a/app/models/building.rb +++ b/app/models/building.rb @@ -6,4 +6,8 @@ class Building < ApplicationRecord has_many :servers validates :name, presence: true + + def self.ransackable_attributes(auth_object = nil) + ["created_at", "id", "name", "organization_id", "updated_at"] + end end diff --git a/app/models/division.rb b/app/models/division.rb index 336c209..24bc9c7 100644 --- a/app/models/division.rb +++ b/app/models/division.rb @@ -4,4 +4,8 @@ class Division < ApplicationRecord has_many :services validates :name, presence: true + + def self.ransackable_attributes(auth_object = nil) + ["created_at", "id", "name", "organization_id", "updated_at"] + end end diff --git a/app/models/service.rb b/app/models/service.rb index c8f9d68..cd401de 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -7,4 +7,15 @@ class Service < ApplicationRecord has_many :users has_many :servers has_many :control_points + + validates :name, :organization, :division, :building, presence: true + validates :name, uniqueness: true + + def self.ransackable_attributes(auth_object = nil) + ["building_id", "created_at", "division_id", "id", "name", "organization_id", "updated_at"] + end + + def self.ransackable_associations(auth_object = nil) + ["building", "control_points", "devices", "division", "organization", "servers", "users"] + end end diff --git a/app/views/admin/service/create.turbo_stream.erb b/app/views/admin/service/create.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/service/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/admin/service/edit.turbo_stream.erb b/app/views/admin/service/edit.turbo_stream.erb new file mode 100644 index 0000000..4ebae7e --- /dev/null +++ b/app/views/admin/service/edit.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('.edit_service') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/service", + locals: {path: admin_service_path(@service), service: @service } %> diff --git a/app/views/admin/service/index.html.erb b/app/views/admin/service/index.html.erb new file mode 100644 index 0000000..7c2cc5d --- /dev/null +++ b/app/views/admin/service/index.html.erb @@ -0,0 +1,117 @@ +<% provide :page_title, "Службы" %> +<%= render 'shared/admin/navbar/admin', selected: "user" %> +<%= render 'shared/flash_alert' %> +
    +
    + <%= render 'shared/admin/navbar/user_navbar', selected: "service" %> +
    +
    +

    + +

    +
    + <%= search_form_for(@query, url: admin_service_index_path, method: :get, class: "rounded accordion-body") do |f| %> +
    + <%= f.label :name, class: "mb-1" %> + <%= f.search_field :name_cont, class:"form-control mb-2", placeholder:"Название" %> + <%= f.label :division_id %> + <%= f.search_field :division_name_cont, class:"form-control mb-2", placeholder:"Подразделение" %> + <%= f.label :organization_id %> + <%= f.collection_select(:organization_id_eq, Organization.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class =>'form-select mb-2'}) %> + <%= f.label :building_id %> + <%= f.search_field :building_name_cont, class:"form-control mb-2", placeholder:"Здание" %> +
    + <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%> +
    + <%= t("b_clear")%> +
    + <% end %> +
    +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_service_path, classes: "btn btn-primary w-100", text: t("b_add") %> +
    +
    +
    + + + + + + + + + + + + + <% @services.each do |service| %> + + + + + <% if service.division.nil? %> + + <% else %> + + <% end %> + + <% if service.organization.nil? %> + + <% else %> + + <% end %> + + <% if service.building.nil? %> + + <% else %> + + <% end %> + + + + <% end %> + +
    <%= t('activerecord.attributes.service.id') %><%= t('activerecord.attributes.service.name') %><%= t('activerecord.attributes.service.division') %><%= t('activerecord.attributes.service.organization') %><%= t('activerecord.attributes.service.building') %><%= t('action') %>
    <%= service.id %><%= service.name %><%= "——" %><%= service.division.name %><%= "——" %><%= service.organization.name %><%= "——" %><%= service.building.name %> +
    + <%= link_to edit_admin_service_path(service.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to admin_service_path(service.id), method: :delete, class: "btn" do %> + + <% end %> +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + <%= pagination @pagy %> +
    +
    +
    +
    +
    + diff --git a/app/views/admin/service/new.turbo_stream.erb b/app/views/admin/service/new.turbo_stream.erb new file mode 100644 index 0000000..ec98616 --- /dev/null +++ b/app/views/admin/service/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('.add_service') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/service", + locals: {path: admin_service_index_path, service: @service } %> diff --git a/app/views/admin/service/update.turbo_stream.erb b/app/views/admin/service/update.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/service/update.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/shared/admin/form/_service.html.erb b/app/views/shared/admin/form/_service.html.erb new file mode 100644 index 0000000..a1cb3fa --- /dev/null +++ b/app/views/shared/admin/form/_service.html.erb @@ -0,0 +1,12 @@ +<%= simple_form_for service, as: :service, url: path, + class: "form-group row" do |f| %> +
    + <%= f.input :name, placeholder: 'Название службы', input_html: {:tabindex => 1}%> + <%= f.association :organization, input_html: { :tabindex => 2}, include_blank: false %> + <%= f.association :division, input_html: { :tabindex => 3 }, include_blank: false %> + <%= f.association :building, input_html: { :tabindex => 4}, include_blank: false %> + +
    + + <%= render 'shared/form/actions', f:f %> +<% end %> diff --git a/app/views/shared/admin/navbar/_user_navbar.html.erb b/app/views/shared/admin/navbar/_user_navbar.html.erb index 3f22cca..62cdb81 100644 --- a/app/views/shared/admin/navbar/_user_navbar.html.erb +++ b/app/views/shared/admin/navbar/_user_navbar.html.erb @@ -6,7 +6,7 @@ <% end %>
    - <%= link_to admin_users_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'service' } text-start" do %> + <%= link_to admin_service_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'service' } text-start" do %> <%= 'Службы' %> <% end %>
    diff --git a/config/routes.rb b/config/routes.rb index 31820e1..2684df5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,7 +20,8 @@ post :create_inspection, :to => 'device#create_inspection' end resources :device_model, :measurement_class - resources :control_point, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, except: [:show] + resources :service, :control_point, :manufacturer, :measurement_group, :device_reg_group, + :supplementary_kit, :device_component, except: [:show] end devise_for :users, controllers: { @@ -32,7 +33,7 @@ resources :device do post :create_inspection, :to => 'device#create_inspection' end - resources :control_point, :device_model, :measurement_class, + resources :service, :control_point, :device_model, :measurement_class, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, only: [:create, :new] resources :inspection, except: [:index] do From f4d9aa840fdac9dfd28755569bd8e4a57592057d Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 30 Jan 2024 14:59:34 +0400 Subject: [PATCH 079/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BD=D0=B5=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D1=8E=D1=89=D0=B8=D0=B5=20=D1=82=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/factories/channels.rb | 2 +- test/factories/control_points.rb | 1 + test/factories/services.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/factories/channels.rb b/test/factories/channels.rb index fae6aaa..6b4c8f5 100644 --- a/test/factories/channels.rb +++ b/test/factories/channels.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory :channel do channel_id { 1 } - control_point { association :control_point} + control_point { association :control_point } server { association :server } service { association :service } location_description { 'MyText' } diff --git a/test/factories/control_points.rb b/test/factories/control_points.rb index f44a307..9b4e4c3 100644 --- a/test/factories/control_points.rb +++ b/test/factories/control_points.rb @@ -5,5 +5,6 @@ room { nil } channel { nil } device { nil } + service { association :service } end end diff --git a/test/factories/services.rb b/test/factories/services.rb index 01b5c54..cd46536 100644 --- a/test/factories/services.rb +++ b/test/factories/services.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :service do - name { 'MyStr2222isng' } + name division { association :division } organization { association :organization } building { association :building } From c8fde1e6d33752a1e165da483c5e7c6a107af0a9 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 30 Jan 2024 15:00:01 +0400 Subject: [PATCH 080/132] =?UTF-8?q?UPD:=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=8F=D0=B5=D1=82=20rubocop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/service_controller.rb | 1 - app/models/building.rb | 4 ++-- app/models/division.rb | 4 ++-- app/models/service.rb | 8 ++++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/controllers/admin/service_controller.rb b/app/controllers/admin/service_controller.rb index d9660d4..2f18ec7 100644 --- a/app/controllers/admin/service_controller.rb +++ b/app/controllers/admin/service_controller.rb @@ -46,7 +46,6 @@ def destroy redirect_to(admin_service_index_path) end - private def set_service diff --git a/app/models/building.rb b/app/models/building.rb index 660876d..55d74e1 100644 --- a/app/models/building.rb +++ b/app/models/building.rb @@ -7,7 +7,7 @@ class Building < ApplicationRecord validates :name, presence: true - def self.ransackable_attributes(auth_object = nil) - ["created_at", "id", "name", "organization_id", "updated_at"] + def self.ransackable_attributes(_auth_object = nil) + ['created_at', 'id', 'name', 'organization_id', 'updated_at'] end end diff --git a/app/models/division.rb b/app/models/division.rb index 24bc9c7..abcf7cd 100644 --- a/app/models/division.rb +++ b/app/models/division.rb @@ -5,7 +5,7 @@ class Division < ApplicationRecord validates :name, presence: true - def self.ransackable_attributes(auth_object = nil) - ["created_at", "id", "name", "organization_id", "updated_at"] + def self.ransackable_attributes(_auth_object = nil) + ['created_at', 'id', 'name', 'organization_id', 'updated_at'] end end diff --git a/app/models/service.rb b/app/models/service.rb index cd401de..b58df8c 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -11,11 +11,11 @@ class Service < ApplicationRecord validates :name, :organization, :division, :building, presence: true validates :name, uniqueness: true - def self.ransackable_attributes(auth_object = nil) - ["building_id", "created_at", "division_id", "id", "name", "organization_id", "updated_at"] + def self.ransackable_attributes(_auth_object = nil) + ['building_id', 'created_at', 'division_id', 'id', 'name', 'organization_id', 'updated_at'] end - def self.ransackable_associations(auth_object = nil) - ["building", "control_points", "devices", "division", "organization", "servers", "users"] + def self.ransackable_associations(_auth_object = nil) + ['building', 'control_points', 'devices', 'division', 'organization', 'servers', 'users'] end end From 5973df50760759f3a75b470b46f1482482da4622 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 31 Jan 2024 12:28:55 +0400 Subject: [PATCH 081/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=B2=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA=D0=B5=20(=D0=B1?= =?UTF-8?q?=D0=B5=D0=B7=20=D1=80=D1=83=D1=81=D0=B8=D1=84=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/division_controller.rb | 35 +++++++ app/controllers/concerns/division_concern.rb | 48 +++++++++ ...tbo_stream.erb => update.turbo_stream.erb} | 0 .../admin/division/create.turbo_stream.erb | 1 + .../admin/division/edit.turbo_stream.erb | 3 + app/views/admin/division/index.html.erb | 97 +++++++++++++++++++ app/views/admin/division/new.turbo_stream.erb | 3 + .../admin/division/update.turbo_stream.erb | 1 + .../shared/admin/form/_division.html.erb | 9 ++ .../shared/admin/navbar/_user_navbar.html.erb | 2 +- config/routes.rb | 4 +- .../admin/division_controller_test.rb | 8 ++ 12 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 app/controllers/admin/division_controller.rb create mode 100644 app/controllers/concerns/division_concern.rb rename app/views/admin/control_point/{update.rutbo_stream.erb => update.turbo_stream.erb} (100%) create mode 100644 app/views/admin/division/create.turbo_stream.erb create mode 100644 app/views/admin/division/edit.turbo_stream.erb create mode 100644 app/views/admin/division/index.html.erb create mode 100644 app/views/admin/division/new.turbo_stream.erb create mode 100644 app/views/admin/division/update.turbo_stream.erb create mode 100644 app/views/shared/admin/form/_division.html.erb create mode 100644 test/controllers/admin/division_controller_test.rb diff --git a/app/controllers/admin/division_controller.rb b/app/controllers/admin/division_controller.rb new file mode 100644 index 0000000..1156c28 --- /dev/null +++ b/app/controllers/admin/division_controller.rb @@ -0,0 +1,35 @@ +class Admin::DivisionController < ApplicationController + include DivisionConcern + load_and_authorize_resource + before_action :set_division, only: [:show, :edit, :update, :destroy] + + def index + division_index + end + + def new + authorize!(:new, :division_admin) + @division = Division.new + end + + def create + authorize!(:create, :division_admin) + division_create + end + + def update + authorize!(:update, :division_admin) + division_update + end + + def destroy + authorize!(:destroy, :division_admin) + division_destroy + end + + private + + def set_division + @division = Division.find(params[:id]) + end +end diff --git a/app/controllers/concerns/division_concern.rb b/app/controllers/concerns/division_concern.rb new file mode 100644 index 0000000..4f3e354 --- /dev/null +++ b/app/controllers/concerns/division_concern.rb @@ -0,0 +1,48 @@ +module DivisionConcern + extend ActiveSupport::Concern + + included do + def division_index + @query = Division.ransack(params[:q]) + @pagy, @divisions = pagy(@query.result.order(:name)) + end + + def division_create + @division = Division.new(division_params) + + if @division.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def division_update + if @division.update(division_params) + redirect_back(fallback_location: root_path) + else + render(:edit, status: :unprocessable_entity) + end + end + + def division_destroy + assigned_models_count = Service.where(division_id: params[:id]).count + + if assigned_models_count.zero? + @division.destroy + else + flash[:error] = t('message.admin.division.delete.error') + end + redirect_to(admin_division_index_path) + end + + private + + def division_params + params.require(:division).permit( + :name, + :organization_id, + ) + end + end +end diff --git a/app/views/admin/control_point/update.rutbo_stream.erb b/app/views/admin/control_point/update.turbo_stream.erb similarity index 100% rename from app/views/admin/control_point/update.rutbo_stream.erb rename to app/views/admin/control_point/update.turbo_stream.erb diff --git a/app/views/admin/division/create.turbo_stream.erb b/app/views/admin/division/create.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/division/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/admin/division/edit.turbo_stream.erb b/app/views/admin/division/edit.turbo_stream.erb new file mode 100644 index 0000000..5f6aea4 --- /dev/null +++ b/app/views/admin/division/edit.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.division.edit.edit_division') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/division", + locals: { path: admin_division_path(@division), division: @division } %> diff --git a/app/views/admin/division/index.html.erb b/app/views/admin/division/index.html.erb new file mode 100644 index 0000000..503b954 --- /dev/null +++ b/app/views/admin/division/index.html.erb @@ -0,0 +1,97 @@ +<% provide :page_title, "Подразделения" %> +<%= render 'shared/admin/navbar/admin', selected: "user" %> +<%= render 'shared/flash_alert' %> +
    +
    + <%= render 'shared/admin/navbar/user_navbar', selected: "division" %> +
    +
    +

    + +

    +
    + <%= search_form_for(@query, url: admin_division_index_path, method: :get, class: "rounded accordion-body") do |f| %> +
    + <%= f.label :name, class: "mb-1" %> + <%= f.search_field :name_cont, class:"form-control mb-2", placeholder:"Название" %> + <%= f.label :organization_id, class:"mb-1" %> + <%= f.collection_select(:organization_id_eq, Organization.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select mb-2'}) %> +
    + <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%> +
    + <%= t("b_clear")%> +
    + <% end %> +
    +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_division_path, classes: "btn btn-primary w-100", text: t("b_add") %> +
    +
    +
    + + + + + + + + + + + <% @divisions.each do |division| %> + + + + <% if division.organization.nil? %> + + <% else %> + + <% end %> + + + <% end %> + +
    <%= t('activerecord.attributes.division.id') %><%= t('activerecord.attributes.division.name') %><%= t('activerecord.attributes.division.organization') %><%= t('action') %>
    <%= division.id %><%= division.name %><%= "——" %><%= division.organization.name %> +
    + <%= link_to edit_admin_division_path(division.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to admin_division_path(division.id), method: :delete, class: "btn" do %> + + <% end %> +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + <%= pagination @pagy %> +
    +
    +
    +
    +
    + diff --git a/app/views/admin/division/new.turbo_stream.erb b/app/views/admin/division/new.turbo_stream.erb new file mode 100644 index 0000000..f4ecaa8 --- /dev/null +++ b/app/views/admin/division/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.division.new.add_division') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/division", + locals: { path: admin_division_index_path, division: @division } %> diff --git a/app/views/admin/division/update.turbo_stream.erb b/app/views/admin/division/update.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/division/update.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/shared/admin/form/_division.html.erb b/app/views/shared/admin/form/_division.html.erb new file mode 100644 index 0000000..f0fb911 --- /dev/null +++ b/app/views/shared/admin/form/_division.html.erb @@ -0,0 +1,9 @@ +<%= simple_form_for division, as: :division, url: path, + class: "form-group row" do |f| %> +
    + <%= f.input :name, placeholder: 'Название подразделения', input_html: {:tabindex => 1}%> + <%= f.association :organization, input_html: {:tabindex => 2}, include_blank: false %> +
    + + <%= render 'shared/form/actions', f:f %> +<% end %> diff --git a/app/views/shared/admin/navbar/_user_navbar.html.erb b/app/views/shared/admin/navbar/_user_navbar.html.erb index 62cdb81..e0ff9ce 100644 --- a/app/views/shared/admin/navbar/_user_navbar.html.erb +++ b/app/views/shared/admin/navbar/_user_navbar.html.erb @@ -11,7 +11,7 @@ <% end %>
    - <%= link_to admin_users_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'division' } text-start" do %> + <%= link_to admin_division_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'division' } text-start" do %> <%= 'Подразделения' %> <% end %>
    diff --git a/config/routes.rb b/config/routes.rb index 2684df5..7f1ba32 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,7 +20,7 @@ post :create_inspection, :to => 'device#create_inspection' end resources :device_model, :measurement_class - resources :service, :control_point, :manufacturer, :measurement_group, :device_reg_group, + resources :division, :service, :control_point, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, except: [:show] end @@ -33,7 +33,7 @@ resources :device do post :create_inspection, :to => 'device#create_inspection' end - resources :service, :control_point, :device_model, :measurement_class, + resources :division, :service, :control_point, :device_model, :measurement_class, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, only: [:create, :new] resources :inspection, except: [:index] do diff --git a/test/controllers/admin/division_controller_test.rb b/test/controllers/admin/division_controller_test.rb new file mode 100644 index 0000000..ff2791b --- /dev/null +++ b/test/controllers/admin/division_controller_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class Admin::DivisionControllerTest < ActionDispatch::IntegrationTest + test 'should get index' do + get admin_division_index_url + assert_response :success + end +end From 74a0c9d72c800be1929f94cc948bee0e988c4749 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 31 Jan 2024 14:08:29 +0400 Subject: [PATCH 082/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=80=D0=B3?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B9=20=D0=B2=20?= =?UTF-8?q?=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA=D0=B5=20(=D0=B1=D0=B5?= =?UTF-8?q?=D0=B7=20=D1=80=D1=83=D1=81=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile.lock | 1 + app/controllers/admin/division_controller.rb | 6 - .../admin/organization_controller.rb | 30 ++++ app/controllers/concerns/division_concern.rb | 4 + .../concerns/organization_concern.rb | 60 ++++++++ app/models/organization.rb | 4 + .../organization/create.turbo_stream.erb | 1 + .../admin/organization/edit.turbo_stream.erb | 3 + app/views/admin/organization/index.html.erb | 128 ++++++++++++++++++ .../admin/organization/new.turbo_stream.erb | 3 + .../organization/update.turbo_stream.erb | 1 + .../shared/admin/form/_organization.html.erb | 13 ++ .../shared/admin/navbar/_user_navbar.html.erb | 2 +- config/routes.rb | 4 +- .../admin/organization_controller_test.rb | 7 + 15 files changed, 258 insertions(+), 9 deletions(-) create mode 100644 app/controllers/admin/organization_controller.rb create mode 100644 app/controllers/concerns/organization_concern.rb create mode 100644 app/views/admin/organization/create.turbo_stream.erb create mode 100644 app/views/admin/organization/edit.turbo_stream.erb create mode 100644 app/views/admin/organization/index.html.erb create mode 100644 app/views/admin/organization/new.turbo_stream.erb create mode 100644 app/views/admin/organization/update.turbo_stream.erb create mode 100644 app/views/shared/admin/form/_organization.html.erb create mode 100644 test/controllers/admin/organization_controller_test.rb diff --git a/Gemfile.lock b/Gemfile.lock index 867fffd..8ae3b86 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -302,6 +302,7 @@ GEM zeitwerk (2.6.7) PLATFORMS + x86_64-linux x86_64-linux-musl DEPENDENCIES diff --git a/app/controllers/admin/division_controller.rb b/app/controllers/admin/division_controller.rb index 1156c28..2dd1459 100644 --- a/app/controllers/admin/division_controller.rb +++ b/app/controllers/admin/division_controller.rb @@ -26,10 +26,4 @@ def destroy authorize!(:destroy, :division_admin) division_destroy end - - private - - def set_division - @division = Division.find(params[:id]) - end end diff --git a/app/controllers/admin/organization_controller.rb b/app/controllers/admin/organization_controller.rb new file mode 100644 index 0000000..a6e21fa --- /dev/null +++ b/app/controllers/admin/organization_controller.rb @@ -0,0 +1,30 @@ +class Admin::OrganizationController < ApplicationController + include OrganizationConcern + + load_and_authorize_resource + before_action :set_organization, only: [:show, :edit, :update, :destroy] + + def index + organization_index + end + + def new + authorize!(:new, :organization_admin) + @organization = Organization.new + end + + def create + authorize!(:create, :organization_admin) + organization_create + end + + def update + authorize!(:update, :organization_admin) + organization_update + end + + def destroy + authorize!(:destroy, :organization_admin) + organization_destroy + end +end diff --git a/app/controllers/concerns/division_concern.rb b/app/controllers/concerns/division_concern.rb index 4f3e354..fa08c4f 100644 --- a/app/controllers/concerns/division_concern.rb +++ b/app/controllers/concerns/division_concern.rb @@ -38,6 +38,10 @@ def division_destroy private + def set_division + @division = Division.find(params[:id]) + end + def division_params params.require(:division).permit( :name, diff --git a/app/controllers/concerns/organization_concern.rb b/app/controllers/concerns/organization_concern.rb new file mode 100644 index 0000000..68b0b22 --- /dev/null +++ b/app/controllers/concerns/organization_concern.rb @@ -0,0 +1,60 @@ +module OrganizationConcern + extend ActiveSupport::Concern + + included do + def organization_index + @query = Organization.ransack(params[:q]) + @pagy, @organizations = pagy(@query.result.order(:name)) + end + + def organization_create + @organization = Organization.new(organization_params) + + if @organization.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def organization_update + if @organization.update(organization_params) + redirect_back(fallback_location: root_path) + else + render(:edit, status: :unprocessable_entity) + end + end + + def organization_destroy + assigned_models_count = + Division.where(organization_id: params[:id]).count + + Service.where(organization_id: params[:id]).count + + Building.where(organization_id: params[:id]).count + + if assigned_models_count.zero? + @organization.destroy + else + flash[:error] = t('message.admin.organization.delete.error') + end + + redirect_to(admin_organization_index_path) + end + + private + + def set_organization + @organization = Organization.find(params[:id]) + end + + def organization_params + params.require(:organization).permit( + :name, + :full_address, + :zip_code, + :phone, + :fax, + :email + ) + end + end +end diff --git a/app/models/organization.rb b/app/models/organization.rb index 666c369..304b1c8 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -4,4 +4,8 @@ class Organization < ApplicationRecord has_many :services validates :name, presence: true + + def self.ransackable_attributes(auth_object = nil) + ["created_at", "email", "fax", "full_address", "id", "name", "phone", "updated_at", "zip_code"] + end end diff --git a/app/views/admin/organization/create.turbo_stream.erb b/app/views/admin/organization/create.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/organization/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/admin/organization/edit.turbo_stream.erb b/app/views/admin/organization/edit.turbo_stream.erb new file mode 100644 index 0000000..c76f5f2 --- /dev/null +++ b/app/views/admin/organization/edit.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.organization.edit.edit_division') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/organization", + locals: { path: admin_organization_path(@organization), organization: @organization } %> diff --git a/app/views/admin/organization/index.html.erb b/app/views/admin/organization/index.html.erb new file mode 100644 index 0000000..45d93c2 --- /dev/null +++ b/app/views/admin/organization/index.html.erb @@ -0,0 +1,128 @@ +<% provide :page_title, "Организации" %> +<%= render 'shared/admin/navbar/admin', selected: "user" %> +<%= render 'shared/flash_alert' %> +
    +
    + <%= render 'shared/admin/navbar/user_navbar', selected: "organization" %> +
    +
    +

    + +

    +
    + <%= search_form_for(@query, url: admin_organization_index_path, method: :get, class: "rounded accordion-body") do |f| %> +
    + <%= f.label :name, class: "mb-1" %> + <%= f.search_field :name_cont, class:"form-control mb-2", placeholder:"Название" %> + <%= f.label :full_address, class: "mb-1" %> + <%= f.search_field :full_address_cont, class:"form-control mb-2", placeholder:"Западное шоссе" %> + <%= f.label :zip_code, class: "mb-1" %> + <%= f.search_field :zip_code_cont, class:"form-control mb-2", placeholder:"433019" %> + <%= f.label :phone, class: "mb-1" %> + <%= f.search_field :phone_cont, class:"form-control mb-2", placeholder:"7-55-35" %> + <%= f.label :fax, class: "mb-1" %> + <%= f.search_field :fax_cont, class:"form-control mb-2", placeholder:"7-55-35" %> + <%= f.label :email, class: "mb-1" %> + <%= f.search_field :email_cont, class:"form-control mb-2", placeholder:"example@mail.com" %> +
    + <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%> +
    + <%= t("b_clear")%> +
    + <% end %> +
    +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_organization_path, classes: "btn btn-primary w-100", text: t("b_add") %> +
    +
    +
    + + + + + + + + + + + + + + + <% @organizations.each do |organization| %> + + + + <% if organization.full_address.nil? %> + + <% else %> + + <% end %> + <% if organization.zip_code.nil? %> + + <% else %> + + <% end %> + <% if organization.phone.nil? %> + + <% else %> + + <% end %> + <% if organization.fax.nil? %> + + <% else %> + + <% end %> + <% if organization.email.nil? %> + + <% else %> + + <% end %> + + + <% end %> + +
    <%= t('activerecord.attributes.organization.id') %><%= t('activerecord.attributes.organization.name') %><%= t('activerecord.attributes.organization.full_address') %><%= t('activerecord.attributes.organization.zip_code') %><%= t('activerecord.attributes.organization.phone') %><%= t('activerecord.attributes.organization.fax') %><%= t('activerecord.attributes.organization.email') %><%= t('action') %>
    <%= organization.id %><%= organization.name %><%= "——" %><%= organization.full_address %><%= "——" %><%= organization.zip_code %><%= "——" %><%= organization.phone %><%= "——" %><%= organization.fax %><%= "——" %><%= organization.email %> +
    + <%= link_to edit_admin_organization_path(organization.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to admin_organization_path(organization.id), method: :delete, class: "btn" do %> + + <% end %> +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + <%= pagination @pagy %> +
    +
    +
    +
    +
    + diff --git a/app/views/admin/organization/new.turbo_stream.erb b/app/views/admin/organization/new.turbo_stream.erb new file mode 100644 index 0000000..ebf6d58 --- /dev/null +++ b/app/views/admin/organization/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.organization.new.add_division') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/organization", + locals: { path: admin_organization_index_path, organization: @organization } %> diff --git a/app/views/admin/organization/update.turbo_stream.erb b/app/views/admin/organization/update.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/organization/update.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/shared/admin/form/_organization.html.erb b/app/views/shared/admin/form/_organization.html.erb new file mode 100644 index 0000000..142da9c --- /dev/null +++ b/app/views/shared/admin/form/_organization.html.erb @@ -0,0 +1,13 @@ +<%= simple_form_for organization, as: :organization, url: path, + class: "form-group row" do |f| %> +
    + <%= f.input :name, placeholder: 'Название рганизации', input_html: {:tabindex => 1}%> + <%= f.input :full_address, placeholder: 'Адрес', input_html: {:tabindex => 1}%> + <%= f.input :zip_code, placeholder: 'Индекс', input_html: {:tabindex => 1}%> + <%= f.input :phone, placeholder: 'Телефон', input_html: {:tabindex => 1}%> + <%= f.input :fax, placeholder: 'Факс', input_html: {:tabindex => 1}%> + <%= f.input :email, placeholder: 'E-mail', input_html: {:tabindex => 1}%> +
    + + <%= render 'shared/form/actions', f:f %> +<% end %> diff --git a/app/views/shared/admin/navbar/_user_navbar.html.erb b/app/views/shared/admin/navbar/_user_navbar.html.erb index e0ff9ce..4252d7b 100644 --- a/app/views/shared/admin/navbar/_user_navbar.html.erb +++ b/app/views/shared/admin/navbar/_user_navbar.html.erb @@ -16,7 +16,7 @@ <% end %>
    - <%= link_to admin_users_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'organization' } text-start" do %> + <%= link_to admin_organization_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'organization' } text-start" do %> <%= 'Организации' %> <% end %>
    diff --git a/config/routes.rb b/config/routes.rb index 7f1ba32..e2bf2eb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,7 +20,7 @@ post :create_inspection, :to => 'device#create_inspection' end resources :device_model, :measurement_class - resources :division, :service, :control_point, :manufacturer, :measurement_group, :device_reg_group, + resources :organization, :division, :service, :control_point, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, except: [:show] end @@ -33,7 +33,7 @@ resources :device do post :create_inspection, :to => 'device#create_inspection' end - resources :division, :service, :control_point, :device_model, :measurement_class, + resources :organization, :division, :service, :control_point, :device_model, :measurement_class, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, only: [:create, :new] resources :inspection, except: [:index] do diff --git a/test/controllers/admin/organization_controller_test.rb b/test/controllers/admin/organization_controller_test.rb new file mode 100644 index 0000000..8dd03d8 --- /dev/null +++ b/test/controllers/admin/organization_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class Admin::OrganizationControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end From a828497ed18ae670da1eb251391a629e545d924f Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 1 Feb 2024 15:09:29 +0400 Subject: [PATCH 083/132] =?UTF-8?q?UPD:=20=D0=BD=D0=B5=D0=BC=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D1=8F?= =?UTF-8?q?=D0=B5=D1=82=20l18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/concerns/device_concern.rb | 2 +- app/views/admin/device_model/index.html.erb | 6 +- .../admin/organization/edit.turbo_stream.erb | 2 +- .../admin/organization/new.turbo_stream.erb | 2 +- .../shared/admin/form/_organization.html.erb | 12 ++-- config/locales/ru.yml | 57 ++++++++++++++++++- 6 files changed, 67 insertions(+), 14 deletions(-) diff --git a/app/controllers/concerns/device_concern.rb b/app/controllers/concerns/device_concern.rb index e862ac1..1acd34d 100644 --- a/app/controllers/concerns/device_concern.rb +++ b/app/controllers/concerns/device_concern.rb @@ -2,7 +2,7 @@ module DeviceConcern extend ActiveSupport::Concern include DeviceHelper - included do + included do # rubocop:disable Metrics/BlockLength def device_index params[:q] ||= {} @query = Device.ransack(params[:q]) diff --git a/app/views/admin/device_model/index.html.erb b/app/views/admin/device_model/index.html.erb index 4b83826..1dbb858 100644 --- a/app/views/admin/device_model/index.html.erb +++ b/app/views/admin/device_model/index.html.erb @@ -18,13 +18,13 @@ <%= f.search_field :name_cont, class: 'form-control', placeholder: 'Название' %> <%= f.label :manufacturer_id, class:"mb-2" %> <%= f.collection_select(:manufacturer_id_eq, Manufacturer.all, - :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select'}) %> + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select'}) %> <%= f.label :measurement_group_id, class:"mb-2" %> <%= f.collection_select(:measurement_group_id_eq, MeasurementGroup.all, - :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select', id:"filtrator-class-index", data: {'action': 'onchange -> filter#filter', 'to_filter_class': 'filter-class-index', 'filtrator_class': 'filtrator-class-index'}}) %> + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select', id:"filtrator-class-index", data: {'action': 'onchange -> filter#filter', 'to_filter_class': 'filter-class-index', 'filtrator_class': 'filtrator-class-index'}}) %> <%= f.label :measurement_class_id, class:"mb-2" %> <%= f.collection_select(:measurement_class_id_eq, MeasurementClass.all, - :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select', id: "filter-class-index"}) %> + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select', id: "filter-class-index"}) %> <%= f.label :measuring_unit, class:"mb-2" %> <%= f.search_field :measuring_unit_cont, class: 'form-control', placeholder: 'мЗв/ч' %> <%= f.label :measurement_sensitivity, class:"mb-2" %> diff --git a/app/views/admin/organization/edit.turbo_stream.erb b/app/views/admin/organization/edit.turbo_stream.erb index c76f5f2..bddec14 100644 --- a/app/views/admin/organization/edit.turbo_stream.erb +++ b/app/views/admin/organization/edit.turbo_stream.erb @@ -1,3 +1,3 @@ -<%= turbo_stream.update "modal-title", t('admin.organization.edit.edit_division') %> +<%= turbo_stream.update "modal-title", t('admin.organization.edit.edit_organization') %> <%= turbo_stream.update "modal-body", partial: "shared/admin/form/organization", locals: { path: admin_organization_path(@organization), organization: @organization } %> diff --git a/app/views/admin/organization/new.turbo_stream.erb b/app/views/admin/organization/new.turbo_stream.erb index ebf6d58..3ee8927 100644 --- a/app/views/admin/organization/new.turbo_stream.erb +++ b/app/views/admin/organization/new.turbo_stream.erb @@ -1,3 +1,3 @@ -<%= turbo_stream.update "modal-title", t('admin.organization.new.add_division') %> +<%= turbo_stream.update "modal-title", t('admin.organization.new') %> <%= turbo_stream.update "modal-body", partial: "shared/admin/form/organization", locals: { path: admin_organization_index_path, organization: @organization } %> diff --git a/app/views/shared/admin/form/_organization.html.erb b/app/views/shared/admin/form/_organization.html.erb index 142da9c..e5dd220 100644 --- a/app/views/shared/admin/form/_organization.html.erb +++ b/app/views/shared/admin/form/_organization.html.erb @@ -1,12 +1,12 @@ <%= simple_form_for organization, as: :organization, url: path, class: "form-group row" do |f| %>
    - <%= f.input :name, placeholder: 'Название рганизации', input_html: {:tabindex => 1}%> - <%= f.input :full_address, placeholder: 'Адрес', input_html: {:tabindex => 1}%> - <%= f.input :zip_code, placeholder: 'Индекс', input_html: {:tabindex => 1}%> - <%= f.input :phone, placeholder: 'Телефон', input_html: {:tabindex => 1}%> - <%= f.input :fax, placeholder: 'Факс', input_html: {:tabindex => 1}%> - <%= f.input :email, placeholder: 'E-mail', input_html: {:tabindex => 1}%> + <%= f.input :name, label: t('.labels.name'), placeholder: t('.placeholders.name'), input_html: {:tabindex => 1}%> + <%= f.input :full_address, label: t('.labels.full_address'), placeholder: t('.placeholders.full_address'), input_html: {:tabindex => 1}%> + <%= f.input :zip_code, label: t('.labels.zip_code'), placeholder: t('.placeholders.zip_code'), input_html: {:tabindex => 1}%> + <%= f.input :phone, label: t('.labels.phone'), placeholder: t('.placeholders.phone'), input_html: {:tabindex => 1}%> + <%= f.input :fax, label: t('.labels.fax'), placeholder: t('.placeholders.fax'), input_html: {:tabindex => 1}%> + <%= f.input :email, label: t('.labels.email'), placeholder: t('.placeholders.email'), input_html: {:tabindex => 1}%>
    <%= render 'shared/form/actions', f:f %> diff --git a/config/locales/ru.yml b/config/locales/ru.yml index c0c3239..83ee49b 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -142,6 +142,12 @@ ru: month: Месяц second: Секунд year: Год + placeholders: + organization: + add_edit_form: + name: Название организации + full_address: Адрес + message: user: create: @@ -193,6 +199,11 @@ ru: user: Пользователь inspection: Инспекция attributes: + building: + id: ID + name: Название + organization_id: Организация + organization: Организация control_point: name: Название room: Помещение @@ -265,6 +276,12 @@ ru: serial_id: Серийный номер name: Название description: Описание + room: + id: ID + name: Название + building: Здание + building_id: Здание + level: Уровень post: title: Заголовок body: Текст записи @@ -315,6 +332,34 @@ ru: leave_blank_if_you_don_t_want_to_change_it: оставьте поле пустым, если не хотите его менять title: Страница редактирования %{resource} we_need_your_current_password_to_confirm_your_changes: введите текущий пароль для подтверждения изменений + shared: + admin: + form: + building: + labels: + name: Название + organization: Организация + placeholders: + name: "106" + room: + placeholders: + name: "112" + level: "-0.8" + organization: + labels: + name: Название + full_address: Адрес + zip_code: Индекс + phone: Телефон + fax: Факс + email: E-mail + placeholders: + name: АО ГНЦ НИИАР + full_address: Западное шоссе, 9 + zip_code: "433019" + phone: 7-55-25 + fax: 7-55-25 + email: info@niiar.ru layouts: application: home: Главная @@ -327,7 +372,6 @@ ru: profile: Профиль logout: Выйти edit_me: Изм. профиль - shared: show: device: send_to_inspection: Отправить на инспекцию @@ -392,7 +436,10 @@ ru: created_at_to: Создан до updated_at_from: Обновлен от updated_at_to: Обновлен до - control_points: + building: + new: Добавить здание + edit: Изменить здание + control_point: new: add_control_point: Добавить т. контроля edit: @@ -432,6 +479,12 @@ ru: add_measurement_class: Добавить класс изм. edit: edit_measurement_class: Изменить класс изм. + room: + new: Добавить пом. + edit: Изменить пом. + organization: + new: Добавить орг. + edit: Изменить орг. combobox_blank: Не учитывать b_change: Изменить b_delete: Удалить From 795b145846eeccf16f15881345db9b8ed166752e Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 1 Feb 2024 15:10:27 +0400 Subject: [PATCH 084/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=20=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9=20=D0=B8=20=D0=BF=D0=BE=D0=BC=D0=B5=D1=89?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B2=20=D0=B0=D0=B4=D0=BC=D0=B8?= =?UTF-8?q?=D0=BD=D0=BA=D0=B5=20(=D1=81=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B5=D0=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/building_controller.rb | 30 ++++++ app/controllers/admin/room_controller.rb | 30 ++++++ app/controllers/concerns/building_concern.rb | 54 ++++++++++ app/controllers/concerns/room_consern.rb | 53 +++++++++ app/models/room.rb | 6 +- .../admin/building/create.turbo_stream.erb | 1 + .../admin/building/edit.turbo_stream.erb | 3 + app/views/admin/building/index.html.erb | 97 +++++++++++++++++ app/views/admin/building/new.turbo_stream.erb | 3 + .../admin/building/update.turbo_stream.erb | 1 + app/views/admin/room/create.turbo_stream.erb | 1 + app/views/admin/room/edit.turbo_stream.erb | 3 + app/views/admin/room/index.html.erb | 101 ++++++++++++++++++ app/views/admin/room/new.turbo_stream.erb | 3 + app/views/admin/room/update.turbo_stream.erb | 1 + .../shared/admin/form/_building.html.erb | 9 ++ app/views/shared/admin/form/_room.html.erb | 10 ++ .../admin/navbar/_location_navbar.html.erb | 4 +- config/routes.rb | 4 +- .../admin/building_controller_test.rb | 7 ++ .../controllers/admin/room_controller_test.rb | 7 ++ 21 files changed, 423 insertions(+), 5 deletions(-) create mode 100644 app/controllers/admin/building_controller.rb create mode 100644 app/controllers/admin/room_controller.rb create mode 100644 app/controllers/concerns/building_concern.rb create mode 100644 app/controllers/concerns/room_consern.rb create mode 100644 app/views/admin/building/create.turbo_stream.erb create mode 100644 app/views/admin/building/edit.turbo_stream.erb create mode 100644 app/views/admin/building/index.html.erb create mode 100644 app/views/admin/building/new.turbo_stream.erb create mode 100644 app/views/admin/building/update.turbo_stream.erb create mode 100644 app/views/admin/room/create.turbo_stream.erb create mode 100644 app/views/admin/room/edit.turbo_stream.erb create mode 100644 app/views/admin/room/index.html.erb create mode 100644 app/views/admin/room/new.turbo_stream.erb create mode 100644 app/views/admin/room/update.turbo_stream.erb create mode 100644 app/views/shared/admin/form/_building.html.erb create mode 100644 app/views/shared/admin/form/_room.html.erb create mode 100644 test/controllers/admin/building_controller_test.rb create mode 100644 test/controllers/admin/room_controller_test.rb diff --git a/app/controllers/admin/building_controller.rb b/app/controllers/admin/building_controller.rb new file mode 100644 index 0000000..ab90369 --- /dev/null +++ b/app/controllers/admin/building_controller.rb @@ -0,0 +1,30 @@ +class Admin::BuildingController < ApplicationController + include BuildingConcern + + load_and_authorize_resource + before_action :set_building, only: [:show, :edit, :update, :destroy] + + def index + building_index + end + + def new + authorize!(:new, :building_admin) + @building = Building.new + end + + def create + authorize!(:create, :building_admin) + building_create + end + + def update + authorize!(:update, :building_admin) + building_update + end + + def destroy + authorize!(:destroy, :building_admin) + building_destroy + end +end diff --git a/app/controllers/admin/room_controller.rb b/app/controllers/admin/room_controller.rb new file mode 100644 index 0000000..80d39eb --- /dev/null +++ b/app/controllers/admin/room_controller.rb @@ -0,0 +1,30 @@ +class Admin::RoomController < ApplicationController + include RoomConsern + + load_and_authorize_resource + before_action :set_room, only: [:show, :edit, :update, :destroy] + + def index + room_index + end + + def new + authorize!(:new, :room_admin) + @room = Room.new + end + + def create + authorize!(:create, :room_admin) + room_create + end + + def update + authorize!(:update, :room_admin) + room_update + end + + def destroy + authorize!(:destroy, :room_admin) + room_destroy + end +end diff --git a/app/controllers/concerns/building_concern.rb b/app/controllers/concerns/building_concern.rb new file mode 100644 index 0000000..3b7b340 --- /dev/null +++ b/app/controllers/concerns/building_concern.rb @@ -0,0 +1,54 @@ +module BuildingConcern + extend ActiveSupport::Concern + + included do # rubocop:disable Metrics/BlockLength + def building_index + @query = Building.ransack(params[:q]) + @pagy, @buildings = pagy(@query.result.order(:name)) + end + + def building_create + @building = Building.new(building_params) + + if @building.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def building_update + if @building.update(building_params) + redirect_back(fallback_location: root_path) + else + render(:edit, status: :unprocessable_entity) + end + end + + def building_destroy + assigned_models_count = + Room.where(building_id: params[:id]).count + + Service.where(building_id: params[:id]).count + + if assigned_models_count.zero? + @building.destroy + else + flash[:error] = t('message.admin.building.delete.error') + end + redirect_to(admin_building_index_path) + end + + private + + def set_building + @building = Building.find(params[:id]) + end + + def building_params + params.require(:building).permit( + :name, + :organization_id, + ) + end + end +end diff --git a/app/controllers/concerns/room_consern.rb b/app/controllers/concerns/room_consern.rb new file mode 100644 index 0000000..bd65252 --- /dev/null +++ b/app/controllers/concerns/room_consern.rb @@ -0,0 +1,53 @@ +module RoomConsern + extend ActiveSupport::Concern + + included do # rubocop:disable Metrics/BlockLength + def room_index + @query = Room.ransack(params[:q]) + @pagy, @rooms = pagy(@query.result.order(:name)) + end + + def room_create + @room = Room.new(room_params) + + if @room.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def room_update + if @room.update(room_params) + redirect_back(fallback_location: root_path) + else + render(:edit, status: :unprocessable_entity) + end + end + + def room_destroy + assigned_models_count = ControlPoint.where(room_id: params[:id]).count + + if assigned_models_count.zero? + @room.destroy + else + flash[:error] = t('message.admin.room.delete.error') + end + redirect_to(admin_room_index_path) + end + + private + + def set_room + @room = Room.find(params[:id]) + end + + def room_params + params.require(:room).permit( + :name, + :building_id, + :level, + ) + end + end +end diff --git a/app/models/room.rb b/app/models/room.rb index 0ae9be9..e2430a7 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -4,5 +4,9 @@ class Room < ApplicationRecord has_many :devices has_many :control_point - validates :name, presence: true + validates :name, :building, presence: true + + def self.ransackable_attributes(_auth_object = nil) + ['building_id', 'created_at', 'id', 'level', 'name', 'updated_at'] + end end diff --git a/app/views/admin/building/create.turbo_stream.erb b/app/views/admin/building/create.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/building/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/admin/building/edit.turbo_stream.erb b/app/views/admin/building/edit.turbo_stream.erb new file mode 100644 index 0000000..d662b90 --- /dev/null +++ b/app/views/admin/building/edit.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.building.edit') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/building", + locals: { path: admin_building_path(@building), building: @building } %> diff --git a/app/views/admin/building/index.html.erb b/app/views/admin/building/index.html.erb new file mode 100644 index 0000000..ce50c4e --- /dev/null +++ b/app/views/admin/building/index.html.erb @@ -0,0 +1,97 @@ +<% provide :page_title, "Здания" %> +<%= render 'shared/admin/navbar/admin', selected: "location" %> +<%= render 'shared/flash_alert' %> +
    +
    + <%= render 'shared/admin/navbar/location_navbar', selected: "building" %> +
    +
    +

    + +

    +
    + <%= search_form_for(@query, url: admin_building_index_path, method: :get, class: "rounded accordion-body") do |f| %> +
    + <%= f.label :name, class: "mb-1" %> + <%= f.search_field :name_cont, class:"form-control mb-2", placeholder:"Название" %> + <%= f.label :organization_id, class: "mb-1" %> + <%= f.collection_select(:organization_id_eq, Organization.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select mb-2'}) %> +
    + <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%> +
    + <%= t("b_clear")%> +
    + <% end %> +
    +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_building_path, classes: "btn btn-primary w-100", text: t("b_add") %> +
    +
    +
    + + + + + + + + + + + <% @buildings.each do |building| %> + + + + <% if building.organization.nil? %> + + <% else %> + + <% end %> + + + <% end %> + +
    <%= t('activerecord.attributes.building.id') %><%= t('activerecord.attributes.building.name') %><%= t('activerecord.attributes.building.organization') %><%= t('action') %>
    <%= building.id %><%= building.name %><%= "——" %><%= building.organization.name %> +
    + <%= link_to edit_admin_building_path(building.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to admin_building_path(building.id), method: :delete, class: "btn" do %> + + <% end %> +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + <%= pagination @pagy %> +
    +
    +
    +
    +
    + diff --git a/app/views/admin/building/new.turbo_stream.erb b/app/views/admin/building/new.turbo_stream.erb new file mode 100644 index 0000000..f2f0499 --- /dev/null +++ b/app/views/admin/building/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.building.new') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/building", + locals: {path: admin_building_index_path, building: @building } %> diff --git a/app/views/admin/building/update.turbo_stream.erb b/app/views/admin/building/update.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/building/update.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/admin/room/create.turbo_stream.erb b/app/views/admin/room/create.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/room/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/admin/room/edit.turbo_stream.erb b/app/views/admin/room/edit.turbo_stream.erb new file mode 100644 index 0000000..aec72af --- /dev/null +++ b/app/views/admin/room/edit.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.room.edit') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/room", + locals: { path: admin_room_path(@room), room: @room } %> diff --git a/app/views/admin/room/index.html.erb b/app/views/admin/room/index.html.erb new file mode 100644 index 0000000..a19302b --- /dev/null +++ b/app/views/admin/room/index.html.erb @@ -0,0 +1,101 @@ +<% provide :page_title, "Помещения" %> +<%= render 'shared/admin/navbar/admin', selected: "location" %> +<%= render 'shared/flash_alert' %> +
    +
    + <%= render 'shared/admin/navbar/location_navbar', selected: "room" %> +
    +
    +

    + +

    +
    + <%= search_form_for(@query, url: admin_room_index_path, method: :get, class: "rounded accordion-body") do |f| %> +
    + <%= f.label :name, class: "mb-1" %> + <%= f.search_field :name_cont, class:"form-control mb-2", placeholder:"Название" %> + <%= f.label :building_id, class: "mb-1" %> + <%= f.collection_select(:building_id_eq, Building.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select mb-2'}) %> + <%= f.label :level, class: "mb-1" %> + <%= f.search_field :level_cont, class: "form-control mb-2", placeholder:"Уровень"%> +
    + <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%> +
    + <%= t("b_clear")%> +
    + <% end %> +
    +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_room_path, classes: "btn btn-primary w-100", text: t("b_add") %> +
    +
    +
    + + + + + + + + + + + + <% @rooms.each do |room| %> + + + + + <% if room.level.nil? %> + + <% else %> + + <% end %> + + + <% end %> + +
    <%= t('activerecord.attributes.room.id') %><%= t('activerecord.attributes.room.name') %><%= t('activerecord.attributes.room.building') %><%= t('activerecord.attributes.room.level') %><%= t('action') %>
    <%= room.id %><%= room.name %><%= room.building.name %><%= "——" %><%= room.level %> +
    + <%= link_to edit_admin_room_path(room.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to admin_room_path(room.id), method: :delete, class: "btn" do %> + + <% end %> +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + <%= pagination @pagy %> +
    +
    +
    +
    +
    + diff --git a/app/views/admin/room/new.turbo_stream.erb b/app/views/admin/room/new.turbo_stream.erb new file mode 100644 index 0000000..e993dcc --- /dev/null +++ b/app/views/admin/room/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.room.new') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/room", + locals: {path: admin_room_index_path, room: @room } %> diff --git a/app/views/admin/room/update.turbo_stream.erb b/app/views/admin/room/update.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/room/update.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/shared/admin/form/_building.html.erb b/app/views/shared/admin/form/_building.html.erb new file mode 100644 index 0000000..ae7cc44 --- /dev/null +++ b/app/views/shared/admin/form/_building.html.erb @@ -0,0 +1,9 @@ +<%= simple_form_for building, as: :building, url: path, + class: "form-group row" do |f| %> +
    + <%= f.input :name, placeholder: t('.placeholders.name'), input_html: {:tabindex => 1}%> + <%= f.association :organization, input_html: {:tabindex => 2} %> +
    + + <%= render 'shared/form/actions', f:f %> +<% end %> diff --git a/app/views/shared/admin/form/_room.html.erb b/app/views/shared/admin/form/_room.html.erb new file mode 100644 index 0000000..22581b7 --- /dev/null +++ b/app/views/shared/admin/form/_room.html.erb @@ -0,0 +1,10 @@ +<%= simple_form_for room, as: :room, url: path, + class: "form-group row" do |f| %> +
    + <%= f.input :name, placeholder: t('.placeholders.name'), input_html: { :tabindex => 1 }%> + <%= f.input :level, placeholder: t('.placeholders.level'), input_html: { :tabindex => 2} %> + <%= f.association :building, input_html: { :tabindex => 3 } %> +
    + + <%= render 'shared/form/actions', f:f %> +<% end %> diff --git a/app/views/shared/admin/navbar/_location_navbar.html.erb b/app/views/shared/admin/navbar/_location_navbar.html.erb index 8bf3dd1..1a9db7d 100644 --- a/app/views/shared/admin/navbar/_location_navbar.html.erb +++ b/app/views/shared/admin/navbar/_location_navbar.html.erb @@ -6,12 +6,12 @@ <% end %>
    - <%= link_to admin_control_point_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'room' } text-start" do %> + <%= link_to admin_room_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'room' } text-start" do %> <%= 'Помещения' %> <% end %>
    - <%= link_to admin_control_point_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'building' } text-start" do %> + <%= link_to admin_building_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'building' } text-start" do %> <%= 'Здания' %> <% end %>
    diff --git a/config/routes.rb b/config/routes.rb index e2bf2eb..843d6db 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,7 +20,7 @@ post :create_inspection, :to => 'device#create_inspection' end resources :device_model, :measurement_class - resources :organization, :division, :service, :control_point, :manufacturer, :measurement_group, :device_reg_group, + resources :room, :building, :organization, :division, :service, :control_point, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, except: [:show] end @@ -33,7 +33,7 @@ resources :device do post :create_inspection, :to => 'device#create_inspection' end - resources :organization, :division, :service, :control_point, :device_model, :measurement_class, + resources :room, :building, :organization, :division, :service, :control_point, :device_model, :measurement_class, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, only: [:create, :new] resources :inspection, except: [:index] do diff --git a/test/controllers/admin/building_controller_test.rb b/test/controllers/admin/building_controller_test.rb new file mode 100644 index 0000000..ddc78ea --- /dev/null +++ b/test/controllers/admin/building_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class Admin::BuildingControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/admin/room_controller_test.rb b/test/controllers/admin/room_controller_test.rb new file mode 100644 index 0000000..b49c04b --- /dev/null +++ b/test/controllers/admin/room_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class Admin::RoomControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end From 2b8610b6cfa45a371087cfe088f6486ffcc9d354 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 1 Feb 2024 15:13:41 +0400 Subject: [PATCH 085/132] =?UTF-8?q?UPD:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE=D0=BA=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BE=D1=80=D0=B3=D0=B0=D0=BD=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D1=81=20l18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/admin/organization/edit.turbo_stream.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/organization/edit.turbo_stream.erb b/app/views/admin/organization/edit.turbo_stream.erb index bddec14..e3e741d 100644 --- a/app/views/admin/organization/edit.turbo_stream.erb +++ b/app/views/admin/organization/edit.turbo_stream.erb @@ -1,3 +1,3 @@ -<%= turbo_stream.update "modal-title", t('admin.organization.edit.edit_organization') %> +<%= turbo_stream.update "modal-title", t('admin.organization.edit') %> <%= turbo_stream.update "modal-body", partial: "shared/admin/form/organization", locals: { path: admin_organization_path(@organization), organization: @organization } %> From c7f2d53a5334ed7c550ae2583b376750e96d7d5e Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 2 Feb 2024 08:58:24 +0400 Subject: [PATCH 086/132] =?UTF-8?q?UPD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BA=D0=BE=D0=BB=D0=BE=D0=BD=D0=BA?= =?UTF-8?q?=D1=83=20=D1=81=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=D0=BC=20=D0=B2=20Rooms=20=D0=B8=20Buildings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/concerns/building_concern.rb | 1 + app/controllers/concerns/room_consern.rb | 1 + app/models/building.rb | 2 +- app/models/room.rb | 2 +- app/views/admin/building/index.html.erb | 6 ++++++ app/views/admin/room/index.html.erb | 6 ++++++ app/views/shared/admin/form/_building.html.erb | 5 +++-- app/views/shared/admin/form/_room.html.erb | 3 ++- config/locales/ru.yml | 6 +++++- db/migrate/20240202044407_add_room_description_column.rb | 5 +++++ .../20240202044416_add_building_description_column.rb | 5 +++++ db/schema.rb | 4 +++- 12 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20240202044407_add_room_description_column.rb create mode 100644 db/migrate/20240202044416_add_building_description_column.rb diff --git a/app/controllers/concerns/building_concern.rb b/app/controllers/concerns/building_concern.rb index 3b7b340..2a1b1ec 100644 --- a/app/controllers/concerns/building_concern.rb +++ b/app/controllers/concerns/building_concern.rb @@ -48,6 +48,7 @@ def building_params params.require(:building).permit( :name, :organization_id, + :description, ) end end diff --git a/app/controllers/concerns/room_consern.rb b/app/controllers/concerns/room_consern.rb index bd65252..8636930 100644 --- a/app/controllers/concerns/room_consern.rb +++ b/app/controllers/concerns/room_consern.rb @@ -47,6 +47,7 @@ def room_params :name, :building_id, :level, + :description, ) end end diff --git a/app/models/building.rb b/app/models/building.rb index 55d74e1..6cd5c8b 100644 --- a/app/models/building.rb +++ b/app/models/building.rb @@ -8,6 +8,6 @@ class Building < ApplicationRecord validates :name, presence: true def self.ransackable_attributes(_auth_object = nil) - ['created_at', 'id', 'name', 'organization_id', 'updated_at'] + ['created_at', 'description', 'id', 'name', 'organization_id', 'updated_at'] end end diff --git a/app/models/room.rb b/app/models/room.rb index e2430a7..a15cea3 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -7,6 +7,6 @@ class Room < ApplicationRecord validates :name, :building, presence: true def self.ransackable_attributes(_auth_object = nil) - ['building_id', 'created_at', 'id', 'level', 'name', 'updated_at'] + ['building_id', 'created_at', 'description', 'id', 'level', 'name', 'updated_at'] end end diff --git a/app/views/admin/building/index.html.erb b/app/views/admin/building/index.html.erb index ce50c4e..ee80518 100644 --- a/app/views/admin/building/index.html.erb +++ b/app/views/admin/building/index.html.erb @@ -38,6 +38,7 @@
    <%= t('activerecord.attributes.building.id') %> <%= t('activerecord.attributes.building.name') %> <%= t('activerecord.attributes.building.organization') %><%= t('activerecord.attributes.building.description') %> <%= t('action') %>
    <%= building.organization.name %><%= "——" %><%= building.description %>
    <%= link_to edit_admin_building_path(building.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> diff --git a/app/views/admin/room/index.html.erb b/app/views/admin/room/index.html.erb index a19302b..351440e 100644 --- a/app/views/admin/room/index.html.erb +++ b/app/views/admin/room/index.html.erb @@ -41,6 +41,7 @@
    <%= t('activerecord.attributes.room.name') %> <%= t('activerecord.attributes.room.building') %> <%= t('activerecord.attributes.room.level') %><%= t('activerecord.attributes.room.description') %> <%= t('action') %>
    <%= room.level %><%= "——" %><%= room.description %>
    <%= link_to edit_admin_room_path(room.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> diff --git a/app/views/shared/admin/form/_building.html.erb b/app/views/shared/admin/form/_building.html.erb index ae7cc44..e3158bf 100644 --- a/app/views/shared/admin/form/_building.html.erb +++ b/app/views/shared/admin/form/_building.html.erb @@ -1,8 +1,9 @@ <%= simple_form_for building, as: :building, url: path, class: "form-group row" do |f| %>
    - <%= f.input :name, placeholder: t('.placeholders.name'), input_html: {:tabindex => 1}%> - <%= f.association :organization, input_html: {:tabindex => 2} %> + <%= f.input :name, placeholder: t('.placeholders.name'), input_html: { :tabindex => 1 }%> + <%= f.input :description, placeholder: t('.placeholders.description'), input_html: { :tabindex => 2 } %> + <%= f.association :organization, input_html: { :tabindex => 3 } %>
    <%= render 'shared/form/actions', f:f %> diff --git a/app/views/shared/admin/form/_room.html.erb b/app/views/shared/admin/form/_room.html.erb index 22581b7..a8f9eb7 100644 --- a/app/views/shared/admin/form/_room.html.erb +++ b/app/views/shared/admin/form/_room.html.erb @@ -3,7 +3,8 @@
    <%= f.input :name, placeholder: t('.placeholders.name'), input_html: { :tabindex => 1 }%> <%= f.input :level, placeholder: t('.placeholders.level'), input_html: { :tabindex => 2} %> - <%= f.association :building, input_html: { :tabindex => 3 } %> + <%= f.input :description, placeholder: t('.placeholders.description'), input_html: { :tabindex => 3 } %> + <%= f.association :building, input_html: { :tabindex => 4 } %>
    <%= render 'shared/form/actions', f:f %> diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 83ee49b..5f60c03 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -204,6 +204,7 @@ ru: name: Название organization_id: Организация organization: Организация + description: Описание control_point: name: Название room: Помещение @@ -282,6 +283,7 @@ ru: building: Здание building_id: Здание level: Уровень + description: Описание post: title: Заголовок body: Текст записи @@ -341,10 +343,12 @@ ru: organization: Организация placeholders: name: "106" + description: "Здание СМ-3 и РБТ-6" room: placeholders: name: "112" - level: "-0.8" + level: "-4.8" + description: "Кобальтовый участок" organization: labels: name: Название diff --git a/db/migrate/20240202044407_add_room_description_column.rb b/db/migrate/20240202044407_add_room_description_column.rb new file mode 100644 index 0000000..91f0236 --- /dev/null +++ b/db/migrate/20240202044407_add_room_description_column.rb @@ -0,0 +1,5 @@ +class AddRoomDescriptionColumn < ActiveRecord::Migration[7.0] + def change + add_column :rooms, :description, :string + end +end diff --git a/db/migrate/20240202044416_add_building_description_column.rb b/db/migrate/20240202044416_add_building_description_column.rb new file mode 100644 index 0000000..4dea84b --- /dev/null +++ b/db/migrate/20240202044416_add_building_description_column.rb @@ -0,0 +1,5 @@ +class AddBuildingDescriptionColumn < ActiveRecord::Migration[7.0] + def change + add_column :buildings, :description, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 009c86d..5573bcb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_01_29_094419) do +ActiveRecord::Schema[7.0].define(version: 2024_02_02_044416) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -19,6 +19,7 @@ t.bigint "organization_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "description" t.index ["organization_id"], name: "index_buildings_on_organization_id" end @@ -216,6 +217,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "level" + t.string "description" t.index ["building_id"], name: "index_rooms_on_building_id" end From b0c907322a163d27bcee8572a48662c6be7fc111 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 2 Feb 2024 15:17:57 +0400 Subject: [PATCH 087/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B8?= =?UTF-8?q?=20=D0=BC=D0=B5=D0=B6=D0=B4=D1=83=20ControlPoint=20=D0=B8=20Dev?= =?UTF-8?q?ice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/control_point.rb | 2 +- app/models/device.rb | 2 +- ...pdate_device_and_control_point_relative.rb | 6 +++++ db/schema.rb | 8 +++--- db/seeds.rb | 25 ++++++++++--------- 5 files changed, 25 insertions(+), 18 deletions(-) create mode 100644 db/migrate/20240202104526_update_device_and_control_point_relative.rb diff --git a/app/models/control_point.rb b/app/models/control_point.rb index a0bd034..d091bae 100644 --- a/app/models/control_point.rb +++ b/app/models/control_point.rb @@ -1,9 +1,9 @@ class ControlPoint < ApplicationRecord belongs_to :room, optional: true - belongs_to :device, optional: true belongs_to :service has_one :channel + has_one :device validates :name, presence: true diff --git a/app/models/device.rb b/app/models/device.rb index e97aefe..e4bddfd 100644 --- a/app/models/device.rb +++ b/app/models/device.rb @@ -11,9 +11,9 @@ class Device < ApplicationRecord belongs_to :supplementary_kit, optional: true belongs_to :room, optional: true belongs_to :service + belongs_to :control_point, optional: true has_many :inspections - has_one :control_point validates :inventory_id, numericality: { less_than_or_equal_to: 2147483647 }, uniqueness: true, allow_nil: true validates :serial_id, :tabel_id, presence: true, uniqueness: true diff --git a/db/migrate/20240202104526_update_device_and_control_point_relative.rb b/db/migrate/20240202104526_update_device_and_control_point_relative.rb new file mode 100644 index 0000000..f0c29f2 --- /dev/null +++ b/db/migrate/20240202104526_update_device_and_control_point_relative.rb @@ -0,0 +1,6 @@ +class UpdateDeviceAndControlPointRelative < ActiveRecord::Migration[7.0] + def change + remove_reference :control_points, :device, index: true, foreign_key: :true + add_reference :devices, :control_point, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 5573bcb..f3e6734 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_02_02_044416) do +ActiveRecord::Schema[7.0].define(version: 2024_02_02_104526) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -54,11 +54,9 @@ t.string "name" t.string "description" t.bigint "room_id" - t.bigint "device_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "service_id" - t.index ["device_id"], name: "index_control_points_on_device_id" t.index ["room_id"], name: "index_control_points_on_room_id" t.index ["service_id"], name: "index_control_points_on_service_id" end @@ -121,6 +119,8 @@ t.bigint "room_id" t.string "inspection_expiration_status", default: "prepare_to_inspection", null: false t.string "status", default: "in_stock", null: false + t.bigint "control_point_id" + t.index ["control_point_id"], name: "index_devices_on_control_point_id" t.index ["device_model_id"], name: "index_devices_on_device_model_id" t.index ["device_reg_group_id"], name: "index_devices_on_device_reg_group_id" t.index ["inventory_id"], name: "index_devices_on_inventory_id", unique: true @@ -279,13 +279,13 @@ add_foreign_key "channels", "control_points" add_foreign_key "channels", "servers" add_foreign_key "channels", "services" - add_foreign_key "control_points", "devices" add_foreign_key "control_points", "rooms" add_foreign_key "control_points", "services" add_foreign_key "device_components", "supplementary_kits" add_foreign_key "device_models", "manufacturers" add_foreign_key "device_models", "measurement_classes" add_foreign_key "device_models", "measurement_groups" + add_foreign_key "devices", "control_points" add_foreign_key "devices", "device_models" add_foreign_key "devices", "device_reg_groups" add_foreign_key "devices", "rooms" diff --git a/db/seeds.rb b/db/seeds.rb index 4c48dbd..738bab9 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -293,6 +293,17 @@ ) end + +# seed ControlPoints +200.times do |i| + ControlPoint.create( + name: "Точка контроля #{i}", + description: "Возможное описание", + room: Room.find_by(id: rand(1..99)), + service: Service.find_by(id: rand(1..10)), + ) +end + # seed Device 1000.times do |i| @@ -305,7 +316,8 @@ year_of_production: 1990, year_of_commissioning: 1991, supplementary_kit: SupplementaryKit.find_by(id: rand(1..20)), - service: Service.find_by(id: rand(1..10)) + service: Service.find_by(id: rand(1..10)), + control_point: ControlPoint.find_by(id: rand(1..10)), ) end @@ -351,17 +363,6 @@ ) end -# seed ControlPoints -200.times do |i| - ControlPoint.create( - name: "Точка контроля #{i}", - description: "Возможное описание", - room: Room.find_by(id: rand(1..99)), - device: Device.find_by(id: rand(1..100)), - service: Service.find_by(id: rand(1..10)), - ) -end - # seed Channel 200.times do |i| From 0ba02f23bb64b40e810cb96969f26a02295ce2ee Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 5 Feb 2024 08:12:20 +0400 Subject: [PATCH 088/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20l18n=20=D0=BD=D0=B0=20shared?= =?UTF-8?q?=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/ru.yml | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 5f60c03..dd04b07 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -364,6 +364,42 @@ ru: phone: 7-55-25 fax: 7-55-25 email: info@niiar.ru + show: + device: + send_to_inspection: Отправить на инспекцию + admin_send_to_inspection: Отправить на инспекцию (админ) + index: + inspection: + device: Прибор + assigned_user: Инспектор + target: Цель + state: Состояние + conclusion: Заключение + conclusion_date: Дата заключения + description: Описание + creator: Направил + device_tabel_id: Таб.№ + device_serial_id: Сер.№ + text-no-new-task: Задач пока нет... + performer_last_name: Иванов + performer_first_name: Иван + performer_second_name: Иванович + inspection_description: Прибор был поверен... + creator_last_name: Петров + creator_first_name: Петр + creator_second_name: Петрович + conclusion_date_from: Дата заключения от + conclusion_date_to: Дата заключения до + device: + b_add_device: Прибор + b_add_device_component: Компонент + navbar: + inspection: + new_tasks: Новые задачи + my_tasks: Мои задачи + completed_tasks: Завершенные задачи + all_tasks: Все задачи + service_tasks: Задачи службы layouts: application: home: Главная @@ -376,10 +412,6 @@ ru: profile: Профиль logout: Выйти edit_me: Изм. профиль - show: - device: - send_to_inspection: Отправить на инспекцию - admin_send_to_inspection: Отправить на инспекцию (админ) navbar: inspection: new_tasks: Новые задачи From 327da1ad5192c6cb0a4b9b0c116cb06c6c400410 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 5 Feb 2024 10:25:41 +0400 Subject: [PATCH 089/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D1=83=20=D0=A1=D0=A0=D0=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Armstrong/Armstrong.jsx | 2 +- db/seeds.rb | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/javascript/Components/Armstrong/Armstrong.jsx b/app/javascript/Components/Armstrong/Armstrong.jsx index d236003..1fc6287 100644 --- a/app/javascript/Components/Armstrong/Armstrong.jsx +++ b/app/javascript/Components/Armstrong/Armstrong.jsx @@ -45,7 +45,7 @@ export default function Armstrong() { serverId: channel.server.id, channelId: channel.channel_id, name: channel.control_point.name, - deviceModel: channel.control_point.device.device_model.name, + deviceModel: channel.control_point.device ? channel.control_point.device.device_model.name : '——', location: channel.control_point.room.name, locationDescription: channel.control_point.description, eventSystemValue: channel.event_system_value.toExponential(3), diff --git a/db/seeds.rb b/db/seeds.rb index 738bab9..9bfb22d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -306,7 +306,8 @@ # seed Device -1000.times do |i| +100.times do |i| + puts ControlPoint.find_by(id: i + 1).name Device.create( inventory_id: i, serial_id: "#{i}-123-N", @@ -317,7 +318,7 @@ year_of_commissioning: 1991, supplementary_kit: SupplementaryKit.find_by(id: rand(1..20)), service: Service.find_by(id: rand(1..10)), - control_point: ControlPoint.find_by(id: rand(1..10)), + control_point: ControlPoint.find_by(id: i + 1), ) end @@ -365,7 +366,7 @@ # seed Channel -200.times do |i| +100.times do |i| Channel.create( channel_id: "#{i}", control_point: ControlPoint.find_by(id: rand(1..100)), @@ -388,3 +389,13 @@ state: "normal" ) end + +100.times do |i| + History.create( + channel_id: Channel.first, + event_impulse_value: rand(0.0..100.0), + event_system_value: Time.now, + event_not_system_value: rand(0.0..100.0), + event_datetime: Time.now, + ) +end From 32319d14fef7fa13e39642aa9c720c055d08deaa Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 12 Feb 2024 09:51:05 +0400 Subject: [PATCH 090/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20l18n=20=D0=BD=D0=B0=20shared?= =?UTF-8?q?=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B0=D1=85=20(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/layouts/application.html.erb | 8 ++++++- config/locales/ru.yml | 32 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 5e690df..ce2fb7b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -24,7 +24,13 @@ <% if user_signed_in? %>
  • <% if can? :read, :armstrong %> - <%= link_to t(".ARMS"), armstrong_index_path, class: "nav-link px-2 link-secondary" %> + <%= link_to armstrong_index_path, class: "nav-link px-2 link-secondary position-relative" do %> + <%= t(".ARMS") %> + + β + Этот раздел в разработке + + <% end %> <% end %>
  • diff --git a/config/locales/ru.yml b/config/locales/ru.yml index dd04b07..35496ec 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -208,6 +208,7 @@ ru: control_point: name: Название room: Помещение + room_id: Помещение description: Описание device: Устройство device: @@ -265,6 +266,14 @@ ru: arms_device_type: ARMS-тип measurement_group: name: Название + organization: + id: ID + name: Название + full_address: Адрес + zip_code: Индекс + phone: Телефон + fax: Факс + email: E-mail device_component: serial_id: Сер. номер name: Название @@ -273,6 +282,11 @@ ru: measuring_unit: Ед. изм. description: Описание supplementary_kit: Набор + division: + id: ID + name: Название + organization: Организация + organization_id: Организация supplementary_kit: serial_id: Серийный номер name: Название @@ -289,6 +303,15 @@ ru: body: Текст записи user: Автор category: Категория + service: + id: ID + name: Название + division: Подразделение + division_id: Подразделение + organization: Организация + organization_id: Организация + building: Здание + building_id: Здание user: tabel_id: Таб. № first_name: Имя @@ -337,6 +360,12 @@ ru: shared: admin: form: + division: + labels: + name: Название + division: Подразделение + organization: Организация + building: Здание building: labels: name: Название @@ -369,6 +398,9 @@ ru: send_to_inspection: Отправить на инспекцию admin_send_to_inspection: Отправить на инспекцию (админ) index: + division: + id: ID + name: Название inspection: device: Прибор assigned_user: Инспектор From 89172a29bcd3b3ed8c3b229691ffb08e31d479bf Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 12 Feb 2024 14:25:01 +0400 Subject: [PATCH 091/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BF=D0=B0=D0=B4=D0=B0=D1=8E?= =?UTF-8?q?=D1=89=D0=B8=D0=B9=20=D1=82=D0=B5=D1=81=D1=82=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20Device?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/concerns/device_concern.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/concerns/device_concern.rb b/app/controllers/concerns/device_concern.rb index 1acd34d..b0fdb72 100644 --- a/app/controllers/concerns/device_concern.rb +++ b/app/controllers/concerns/device_concern.rb @@ -43,9 +43,8 @@ def device_update(device) def device_destroy(device, path_success, path_failure) assigned_inspections_count = Inspection.where(device_id: device.id).count - assigned_channels_count = ControlPoint.where(device_id: device.id).count - if assigned_inspections_count.zero? && assigned_channels_count.zero? + if assigned_inspections_count.zero? && device.control_point_id? device.destroy redirect_to(path_success) else From 83fadf67020dc8f554b723e1db951cc3d50954f3 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 12 Feb 2024 14:51:30 +0400 Subject: [PATCH 092/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=82=D0=BE=D1=87=D0=BA=D0=B8=20=D0=BA=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=BB=D1=8F=20=D0=B8=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20Device?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/concerns/device_concern.rb | 2 +- app/views/shared/admin/form/_control_point.html.erb | 10 ---------- app/views/shared/form/_device.html.erb | 2 +- config/locales/ru.yml | 1 + 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/app/controllers/concerns/device_concern.rb b/app/controllers/concerns/device_concern.rb index b0fdb72..ee3f035 100644 --- a/app/controllers/concerns/device_concern.rb +++ b/app/controllers/concerns/device_concern.rb @@ -87,7 +87,7 @@ def device_params :year_of_production, :year_of_commissioning, :supplementary_kit_id, - :room_id, + :control_point_id, :service_id) end end diff --git a/app/views/shared/admin/form/_control_point.html.erb b/app/views/shared/admin/form/_control_point.html.erb index 2c41434..495c456 100644 --- a/app/views/shared/admin/form/_control_point.html.erb +++ b/app/views/shared/admin/form/_control_point.html.erb @@ -11,16 +11,6 @@
    <%= render 'shared/modal_button_add', path: new_admin_control_point_path, classes: "btn btn-ligth", text: nil %>
    -
  • - <%= f.label :device, class: "mb-2" %> -
    -
    - <%= f.association :device, label: false, input_html: {:tabindex => 3}, - label_method: ->(device) { "#{device.tabel_id}\t-\t#{device.device_model.name}" } %> -
    -
    - <%= render 'shared/modal_button_add', path: new_admin_control_point_path, classes: "btn btn-ligth", text: nil %> -
    <%= f.input :description, placeholder: 'Описание точки контроля', input_html: {:tabindex => 4}%> <% if current_user.admin? %> diff --git a/app/views/shared/form/_device.html.erb b/app/views/shared/form/_device.html.erb index 4950588..83afc6e 100644 --- a/app/views/shared/form/_device.html.erb +++ b/app/views/shared/form/_device.html.erb @@ -58,7 +58,7 @@ <%= f.input :service_id, label: false, input_html: {hidden: true, value: current_user.service_id} %> <% end %>
    - <%= f.association :room, input_html: {:tabindex => 10}, include_blank: t('combobox_blank')%> + <%= f.association :control_point, input_html: {:tabindex => 10}, include_blank: t('combobox_blank')%>
    diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 35496ec..ed280f9 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -213,6 +213,7 @@ ru: device: Устройство device: inventory_id: Инв. № + control_point: Точка контроля device_model: Модель device_model_id: Модель serial_id: Сер. № From f80c7f49c05f5cf5d223d8d91a7d0c34c0065c44 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 12 Feb 2024 15:17:54 +0400 Subject: [PATCH 093/132] =?UTF-8?q?UPD:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86?= =?UTF-8?q?=D1=83=20=D0=BF=D1=80=D0=B8=D0=B1=D0=BE=D1=80=D0=BE=D0=B2,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BC=D0=B5=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8?= =?UTF-8?q?=20=D1=82=D0=BE=D1=87=D0=BA=D0=B8=20=D0=BA=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/shared/index/_device.html.erb | 13 +++++++++++-- config/locales/ru.yml | 4 ++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/views/shared/index/_device.html.erb b/app/views/shared/index/_device.html.erb index d9af8d5..795cfaa 100644 --- a/app/views/shared/index/_device.html.erb +++ b/app/views/shared/index/_device.html.erb @@ -75,14 +75,16 @@
    - +
    - + + + <% if current_user.admin? %> <% end %> @@ -99,6 +101,13 @@ + <% if device.control_point_id? %> + + + <% else %> + + + <% end %> <% if current_user.admin? %> <% end %> diff --git a/config/locales/ru.yml b/config/locales/ru.yml index ed280f9..5c709d9 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -221,8 +221,8 @@ ru: device_reg_group: Рег. группа year_of_production: Год выпуска year_of_commissioning: Год ввода - room: Расположение - room_id: Расположение + room: Помещение + room_id: Помещение date_of_inspection: Дата инспекции supplementary_kit: Набор service: Служба From eafb6d8d4d8e7c56b4b41a28c321c393ca3bad6a Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 14 Feb 2024 15:36:29 +0400 Subject: [PATCH 094/132] =?UTF-8?q?UPD:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D1=81=D0=B2=D1=8F=D0=B7=D1=8C?= =?UTF-8?q?=20=D0=B8=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D1=8F?= =?UTF-8?q?=D0=B5=D1=82=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/channel_controller.rb | 9 ++ app/controllers/admin/server_controller.rb | 3 + app/controllers/concerns/channel_concern.rb | 10 ++ app/controllers/concerns/device_concern.rb | 2 +- app/controllers/device_controller.rb | 2 + app/models/channel.rb | 9 ++ app/models/control_point.rb | 2 +- app/models/device.rb | 4 +- app/views/admin/channel/index.html.erb | 114 ++++++++++++++++++ app/views/admin/server/index.html.erb | 0 app/views/layouts/application.html.erb | 12 +- .../shared/admin/form/_control_point.html.erb | 3 +- app/views/shared/admin/navbar/_admin.html.erb | 3 + .../shared/admin/navbar/_asrc_navbar.html.erb | 13 ++ app/views/shared/form/_device.html.erb | 9 +- app/views/shared/index/_device.html.erb | 37 ++++-- config/routes.rb | 4 +- .../20240214043200_revert_last_migration.rb | 8 ++ ...9_rename_consumption_column_for_channel.rb | 5 + db/schema.rb | 12 +- db/seeds.rb | 33 +++-- .../admin/channel_controller_test.rb | 7 ++ .../admin/server_controller_test.rb | 7 ++ 23 files changed, 269 insertions(+), 39 deletions(-) create mode 100644 app/controllers/admin/channel_controller.rb create mode 100644 app/controllers/admin/server_controller.rb create mode 100644 app/controllers/concerns/channel_concern.rb create mode 100644 app/views/admin/channel/index.html.erb create mode 100644 app/views/admin/server/index.html.erb create mode 100644 app/views/shared/admin/navbar/_asrc_navbar.html.erb create mode 100644 db/migrate/20240214043200_revert_last_migration.rb create mode 100644 db/migrate/20240214111919_rename_consumption_column_for_channel.rb create mode 100644 test/controllers/admin/channel_controller_test.rb create mode 100644 test/controllers/admin/server_controller_test.rb diff --git a/app/controllers/admin/channel_controller.rb b/app/controllers/admin/channel_controller.rb new file mode 100644 index 0000000..abc5ed2 --- /dev/null +++ b/app/controllers/admin/channel_controller.rb @@ -0,0 +1,9 @@ +class Admin::ChannelController < ApplicationController + include ChannelConcern + load_and_authorize_resource + # before_action :set_building, only: [:show, :edit, :update, :destroy] + + def index + channel_index + end +end diff --git a/app/controllers/admin/server_controller.rb b/app/controllers/admin/server_controller.rb new file mode 100644 index 0000000..add1cdb --- /dev/null +++ b/app/controllers/admin/server_controller.rb @@ -0,0 +1,3 @@ +class Admin::ServerController < ApplicationController + def index; end +end diff --git a/app/controllers/concerns/channel_concern.rb b/app/controllers/concerns/channel_concern.rb new file mode 100644 index 0000000..c9fa073 --- /dev/null +++ b/app/controllers/concerns/channel_concern.rb @@ -0,0 +1,10 @@ +module ChannelConcern + extend ActiveSupport::Concern + + included do + def channel_index + @query = Channel.ransack(params[:q]) + @pagy, @channels = pagy(@query.result.order(:id)) + end + end +end diff --git a/app/controllers/concerns/device_concern.rb b/app/controllers/concerns/device_concern.rb index ee3f035..6ec4697 100644 --- a/app/controllers/concerns/device_concern.rb +++ b/app/controllers/concerns/device_concern.rb @@ -87,7 +87,7 @@ def device_params :year_of_production, :year_of_commissioning, :supplementary_kit_id, - :control_point_id, + :control_point_ids, :service_id) end end diff --git a/app/controllers/device_controller.rb b/app/controllers/device_controller.rb index 65dda82..20ecee6 100644 --- a/app/controllers/device_controller.rb +++ b/app/controllers/device_controller.rb @@ -4,6 +4,8 @@ class DeviceController < ApplicationController before_action :set_device, only: [:show, :edit, :update, :destroy] load_and_authorize_resource + @seporator = ',' + def index device_index end diff --git a/app/models/channel.rb b/app/models/channel.rb index 4f57cc2..76d7121 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -4,4 +4,13 @@ class Channel < ApplicationRecord has_many :history belongs_to :control_point + + def self.ransackable_associations(_auth_object = nil) + ['control_point', 'history', 'server', 'service'] + end + + def self.ransackable_attributes(_auth_object = nil) + ['channel_id', 'consumption', 'control_point_id', 'conversion_coefficient', 'created_at', 'emergency_limit', 'event_count', + 'event_datetime', 'event_error_count', 'event_impulse_value', 'event_not_system_value', 'event_system_value', 'id', 'is_online', 'is_special_control', 'location_description', 'pre_emergency_limit', 'self_background', 'server_id', 'service_id', 'state', 'updated_at'] + end end diff --git a/app/models/control_point.rb b/app/models/control_point.rb index d091bae..7bf8344 100644 --- a/app/models/control_point.rb +++ b/app/models/control_point.rb @@ -3,7 +3,7 @@ class ControlPoint < ApplicationRecord belongs_to :service has_one :channel - has_one :device + belongs_to :device, optional: true validates :name, presence: true diff --git a/app/models/device.rb b/app/models/device.rb index e4bddfd..f2b2c14 100644 --- a/app/models/device.rb +++ b/app/models/device.rb @@ -5,20 +5,22 @@ class Device < ApplicationRecord current_year = Date.today.year year_error_msg = "должен быть больше 1900 и меньше или равен #{current_year}" + inspection_interval_msg = 'должен быть от 0.1 (раз в месяц) до 10.0 (раз в 10 лет)' belongs_to :device_model belongs_to :device_reg_group belongs_to :supplementary_kit, optional: true belongs_to :room, optional: true belongs_to :service - belongs_to :control_point, optional: true + has_many :control_point has_many :inspections validates :inventory_id, numericality: { less_than_or_equal_to: 2147483647 }, uniqueness: true, allow_nil: true validates :serial_id, :tabel_id, presence: true, uniqueness: true validates :year_of_commissioning, :year_of_production, numericality: { in: 1900..current_year, message: year_error_msg }, allow_nil: true validates :year_of_production, presence: true + validates :inspection_interval, presence: true, numericality: { in: 0.1..10.0, message: inspection_interval_msg } STATUS = { verified: 'verified', diff --git a/app/views/admin/channel/index.html.erb b/app/views/admin/channel/index.html.erb new file mode 100644 index 0000000..6839630 --- /dev/null +++ b/app/views/admin/channel/index.html.erb @@ -0,0 +1,114 @@ +<% provide :page_title, "Точки контроля" %> +<%= render 'shared/admin/navbar/admin', selected: "asrc" %> +<%= render 'shared/flash_alert' %> +
    +
    + <%= render 'shared/admin/navbar/asrc_navbar', selected: "channel" %> +
    +
    +

    + +

    +
    + <%= search_form_for(@query, url: admin_channel_index_path, method: :get, class: "rounded accordion-body") do |f| %> +
    + <%= f.label :id, class: "mb-2" %> + <%= f.search_field :id_cont, class:"form-control", placeholder:"ID" %> +
    + <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%> +
    + <%= t("b_clear")%> +
    + <% end %> +
    +
    +
    + +
    +
    +
    +
    <%=t('activerecord.attributes.device.tabel_id')%> <%=t('activerecord.attributes.device.serial_id')%> <%=t('activerecord.attributes.device.device_model')%> <%=t('activerecord.attributes.device.year_of_production')%> <%=t('activerecord.attributes.device.year_of_commissioning')%><%=t('activerecord.attributes.device.room')%><%=t('activerecord.attributes.device.control_point')%><%=t('activerecord.attributes.device.service')%><%= device.device_model.name %> <%= device.year_of_production %> <%= device.year_of_commissioning %><%= device.control_point.room.name %><%= device.control_point.name %><%= "——" %><%= "——" %><%= device.service.name %>
    + + + + + + + + + + + + + + + + + + + + <% @channels.each do |channel| %> + + + + + <% if channel.control_point.nil? %> + + <% else %> + + + + + + + + + + + + <% end %> + +
    <%= t('activerecord.attributes.channel.id') %><%= t('activerecord.attributes.channel.channel_id')%><%= t('activerecord.attributes.channel.server')%><%= t('activerecord.attributes.channel.control_point.name')%><%= t('activerecord.attributes.channel.location_description')%><%= t('activerecord.attributes.channel.self_background')%><%= t('activerecord.attributes.channel.pre_emergency_limit')%><%= t('activerecord.attributes.channel.emergency_limit')%><%= t('activerecord.attributes.channel.consumption')%><%= t('activerecord.attributes.channel.conversion_coefficient')%><%= t('activerecord.attributes.channel.service')%><%= t('action') %>
    <%= channel.id %><%= channel.channel_id%><%= channel.server_id%><%= "——" %><%= channel.control_point.name %> + <% end %> + <%= channel.location_description %><%= channel.self_background %><%= channel.pre_emergency_limit %><%= channel.emergency_limit %><%= channel.consumption %><%= channel.conversion_coefficient %><%= channel.service.name %> +
    + <%= link_to edit_admin_channel_path(channel.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to admin_channel_path(channel.id), method: :delete, class: "btn" do %> + + <% end %> +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    + <%= pagination @pagy %> +
    +
    +
    +
    +
    + diff --git a/app/views/admin/server/index.html.erb b/app/views/admin/server/index.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index ce2fb7b..621e5f1 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -26,7 +26,7 @@ <% if can? :read, :armstrong %> <%= link_to armstrong_index_path, class: "nav-link px-2 link-secondary position-relative" do %> <%= t(".ARMS") %> - + β Этот раздел в разработке @@ -35,8 +35,14 @@
  • <% if can? :read, Device %> - <%= link_to t(".devices"), device_index_path, class: "nav-link px-2 link-secondary" %> - <%end%> + <%= link_to device_index_path, class: "nav-link px-2 link-secondary position-relative" do %> + <%= t(".devices") %> + + β + Этот раздел в разработке + + <% end %> + <% end %>
  • <% if can? :read, Inspection %> diff --git a/app/views/shared/admin/form/_control_point.html.erb b/app/views/shared/admin/form/_control_point.html.erb index 495c456..888a422 100644 --- a/app/views/shared/admin/form/_control_point.html.erb +++ b/app/views/shared/admin/form/_control_point.html.erb @@ -13,9 +13,10 @@ <%= f.input :description, placeholder: 'Описание точки контроля', input_html: {:tabindex => 4}%> + <%= f.association :device, label_method: :tabel_id, input_html: {:tabindex => 3} %> <% if current_user.admin? %>
    - <%= f.association :service, input_html: {:tabindex => 9}, include_blank: false %> + <%= f.association :service, input_html: {:tabindex => 4}, include_blank: false %>
    <% else %> <%= f.input :service_id, label: false, input_html: {hidden: true, value: current_user.service_id} %> diff --git a/app/views/shared/admin/navbar/_admin.html.erb b/app/views/shared/admin/navbar/_admin.html.erb index c4b4588..35550ed 100644 --- a/app/views/shared/admin/navbar/_admin.html.erb +++ b/app/views/shared/admin/navbar/_admin.html.erb @@ -9,4 +9,7 @@
  • + diff --git a/app/views/shared/admin/navbar/_asrc_navbar.html.erb b/app/views/shared/admin/navbar/_asrc_navbar.html.erb new file mode 100644 index 0000000..bb98cc0 --- /dev/null +++ b/app/views/shared/admin/navbar/_asrc_navbar.html.erb @@ -0,0 +1,13 @@ +
    + <% selected = local_assigns[:selected] %> +
    + <%= link_to admin_channel_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'channel' } text-start" do %> + <%= 'Каналы' %> + <% end %> +
    +
    + <%= link_to admin_channel_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'server' } text-start" do %> + <%= 'Серверы' %> + <% end %> +
    +
    diff --git a/app/views/shared/form/_device.html.erb b/app/views/shared/form/_device.html.erb index 83afc6e..a593b76 100644 --- a/app/views/shared/form/_device.html.erb +++ b/app/views/shared/form/_device.html.erb @@ -49,7 +49,7 @@ -
    +
    <% if current_user.admin? %>
    <%= f.association :service, input_html: {:tabindex => 9}, include_blank: false %> @@ -58,7 +58,12 @@ <%= f.input :service_id, label: false, input_html: {hidden: true, value: current_user.service_id} %> <% end %>
    - <%= f.association :control_point, input_html: {:tabindex => 10}, include_blank: t('combobox_blank')%> + <%= f.label :control_point %> +
    + <% ControlPoint.where(device_id: device.id).each do |e| %> + <%= content_tag(:span, e.name, class: "btn btn-light btn-sm") %> + <% end %> +
    diff --git a/app/views/shared/index/_device.html.erb b/app/views/shared/index/_device.html.erb index 795cfaa..80aa2a9 100644 --- a/app/views/shared/index/_device.html.erb +++ b/app/views/shared/index/_device.html.erb @@ -78,6 +78,7 @@ + @@ -89,24 +90,45 @@ <% end %> - <% @devices.each do |device| %> <% path = "#{show_path}#{device.id}" %> + <% if device.status == Device::STATUS[:in_storage] %> + + <% elsif device.status == Device::STATUS[:expired] %> + + <% elsif device.status == Device::STATUS[:on_repair] %> + + <% elsif device.status == Device::STATUS[:in_stock] %> + + <% elsif device.status == Device::STATUS[:sended_to_inspection] %> + + <% end %> + - <% if device.control_point_id? %> - - - <% else %> + <% if device.control_point.nil? %> + <% else %> + + <% end %> <% if current_user.admin? %> @@ -124,11 +146,6 @@ <%= device.last_successful_inspection %> <% end %> - <% if device.last_successful_inspection.present? %> - - <% else %> - - <% end %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index 843d6db..1b8afcd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,7 +20,7 @@ post :create_inspection, :to => 'device#create_inspection' end resources :device_model, :measurement_class - resources :room, :building, :organization, :division, :service, :control_point, :manufacturer, :measurement_group, :device_reg_group, + resources :server, :channel, :room, :building, :organization, :division, :service, :control_point, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, except: [:show] end @@ -33,7 +33,7 @@ resources :device do post :create_inspection, :to => 'device#create_inspection' end - resources :room, :building, :organization, :division, :service, :control_point, :device_model, :measurement_class, + resources :server, :channel, :room, :building, :organization, :division, :service, :control_point, :device_model, :measurement_class, :manufacturer, :measurement_group, :device_reg_group, :supplementary_kit, :device_component, only: [:create, :new] resources :inspection, except: [:index] do diff --git a/db/migrate/20240214043200_revert_last_migration.rb b/db/migrate/20240214043200_revert_last_migration.rb new file mode 100644 index 0000000..b9c32d2 --- /dev/null +++ b/db/migrate/20240214043200_revert_last_migration.rb @@ -0,0 +1,8 @@ +class RevertLastMigration < ActiveRecord::Migration[7.0] + def change + remove_reference :devices, :control_point, index: true, foreign_key: :true + add_reference :control_points, :device, foreign_key: true + add_column :devices, :inspection_interval, :float, default: 1.0, null: false + add_column :control_points, :control_point_type, :string + end +end diff --git a/db/migrate/20240214111919_rename_consumption_column_for_channel.rb b/db/migrate/20240214111919_rename_consumption_column_for_channel.rb new file mode 100644 index 0000000..a4db4e7 --- /dev/null +++ b/db/migrate/20240214111919_rename_consumption_column_for_channel.rb @@ -0,0 +1,5 @@ +class RenameConsumptionColumnForChannel < ActiveRecord::Migration[7.0] + def change + rename_column :channels, :consumptiom, :consumption + end +end diff --git a/db/schema.rb b/db/schema.rb index f3e6734..f193735 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_02_02_104526) do +ActiveRecord::Schema[7.0].define(version: 2024_02_14_111919) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -31,7 +31,7 @@ t.float "self_background", default: 0.0 t.float "pre_emergency_limit", default: 1.0 t.float "emergency_limit", default: 2.0 - t.float "consumptiom", default: 1.0 + t.float "consumption", default: 1.0 t.float "conversion_coefficient", default: 0.0 t.float "event_system_value", default: 0.0 t.float "event_not_system_value", default: 0.0 @@ -57,6 +57,9 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "service_id" + t.bigint "device_id" + t.string "control_point_type" + t.index ["device_id"], name: "index_control_points_on_device_id" t.index ["room_id"], name: "index_control_points_on_room_id" t.index ["service_id"], name: "index_control_points_on_service_id" end @@ -119,8 +122,7 @@ t.bigint "room_id" t.string "inspection_expiration_status", default: "prepare_to_inspection", null: false t.string "status", default: "in_stock", null: false - t.bigint "control_point_id" - t.index ["control_point_id"], name: "index_devices_on_control_point_id" + t.float "inspection_interval", default: 1.0, null: false t.index ["device_model_id"], name: "index_devices_on_device_model_id" t.index ["device_reg_group_id"], name: "index_devices_on_device_reg_group_id" t.index ["inventory_id"], name: "index_devices_on_inventory_id", unique: true @@ -279,13 +281,13 @@ add_foreign_key "channels", "control_points" add_foreign_key "channels", "servers" add_foreign_key "channels", "services" + add_foreign_key "control_points", "devices" add_foreign_key "control_points", "rooms" add_foreign_key "control_points", "services" add_foreign_key "device_components", "supplementary_kits" add_foreign_key "device_models", "manufacturers" add_foreign_key "device_models", "measurement_classes" add_foreign_key "device_models", "measurement_groups" - add_foreign_key "devices", "control_points" add_foreign_key "devices", "device_models" add_foreign_key "devices", "device_reg_groups" add_foreign_key "devices", "rooms" diff --git a/db/seeds.rb b/db/seeds.rb index 9bfb22d..4c30485 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -293,21 +293,9 @@ ) end - -# seed ControlPoints -200.times do |i| - ControlPoint.create( - name: "Точка контроля #{i}", - description: "Возможное описание", - room: Room.find_by(id: rand(1..99)), - service: Service.find_by(id: rand(1..10)), - ) -end - # seed Device 100.times do |i| - puts ControlPoint.find_by(id: i + 1).name Device.create( inventory_id: i, serial_id: "#{i}-123-N", @@ -318,7 +306,18 @@ year_of_commissioning: 1991, supplementary_kit: SupplementaryKit.find_by(id: rand(1..20)), service: Service.find_by(id: rand(1..10)), - control_point: ControlPoint.find_by(id: i + 1), + inspection_interval: rand(0.1..9.9) + ) +end + +# seed ControlPoints +200.times do |i| + ControlPoint.create( + name: "Точка контроля #{i}", + description: "Возможное описание", + room: Room.find_by(id: rand(1..99)), + service: Service.find_by(id: rand(1..10)), + device: Device.find_by(id: rand(1..100)), ) end @@ -398,4 +397,12 @@ event_not_system_value: rand(0.0..100.0), event_datetime: Time.now, ) + + History.create( + channel_id: Channel.last, + event_impulse_value: rand(0.0..100.0), + event_system_value: Time.now, + event_not_system_value: rand(0.0..100.0), + event_datetime: Time.now, + ) end diff --git a/test/controllers/admin/channel_controller_test.rb b/test/controllers/admin/channel_controller_test.rb new file mode 100644 index 0000000..a495007 --- /dev/null +++ b/test/controllers/admin/channel_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class Admin::ChannelControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/admin/server_controller_test.rb b/test/controllers/admin/server_controller_test.rb new file mode 100644 index 0000000..c6fe0ca --- /dev/null +++ b/test/controllers/admin/server_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class Admin::ServerControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end From 207386669904f739f72e148bdcba4b4477a8e001 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 15 Feb 2024 13:40:50 +0400 Subject: [PATCH 095/132] =?UTF-8?q?UPD:=20=D1=83=D0=BC=D0=B5=D0=BD=D1=8C?= =?UTF-8?q?=D1=88=D0=B0=D0=B5=D1=82=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80?= =?UTF-8?q?=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D1=84=D0=B8=D0=BA=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=90=D0=A1?= =?UTF-8?q?=D0=A0=D0=9A=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/javascript/Components/Armstrong/Table/TableBody.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/javascript/Components/Armstrong/Table/TableBody.jsx b/app/javascript/Components/Armstrong/Table/TableBody.jsx index 06aca78..96bee36 100644 --- a/app/javascript/Components/Armstrong/Table/TableBody.jsx +++ b/app/javascript/Components/Armstrong/Table/TableBody.jsx @@ -13,7 +13,13 @@ export default function TableBody({ columns, data, openModal }) { let tData = row[accessor] ? row[accessor] : '——'; if (accessor === 'chart') { tData = ( - ); From 819e5e9606003a8827f85b36fb9ca129fc208d45 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 15 Feb 2024 13:41:57 +0400 Subject: [PATCH 096/132] =?UTF-8?q?UPD:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BD=D0=B0=20=D1=80=D1=83=D1=81=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/ru.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 5c709d9..10afaa9 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -211,6 +211,18 @@ ru: room_id: Помещение description: Описание device: Устройство + channel: + id: ID + channel_id: № кнл. + server: № срв. + control_point: Точка контроля + location_description: Описание располож. + self_background: Собств. фон + pre_emergency_limit: Пред.-ав. уст. + emergency_limit: Ав. уст. + consumption: Расход + conversion_coefficient: Коэфф. конв. + service: Служба device: inventory_id: Инв. № control_point: Точка контроля @@ -361,6 +373,18 @@ ru: shared: admin: form: + channel: + placeholders: + channel_id: ID канала (1...48) + server_id: ID сервера + server: Название сервера + location_description: Описание канала + self_background: Собственный фон (12.34) + pre_emergency_limit: Предаварийная уставка (123.45) + emergency_limit: Аварийная уставка (1234.56) + consumption: Расход (123.45) + conversion_coefficient: Коэфф. преобразования (0.0009) + service: Служба division: labels: name: Название From 9bf6b471577fec72b95cafb24fc9d5cc42f0ec01 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 15 Feb 2024 13:42:35 +0400 Subject: [PATCH 097/132] =?UTF-8?q?UPD:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=20=D0=B0=D0=B4=D0=BC=D0=B8?= =?UTF-8?q?=D0=BD=D0=BA=D1=83=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86?= =?UTF-8?q?=D1=83=20index=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=B0=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=20=D0=B8=20=D1=84=D1=83=D0=BD=D0=BA=D1=86?= =?UTF-8?q?=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=20=D0=B4=D0=BB=D1=8F=20=D1=81?= =?UTF-8?q?=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=B0=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/channel_controller.rb | 14 ++- app/controllers/concerns/channel_concern.rb | 31 +++++++ .../admin/channel/create.turbo_stream.erb | 1 + app/views/admin/channel/edit.turbo_stream.erb | 3 + app/views/admin/channel/index.html.erb | 7 +- app/views/admin/channel/new.turbo_stream.erb | 3 + .../admin/channel/update.turbo_stream.erb | 1 + app/views/shared/admin/form/_channel.html.erb | 91 +++++++++++++++++++ 8 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 app/views/admin/channel/create.turbo_stream.erb create mode 100644 app/views/admin/channel/edit.turbo_stream.erb create mode 100644 app/views/admin/channel/new.turbo_stream.erb create mode 100644 app/views/admin/channel/update.turbo_stream.erb create mode 100644 app/views/shared/admin/form/_channel.html.erb diff --git a/app/controllers/admin/channel_controller.rb b/app/controllers/admin/channel_controller.rb index abc5ed2..84f18d2 100644 --- a/app/controllers/admin/channel_controller.rb +++ b/app/controllers/admin/channel_controller.rb @@ -1,9 +1,21 @@ class Admin::ChannelController < ApplicationController include ChannelConcern load_and_authorize_resource - # before_action :set_building, only: [:show, :edit, :update, :destroy] + before_action :set_channel, only: [:show, :edit, :update, :destroy] def index channel_index end + + def new + @channel = Channel.new + end + + def create + channel_create + end + + def update; end + + def destroy; end end diff --git a/app/controllers/concerns/channel_concern.rb b/app/controllers/concerns/channel_concern.rb index c9fa073..5744010 100644 --- a/app/controllers/concerns/channel_concern.rb +++ b/app/controllers/concerns/channel_concern.rb @@ -6,5 +6,36 @@ def channel_index @query = Channel.ransack(params[:q]) @pagy, @channels = pagy(@query.result.order(:id)) end + + def channel_create + @channel = Channel.new(channel_params) + + if @channel.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + private + + def set_channel + @channel = Channel.find(channel_params[:id]) + end + + def channel_params + params.require(:channel).permit( + :channel_id, + :server_id, + :control_point_id, + :location_description, + :self_background, + :pre_emergency_limit, + :emergency_limit, + :consumption, + :conversion_coefficient, + :service_id, + ) + end end end diff --git a/app/views/admin/channel/create.turbo_stream.erb b/app/views/admin/channel/create.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/channel/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/admin/channel/edit.turbo_stream.erb b/app/views/admin/channel/edit.turbo_stream.erb new file mode 100644 index 0000000..fd44313 --- /dev/null +++ b/app/views/admin/channel/edit.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('.edit_channel') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/channel", + locals: { path: admin_channel_path(@channel), channel: @channel } %> diff --git a/app/views/admin/channel/index.html.erb b/app/views/admin/channel/index.html.erb index 6839630..a448ae4 100644 --- a/app/views/admin/channel/index.html.erb +++ b/app/views/admin/channel/index.html.erb @@ -25,7 +25,7 @@ - + <%= render 'shared/modal_button_add', path: new_admin_channel_path, classes: "btn btn-primary w-100", text: t("b_add") %>
    @@ -36,7 +36,6 @@
    - @@ -52,8 +51,8 @@ <% @channels.each do |channel| %> - - + + <% if channel.control_point.nil? %> <% else %> diff --git a/app/views/admin/channel/new.turbo_stream.erb b/app/views/admin/channel/new.turbo_stream.erb new file mode 100644 index 0000000..2cf090d --- /dev/null +++ b/app/views/admin/channel/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.channel.new.add_channel') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/channel", + locals: {path: admin_channel_index_path, channel: @channel } %> diff --git a/app/views/admin/channel/update.turbo_stream.erb b/app/views/admin/channel/update.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/channel/update.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/shared/admin/form/_channel.html.erb b/app/views/shared/admin/form/_channel.html.erb new file mode 100644 index 0000000..d5f033e --- /dev/null +++ b/app/views/shared/admin/form/_channel.html.erb @@ -0,0 +1,91 @@ +<%= simple_form_for channel, as: :channel, url: path, + class: "form-group row" do |f| %> + + + +
    +
    +
    + <%= f.label :channel_id %> +
    +
    + <%= f.input :channel_id, label: false, placeholder: t('.placeholders.channel_id'), input_html: {:tabindex => 1} %> +
    +
    +
    +
    + <%= f.label :server%> +
    +
    +
    + <%= f.association :server, label: false, input_html: {:tabindex => 2}, include_blank: false %> +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_server_path, classes: "btn btn-ligth", text: nil %> +
    +
    +
    +
    + +
    +
    +
    + <%= f.label :location_description %> +
    +
    + <%= f.input :location_description, as: :string, label: false, placeholder: t('.placeholders.location_description'), input_html: {:tabindex => 3} %> +
    +
    +
    +
    + <%= f.label :control_point, label_method: :name %> +
    +
    +
    + <%= f.association :control_point, label: false, input_html: {:tabindex => 4}, include_blank: false %> +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_control_point_path, classes: "btn btn-ligth", text: nil %> +
    +
    +
    +
    + +
    +
    + <%= f.input :self_background, placeholder: t('.placeholders.self_background'), input_html: {:tabindex => 5} %> +
    +
    + <%= f.input :consumption, placeholder: t('.placeholders.consumption'), input_html: {:tabindex => 6} %> +
    +
    + <%= f.input :conversion_coefficient, placeholder: t('.placeholders.conversion_coefficient'), input_html: {:tabindex => 7} %> +
    +
    + +
    +
    + <%= f.input :pre_emergency_limit, placeholder: t('.placeholders.pre_emergency_limit'), input_html: {:tabindex => 8} %> +
    +
    + <%= f.input :emergency_limit, placeholder: t('.placeholders.emergency_limit'), input_html: {:tabindex => 9} %> +
    +
    + + <% if current_user.admin? %> +
    + <%= f.association :service, input_html: { :tabindex => 10} %> +
    + <% end %> + + <%= render 'shared/form/actions', f:f %> +<% end %> From e75c3d785f50cf6ab43dfb30dca1dd5e057bd53e Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 15 Feb 2024 15:15:08 +0400 Subject: [PATCH 098/132] =?UTF-8?q?FIX:=20=D0=98=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B1=D0=B0=D0=B3=D0=B8=20?= =?UTF-8?q?=D0=B8=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/channel_controller.rb | 9 +++++-- app/controllers/concerns/channel_concern.rb | 27 +++++++++++++++++-- app/controllers/concerns/device_concern.rb | 2 +- app/models/channel.rb | 4 +++ app/views/admin/channel/index.html.erb | 19 +++++++++++-- app/views/admin/control_point/index.html.erb | 4 +-- app/views/shared/admin/form/_channel.html.erb | 2 +- app/views/shared/form/_device.html.erb | 9 +------ test/factories/channels.rb | 2 +- test/factories/control_points.rb | 1 + 10 files changed, 60 insertions(+), 19 deletions(-) diff --git a/app/controllers/admin/channel_controller.rb b/app/controllers/admin/channel_controller.rb index 84f18d2..118473f 100644 --- a/app/controllers/admin/channel_controller.rb +++ b/app/controllers/admin/channel_controller.rb @@ -15,7 +15,12 @@ def create channel_create end - def update; end + def update + channel_update + end - def destroy; end + def destroy + authorize!(:destroy, :channel_admin) + channel_destroy + end end diff --git a/app/controllers/concerns/channel_concern.rb b/app/controllers/concerns/channel_concern.rb index 5744010..8dbe3c3 100644 --- a/app/controllers/concerns/channel_concern.rb +++ b/app/controllers/concerns/channel_concern.rb @@ -1,7 +1,7 @@ module ChannelConcern extend ActiveSupport::Concern - included do + included do # rubocop:disable Metrics/BlockLength def channel_index @query = Channel.ransack(params[:q]) @pagy, @channels = pagy(@query.result.order(:id)) @@ -17,10 +17,33 @@ def channel_create end end + def channel_update + if @channel.update(channel_params) + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def channel_destroy + assigned_histories = History.where(channel_id: @channel.id).count + + if assigned_histories.zero? + if @channel.destroy + flash[:success] = t('message.admin.channel.delete.success') + else + flash[:error] = t('message.admin.channel.delete.error') + end + else + flash[:error] = t('message.admin.channel.delete.error') + end + redirect_to(admin_channel_index_path) + end + private def set_channel - @channel = Channel.find(channel_params[:id]) + @channel = Channel.find(params[:id]) end def channel_params diff --git a/app/controllers/concerns/device_concern.rb b/app/controllers/concerns/device_concern.rb index 6ec4697..ef7d611 100644 --- a/app/controllers/concerns/device_concern.rb +++ b/app/controllers/concerns/device_concern.rb @@ -44,7 +44,7 @@ def device_update(device) def device_destroy(device, path_success, path_failure) assigned_inspections_count = Inspection.where(device_id: device.id).count - if assigned_inspections_count.zero? && device.control_point_id? + if assigned_inspections_count.zero? && device.control_point.nil? device.destroy redirect_to(path_success) else diff --git a/app/models/channel.rb b/app/models/channel.rb index 76d7121..54477c8 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -5,6 +5,10 @@ class Channel < ApplicationRecord has_many :history belongs_to :control_point + channel_id_msg = 'должен быть в диапозоне от 1 до 48' + + validates :channel_id, presence: true, numericality: { in: 1..48, message: channel_id_msg } + def self.ransackable_associations(_auth_object = nil) ['control_point', 'history', 'server', 'service'] end diff --git a/app/views/admin/channel/index.html.erb b/app/views/admin/channel/index.html.erb index a448ae4..1d93fda 100644 --- a/app/views/admin/channel/index.html.erb +++ b/app/views/admin/channel/index.html.erb @@ -14,8 +14,23 @@
    <%= search_form_for(@query, url: admin_channel_index_path, method: :get, class: "rounded accordion-body") do |f| %>
    - <%= f.label :id, class: "mb-2" %> - <%= f.search_field :id_cont, class:"form-control", placeholder:"ID" %> + <%= f.label :id, class: "mb-1" %> + <%= f.search_field :id_eq, class:"form-control mb-2", placeholder:"ID" %> + + <%= f.label :channel_id, class: "mb-1" %> + <%= f.search_field :channel_id_eq, class:"form-control mb-2", placeholder:"25" %> + + <%= f.label :server_id, class: "mb-1" %> + <%= f.collection_select(:server_id_eq, Server.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select mb-2'}) %> + + <%= f.label :control_point_id, class: "mb-1" %> + <%= f.collection_select(:control_point_id_eq, ControlPoint.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select mb-2'}) %> + + <%= f.label :service_id, class: "mb-1" %> + <%= f.collection_select(:service_id_eq, Service.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select mb-2'}) %>
    <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%>
    diff --git a/app/views/admin/control_point/index.html.erb b/app/views/admin/control_point/index.html.erb index 2ae22b5..463220e 100644 --- a/app/views/admin/control_point/index.html.erb +++ b/app/views/admin/control_point/index.html.erb @@ -17,8 +17,8 @@ <%= f.label :name, class: "mb-2" %> <%= f.search_field :name_cont, class:"form-control", placeholder:"Название" %> <%= f.label :room_id, class:"mb-1" %> - <%= f.collection_select(:room_id_eq, Room.all, - :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select'}) %> + <%= f.collection_select(:room_id_eq, Room.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select form-select'}) %>
    <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%>
    diff --git a/app/views/shared/admin/form/_channel.html.erb b/app/views/shared/admin/form/_channel.html.erb index d5f033e..e496f02 100644 --- a/app/views/shared/admin/form/_channel.html.erb +++ b/app/views/shared/admin/form/_channel.html.erb @@ -83,7 +83,7 @@ <% if current_user.admin? %>
    - <%= f.association :service, input_html: { :tabindex => 10} %> + <%= f.association :service, input_html: { :tabindex => 10 }, include_blank: false %>
    <% end %> diff --git a/app/views/shared/form/_device.html.erb b/app/views/shared/form/_device.html.erb index a593b76..6bf8161 100644 --- a/app/views/shared/form/_device.html.erb +++ b/app/views/shared/form/_device.html.erb @@ -57,14 +57,7 @@ <% else %> <%= f.input :service_id, label: false, input_html: {hidden: true, value: current_user.service_id} %> <% end %> -
    - <%= f.label :control_point %> -
    - <% ControlPoint.where(device_id: device.id).each do |e| %> - <%= content_tag(:span, e.name, class: "btn btn-light btn-sm") %> - <% end %> -
    -
    +
    <%= render 'shared/form/actions', f:f %> diff --git a/test/factories/channels.rb b/test/factories/channels.rb index 6b4c8f5..255b392 100644 --- a/test/factories/channels.rb +++ b/test/factories/channels.rb @@ -8,7 +8,7 @@ self_background { 1.5 } pre_emergency_limit { 1.5 } emergency_limit { 1.5 } - consumptiom { 1.5 } + consumption { 1.5 } conversion_coefficient { 1.5 } event_system_value { 1.5 } event_not_system_value { 1.5 } diff --git a/test/factories/control_points.rb b/test/factories/control_points.rb index 9b4e4c3..e0476f8 100644 --- a/test/factories/control_points.rb +++ b/test/factories/control_points.rb @@ -6,5 +6,6 @@ channel { nil } device { nil } service { association :service } + device_id { association :device } end end From 40833f1fc0f98b758f97ee177906962b4b56e129 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 15 Feb 2024 15:31:17 +0400 Subject: [PATCH 099/132] =?UTF-8?q?UPD:=20=D0=B8=D0=BC=D0=BF=D1=80=D1=83?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D1=83?= =?UTF-8?q?=20=D0=BA=D0=B0=D0=BD=D0=B0=D0=BB=D0=BE=D0=B2=20(=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D1=81=D0=B8=D1=80=D1=83=D0=B5=D1=82=20=D0=BA=D0=BD=D0=BE?= =?UTF-8?q?=D0=BF=D0=BA=D0=B8=20=D1=81=20=D0=B4=D0=B5=D0=B9=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D0=B8=D1=8F=D0=BC=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/admin/channel/index.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/channel/index.html.erb b/app/views/admin/channel/index.html.erb index 1d93fda..6da545c 100644 --- a/app/views/admin/channel/index.html.erb +++ b/app/views/admin/channel/index.html.erb @@ -59,7 +59,7 @@
    - + @@ -82,7 +82,7 @@ - diff --git a/app/views/shared/ui/table/header/_action.erb b/app/views/shared/ui/table/header/_action.erb new file mode 100644 index 0000000..4dc4658 --- /dev/null +++ b/app/views/shared/ui/table/header/_action.erb @@ -0,0 +1,5 @@ + From 8d9836efdfbd5ddcf201bd0dfd38420457604f61 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 16 Feb 2024 12:29:36 +0400 Subject: [PATCH 101/132] =?UTF-8?q?CS:=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=8F=D0=B5=D1=82=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=D0=B0=20rubocop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/concerns/organization_concern.rb | 2 +- app/controllers/device_controller.rb | 2 -- app/models/organization.rb | 4 ++-- test/controllers/admin/building_controller_test.rb | 2 +- test/controllers/admin/channel_controller_test.rb | 2 +- test/controllers/admin/organization_controller_test.rb | 2 +- test/controllers/admin/room_controller_test.rb | 2 +- test/controllers/admin/server_controller_test.rb | 2 +- 8 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/controllers/concerns/organization_concern.rb b/app/controllers/concerns/organization_concern.rb index 68b0b22..6abbc74 100644 --- a/app/controllers/concerns/organization_concern.rb +++ b/app/controllers/concerns/organization_concern.rb @@ -53,7 +53,7 @@ def organization_params :zip_code, :phone, :fax, - :email + :email, ) end end diff --git a/app/controllers/device_controller.rb b/app/controllers/device_controller.rb index 20ecee6..65dda82 100644 --- a/app/controllers/device_controller.rb +++ b/app/controllers/device_controller.rb @@ -4,8 +4,6 @@ class DeviceController < ApplicationController before_action :set_device, only: [:show, :edit, :update, :destroy] load_and_authorize_resource - @seporator = ',' - def index device_index end diff --git a/app/models/organization.rb b/app/models/organization.rb index 304b1c8..3dfcd03 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -5,7 +5,7 @@ class Organization < ApplicationRecord validates :name, presence: true - def self.ransackable_attributes(auth_object = nil) - ["created_at", "email", "fax", "full_address", "id", "name", "phone", "updated_at", "zip_code"] + def self.ransackable_attributes(_auth_object = nil) + ['created_at', 'email', 'fax', 'full_address', 'id', 'name', 'phone', 'updated_at', 'zip_code'] end end diff --git a/test/controllers/admin/building_controller_test.rb b/test/controllers/admin/building_controller_test.rb index ddc78ea..21b56bb 100644 --- a/test/controllers/admin/building_controller_test.rb +++ b/test/controllers/admin/building_controller_test.rb @@ -1,4 +1,4 @@ -require "test_helper" +require 'test_helper' class Admin::BuildingControllerTest < ActionDispatch::IntegrationTest # test "the truth" do diff --git a/test/controllers/admin/channel_controller_test.rb b/test/controllers/admin/channel_controller_test.rb index a495007..3699ee7 100644 --- a/test/controllers/admin/channel_controller_test.rb +++ b/test/controllers/admin/channel_controller_test.rb @@ -1,4 +1,4 @@ -require "test_helper" +require 'test_helper' class Admin::ChannelControllerTest < ActionDispatch::IntegrationTest # test "the truth" do diff --git a/test/controllers/admin/organization_controller_test.rb b/test/controllers/admin/organization_controller_test.rb index 8dd03d8..4d1c564 100644 --- a/test/controllers/admin/organization_controller_test.rb +++ b/test/controllers/admin/organization_controller_test.rb @@ -1,4 +1,4 @@ -require "test_helper" +require 'test_helper' class Admin::OrganizationControllerTest < ActionDispatch::IntegrationTest # test "the truth" do diff --git a/test/controllers/admin/room_controller_test.rb b/test/controllers/admin/room_controller_test.rb index b49c04b..4ef8898 100644 --- a/test/controllers/admin/room_controller_test.rb +++ b/test/controllers/admin/room_controller_test.rb @@ -1,4 +1,4 @@ -require "test_helper" +require 'test_helper' class Admin::RoomControllerTest < ActionDispatch::IntegrationTest # test "the truth" do diff --git a/test/controllers/admin/server_controller_test.rb b/test/controllers/admin/server_controller_test.rb index c6fe0ca..6c276b7 100644 --- a/test/controllers/admin/server_controller_test.rb +++ b/test/controllers/admin/server_controller_test.rb @@ -1,4 +1,4 @@ -require "test_helper" +require 'test_helper' class Admin::ServerControllerTest < ActionDispatch::IntegrationTest # test "the truth" do From 9be41dd997f0da31a6da05dfdaae0e4f2e03911e Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 16 Feb 2024 15:08:55 +0400 Subject: [PATCH 102/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20partial=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D0=B8=20=D1=82=D0=B0=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D1=86=D1=8B=20=D1=81=20=D0=BA=D0=BE=D0=BB=D0=B8?= =?UTF-8?q?=D1=87=D0=B5=D1=81=D1=82=D0=B2=D0=BE=D0=BC=20=D0=BE=D0=B1=D1=8A?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/shared/ui/table/caption/_all_items.erb | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 app/views/shared/ui/table/caption/_all_items.erb diff --git a/app/views/shared/ui/table/caption/_all_items.erb b/app/views/shared/ui/table/caption/_all_items.erb new file mode 100644 index 0000000..f79e974 --- /dev/null +++ b/app/views/shared/ui/table/caption/_all_items.erb @@ -0,0 +1,3 @@ + From 52ff532db1be9ddc575cf986ac2e94d7cca02d71 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 16 Feb 2024 15:09:36 +0400 Subject: [PATCH 103/132] =?UTF-8?q?UPD:=20=D0=B8=D0=BC=D0=BF=D1=80=D1=83?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=20=D1=87=D0=B0=D1=81=D1=82=D1=8C=20=D0=B2?= =?UTF-8?q?=D1=8C=D1=8E=D1=88=D0=B5=D0=BA,=20=D0=B7=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=8F=D0=B5=D1=82=20=D1=87=D0=B0=D1=81=D1=82=D1=8C=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=B4=D0=B0=20=D0=BD=D0=B0=20=D0=BF=D0=B0=D1=80?= =?UTF-8?q?=D1=88=D0=B8=D0=B0=D0=BB=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/admin/building/index.html.erb | 79 +++++---- app/views/admin/channel/index.html.erb | 100 ++++++------ app/views/admin/control_point/index.html.erb | 90 +++++------ .../admin/device_component/index.html.erb | 82 +++++----- app/views/admin/device_model/index.html.erb | 6 +- .../admin/device_reg_group/index.html.erb | 48 +++--- app/views/admin/division/index.html.erb | 65 ++++---- app/views/admin/manufacturer/index.html.erb | 65 ++++---- .../admin/measurement_class/index.html.erb | 57 ++++--- .../admin/measurement_group/index.html.erb | 47 +++--- app/views/admin/room/index.html.erb | 17 +- app/views/shared/index/_device.html.erb | 152 +++++++++--------- config/locales/en.yml | 5 + config/locales/ru.yml | 4 + 14 files changed, 402 insertions(+), 415 deletions(-) diff --git a/app/views/admin/building/index.html.erb b/app/views/admin/building/index.html.erb index ee80518..5553e11 100644 --- a/app/views/admin/building/index.html.erb +++ b/app/views/admin/building/index.html.erb @@ -30,47 +30,46 @@ <%= render 'shared/modal_button_add', path: new_admin_building_path, classes: "btn btn-primary w-100", text: t("b_add") %> -
    -
    -
    <%=t('activerecord.attributes.device.tabel_id')%> <%=t('activerecord.attributes.device.serial_id')%> <%=t('activerecord.attributes.device.device_model')%><%=t('activerecord.attributes.device.service')%><%=t('activerecord.attributes.device.date_of_inspection')%>
    <%= device.tabel_id %> <%= device.serial_id %> <%= device.device_model.name %> <%= device.year_of_production %> <%= device.year_of_commissioning %><%= device.control_point.room.name %><%= device.control_point.name %><%= "——" %> <%= "——" %> + + <% device.control_point.each do |e| %> + <%= content_tag(:span, e.room.name, class: "btn btn-light btn-sm") %> + <% end %> + + + <% device.control_point.each do |e| %> + <%= content_tag(:span, e.name, class: "btn btn-light btn-sm") %> + <% end %> + <%= device.service.name %>
    <%= t('activerecord.attributes.channel.channel_id')%> <%= t('activerecord.attributes.channel.server')%> <%= t('activerecord.attributes.channel.control_point.name')%><%= t('activerecord.attributes.channel.location_description')%> <%= t('activerecord.attributes.channel.self_background')%> <%= t('activerecord.attributes.channel.pre_emergency_limit')%>
    <%= channel.id %><%= channel.channel_id%><%= channel.server_id%><%= channel.channel_id %><%= channel.server.name %><%= "——" %><%= t('activerecord.attributes.channel.conversion_coefficient')%> <%= t('activerecord.attributes.channel.service')%><%= t('action') %><%= t('action') %>
    <%= channel.service.name %> +
    <%= link_to edit_admin_channel_path(channel.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> From 5d604aa80e51dbe877b946e45014c9a018d0a38b Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Fri, 16 Feb 2024 12:26:31 +0400 Subject: [PATCH 100/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20partial=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BA=D1=80=D0=B5=D0=BF=D0=BB=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=82=D0=B5=D0=BD=D1=82=D0=B0=20=D1=8F=D1=87=D0=B5=D0=B9=D0=BA?= =?UTF-8?q?=D0=B8=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/shared/ui/table/cell/_action.erb | 10 ++++++++++ app/views/shared/ui/table/header/_action.erb | 5 +++++ 2 files changed, 15 insertions(+) create mode 100644 app/views/shared/ui/table/cell/_action.erb create mode 100644 app/views/shared/ui/table/header/_action.erb diff --git a/app/views/shared/ui/table/cell/_action.erb b/app/views/shared/ui/table/cell/_action.erb new file mode 100644 index 0000000..4630562 --- /dev/null +++ b/app/views/shared/ui/table/cell/_action.erb @@ -0,0 +1,10 @@ +
    +
    + <%= link_to edit_path, class: "btn btn-sm", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to destroy_path, method: :delete, class: "btn btn-sm" do %> + + <% end %> +
    +
    + <%= t('action')%> +
    + <%= "#{t('.')} #{count}" %> +
    - - - - - - - - - - - <% @buildings.each do |building| %> - - - - <% if building.organization.nil? %> - - <% else %> - - <% end %> - <% if building.description.nil? %> - - <% else %> - - <% end %> - +
    +
    +
    +
    <%= t('activerecord.attributes.building.id') %><%= t('activerecord.attributes.building.name') %><%= t('activerecord.attributes.building.organization') %><%= t('activerecord.attributes.building.description') %><%= t('action') %>
    <%= building.id %><%= building.name %><%= "——" %><%= building.organization.name %><%= "——" %><%= building.description %> -
    - <%= link_to edit_admin_building_path(building.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_building_path(building.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + <%= render 'shared/ui/table/caption/all_items', + count: Building.count %> + + + + + + + + <%= render 'shared/ui/table/header/action'%> - <% end %> - -
    <%= t('activerecord.attributes.building.id') %><%= t('activerecord.attributes.building.name') %><%= t('activerecord.attributes.building.organization') %><%= t('activerecord.attributes.building.description') %>
    + +
    <%= building.id %><%= building.name %><%= "——" %><%= building.organization.name %><%= "——" %><%= building.description %>
    +
    diff --git a/app/views/admin/channel/index.html.erb b/app/views/admin/channel/index.html.erb index 6da545c..3de3133 100644 --- a/app/views/admin/channel/index.html.erb +++ b/app/views/admin/channel/index.html.erb @@ -42,60 +42,56 @@
    <%= render 'shared/modal_button_add', path: new_admin_channel_path, classes: "btn btn-primary w-100", text: t("b_add") %>
    -
    -
    - - - - - - - - - - - - - - - - - - - - <% @channels.each do |channel| %> - - - - - <% if channel.control_point.nil? %> - - <% else %> - - - - - - - +
    +
    +
    +
    <%= t('activerecord.attributes.channel.id') %><%= t('activerecord.attributes.channel.channel_id')%><%= t('activerecord.attributes.channel.server')%><%= t('activerecord.attributes.channel.control_point.name')%><%= t('activerecord.attributes.channel.location_description')%><%= t('activerecord.attributes.channel.self_background')%><%= t('activerecord.attributes.channel.pre_emergency_limit')%><%= t('activerecord.attributes.channel.emergency_limit')%><%= t('activerecord.attributes.channel.consumption')%><%= t('activerecord.attributes.channel.conversion_coefficient')%><%= t('activerecord.attributes.channel.service')%><%= t('action') %>
    <%= channel.id %><%= channel.channel_id %><%= channel.server.name %><%= "——" %><%= channel.control_point.name %> - <% end %> - <%= channel.location_description %><%= channel.self_background %><%= channel.pre_emergency_limit %><%= channel.emergency_limit %><%= channel.consumption %><%= channel.conversion_coefficient %><%= channel.service.name %>
    + <%= render 'shared/ui/table/caption/all_items', + count: Channel.count %> + + + + + + + + + + + + + + <%= render 'shared/ui/table/header/action'%> + + + + <% @channels.each do |channel| %> + + + + + <% if channel.control_point.nil? %> + + <% else %> + + + + + + + - - - <% end %> - -
    <%= t('activerecord.attributes.channel.id') %><%= t('activerecord.attributes.channel.channel_id')%><%= t('activerecord.attributes.channel.server')%><%= t('activerecord.attributes.channel.control_point.name')%><%= t('activerecord.attributes.channel.location_description')%><%= t('activerecord.attributes.channel.self_background')%><%= t('activerecord.attributes.channel.pre_emergency_limit')%><%= t('activerecord.attributes.channel.emergency_limit')%><%= t('activerecord.attributes.channel.consumption')%><%= t('activerecord.attributes.channel.conversion_coefficient')%><%= t('activerecord.attributes.channel.service')%>
    <%= channel.id %><%= channel.channel_id %><%= channel.server.name %><%= "——" %><%= channel.control_point.name %> + <% end %> + <%= channel.location_description %><%= channel.self_background %><%= channel.pre_emergency_limit %><%= channel.emergency_limit %><%= channel.consumption %><%= channel.conversion_coefficient %><%= channel.service.name %> -
    - <%= link_to edit_admin_channel_path(channel.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_channel_path(channel.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_channel_path(channel.id), + destroy_path: admin_channel_path(channel.id) %> + + <% end %> + + +
    diff --git a/app/views/admin/control_point/index.html.erb b/app/views/admin/control_point/index.html.erb index 463220e..508fe67 100644 --- a/app/views/admin/control_point/index.html.erb +++ b/app/views/admin/control_point/index.html.erb @@ -30,53 +30,51 @@
    <%= render 'shared/modal_button_add', path: new_admin_control_point_path, classes: "btn btn-primary w-100", text: t("b_add") %>
    -
    -
    - - - - - - - - - - - - <% @control_points.each do |control_point| %> - - - <% if control_point.room.nil? %> - - <% else %> - - <% end %> - <% if control_point.device.nil? %> - - <% else %> - - <% end %> - <% if control_point.description.nil? %> - - <% else %> - - <% end %> - +
    +
    +
    +
    <%= t('activerecord.attributes.control_point.name') %><%= t('activerecord.attributes.control_point.room') %><%= t('activerecord.attributes.control_point.device') %><%= t('activerecord.attributes.control_point.description') %><%= t('action') %>
    <%= control_point.name %><%= "——" %><%= control_point.room.name %><%= "——" %> - <%= button_to "(#{sprintf("%05d", control_point.device.tabel_id)}) #{control_point.device.device_model.name}", admin_device_path(control_point.device.id), method: :get, class: "btn btn-light btn-sm" %> - <%= "——" %><%= control_point.description %> -
    - <%= link_to edit_admin_control_point_path(control_point.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_control_point_path(control_point.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + <%= render 'shared/ui/table/caption/all_items', + count: ControlPoint.count %> + + + + + + + <%= render 'shared/ui/table/header/action'%> - <% end %> - -
    <%= t('activerecord.attributes.control_point.name') %><%= t('activerecord.attributes.control_point.room') %><%= t('activerecord.attributes.control_point.device') %><%= t('activerecord.attributes.control_point.description') %>
    + + + <% @control_points.each do |control_point| %> + + <%= control_point.name %> + <% if control_point.room.nil? %> + <%= "——" %> + <% else %> + <%= control_point.room.name %> + <% end %> + <% if control_point.device.nil? %> + <%= "——" %> + <% else %> + + <%= button_to "(#{sprintf("%05d", control_point.device.tabel_id)}) #{control_point.device.device_model.name}", admin_device_path(control_point.device.id), method: :get, class: "btn btn-light btn-sm" %> + + <% end %> + <% if control_point.description.nil? %> + <%= "——" %> + <% else %> + <%= control_point.description %> + <% end %> + + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_control_point_path(control_point.id), + destroy_path: admin_control_point_path(control_point.id) %> + + <% end %> + + +
    diff --git a/app/views/admin/device_component/index.html.erb b/app/views/admin/device_component/index.html.erb index 6494867..3e01fe1 100644 --- a/app/views/admin/device_component/index.html.erb +++ b/app/views/admin/device_component/index.html.erb @@ -36,49 +36,47 @@ <%= render 'shared/modal_button_add', path: new_admin_device_component_path, classes: "btn btn-primary w-100 mb-3", text: t("b_add") %> -
    -
    - - - - - - - - - - - - - - - <% @device_components.each do |device_component| %> - - - - <% if device_component.supplementary_kit.nil? %> - - <% else %> - - <% end %> - - - - - +
    +
    +
    +
    <%= t('activerecord.attributes.device_component.serial_id')%><%= t('activerecord.attributes.device_component.name')%><%= t('activerecord.attributes.device_component.supplementary_kit')%><%= t('activerecord.attributes.device_component.measurement_min')%><%= t('activerecord.attributes.device_component.measurement_max')%><%= t('activerecord.attributes.device_component.measuring_unit')%><%= t('activerecord.attributes.device_component.description')%><%= t('action')%>
    <%= device_component.serial_id %><%= device_component.name %><%= device_component.supplementary_kit.name %><%= device_component.measurement_min %><%= device_component.measurement_max %><%= device_component.measuring_unit %><%= device_component.description %> -
    - <%= link_to edit_admin_device_component_path(device_component.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_device_component_path(device_component.id), method: :delete, form: { data: { turbo_confirm: t('delete_confirm') } }, class: "btn" do %> - - <% end %> -
    -
    + <%= render 'shared/ui/table/caption/all_items', + count: DeviceComponent.count %> + + + + + + + + + + <%= render 'shared/ui/table/header/action'%> - <% end %> - -
    <%= t('activerecord.attributes.device_component.serial_id')%><%= t('activerecord.attributes.device_component.name')%><%= t('activerecord.attributes.device_component.supplementary_kit')%><%= t('activerecord.attributes.device_component.measurement_min')%><%= t('activerecord.attributes.device_component.measurement_max')%><%= t('activerecord.attributes.device_component.measuring_unit')%><%= t('activerecord.attributes.device_component.description')%>
    + + + <% @device_components.each do |device_component| %> + + <%= device_component.serial_id %> + <%= device_component.name %> + <% if device_component.supplementary_kit.nil? %> + + <% else %> + <%= device_component.supplementary_kit.name %> + <% end %> + <%= device_component.measurement_min %> + <%= device_component.measurement_max %> + <%= device_component.measuring_unit %> + <%= device_component.description %> + + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_device_component_path(device_component.id), + destroy_path: admin_device_component_path(device_component.id) %> + + <% end %> + + +
    diff --git a/app/views/admin/device_model/index.html.erb b/app/views/admin/device_model/index.html.erb index 1dbb858..64fe22b 100644 --- a/app/views/admin/device_model/index.html.erb +++ b/app/views/admin/device_model/index.html.erb @@ -55,9 +55,12 @@ <%= render 'shared/modal_button_add', path: new_admin_device_model_path, classes: "btn btn-primary w-100 mb-3", text: t("b_add") %> -
    +
    +
    + <%= render 'shared/ui/table/caption/all_items', + count: DeviceModel.count %> @@ -94,6 +97,7 @@
    <%= t('activerecord.attributes.device_model.name')%>
    +
    diff --git a/app/views/admin/device_reg_group/index.html.erb b/app/views/admin/device_reg_group/index.html.erb index b713ec5..3925556 100644 --- a/app/views/admin/device_reg_group/index.html.erb +++ b/app/views/admin/device_reg_group/index.html.erb @@ -29,33 +29,31 @@ <%= render 'shared/modal_button_add', path: new_admin_device_reg_group_path, classes: "btn btn-primary w-100", text: t("b_add") %>
    -
    -
    - - - - - - - - - <% @device_reg_groups.each do |device_reg_group| %> +
    +
    +
    +
    <%= t('activerecord.attributes.device_reg_group.name') %><%= t('action')%>
    + <%= render 'shared/ui/table/caption/all_items', + count: @device_reg_groups.count %> + - - + + + <%= render 'shared/ui/table/header/action' %> - <% end %> - -
    <%= device_reg_group.name %> -
    - <%= link_to edit_admin_device_reg_group_path(device_reg_group.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_device_reg_group_path(device_reg_group.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    <%= t('activerecord.attributes.device_reg_group.name') %>
    + + + <% @device_reg_groups.each do |device_reg_group| %> + + <%= device_reg_group.name %> + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_device_reg_group_path(device_reg_group.id), + destroy_path: admin_device_reg_group_index_path(device_reg_group.id) %> + + <% end %> + + +
    diff --git a/app/views/admin/division/index.html.erb b/app/views/admin/division/index.html.erb index 503b954..51f2182 100644 --- a/app/views/admin/division/index.html.erb +++ b/app/views/admin/division/index.html.erb @@ -30,41 +30,38 @@ <%= render 'shared/modal_button_add', path: new_admin_division_path, classes: "btn btn-primary w-100", text: t("b_add") %> -
    -
    - - - - - - - - - - - <% @divisions.each do |division| %> - - - - <% if division.organization.nil? %> - - <% else %> - - <% end %> - +
    +
    +
    +
    <%= t('activerecord.attributes.division.id') %><%= t('activerecord.attributes.division.name') %><%= t('activerecord.attributes.division.organization') %><%= t('action') %>
    <%= division.id %><%= division.name %><%= "——" %><%= division.organization.name %> -
    - <%= link_to edit_admin_division_path(division.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_division_path(division.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + <%= render 'shared/ui/table/caption/all_items', + count: Division.count %> + + + + + + <%= render 'shared/ui/table/header/action' %> - <% end %> - -
    <%= t('activerecord.attributes.division.id') %><%= t('activerecord.attributes.division.name') %><%= t('activerecord.attributes.division.organization') %>
    + + + <% @divisions.each do |division| %> + + <%= division.id %> + <%= division.name %> + <% if division.organization.nil? %> + <%= "——" %> + <% else %> + <%= division.organization.name %> + <% end %> + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_division_path(division.id), + destroy_path: admin_division_path(division.id) %> + + <% end %> + + +
    diff --git a/app/views/admin/manufacturer/index.html.erb b/app/views/admin/manufacturer/index.html.erb index fc946f3..49e39d5 100644 --- a/app/views/admin/manufacturer/index.html.erb +++ b/app/views/admin/manufacturer/index.html.erb @@ -35,41 +35,38 @@ <%= render 'shared/modal_button_add', path: new_admin_manufacturer_path, classes: "btn btn-primary w-100 mb-3", text: t("b_add") %> -
    -
    - - - - - - - - - - - - - <% @manufacturers.each do |manufacturer| %> - - - - - - - +
    +
    +
    +
    <%= t('activerecord.attributes.manufacturer.name') %><%= t('activerecord.attributes.manufacturer.adress') %><%= t('activerecord.attributes.manufacturer.phone') %><%= t('activerecord.attributes.manufacturer.email') %><%= t('activerecord.attributes.manufacturer.site_url') %><%= t('action') %>
    <%= manufacturer.name %><%= manufacturer.adress %><%= manufacturer.phone %><%= manufacturer.email %><%= manufacturer.site_url %> -
    - <%= link_to edit_admin_manufacturer_path(manufacturer.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_manufacturer_path(manufacturer.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + + + + + + + + + <%= render 'shared/ui/table/header/action' %> - <% end %> - -
    <%= t('activerecord.attributes.manufacturer.name') %><%= t('activerecord.attributes.manufacturer.adress') %><%= t('activerecord.attributes.manufacturer.phone') %><%= t('activerecord.attributes.manufacturer.email') %><%= t('activerecord.attributes.manufacturer.site_url') %>
    + + + <% @manufacturers.each do |manufacturer| %> + + <%= manufacturer.name %> + <%= manufacturer.adress %> + <%= manufacturer.phone %> + <%= manufacturer.email %> + <%= manufacturer.site_url %> + + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_manufacturer_path(manufacturer.id), + destroy_path: admin_manufacturer_path(manufacturer.id) %> + + <% end %> + + +
    diff --git a/app/views/admin/measurement_class/index.html.erb b/app/views/admin/measurement_class/index.html.erb index 6364567..bfa33ca 100644 --- a/app/views/admin/measurement_class/index.html.erb +++ b/app/views/admin/measurement_class/index.html.erb @@ -32,37 +32,34 @@ <%= render 'shared/modal_button_add', path: new_admin_measurement_class_path, classes: "btn btn-primary w-100", text: t("b_add") %> -
    -
    - - - - - - - - - - - <% @measurement_classes.each do |measurement_class| %> - - - - - + +
    +
    +
    +
    <%= t('activerecord.attributes.measurement_class.name') %><%= t('activerecord.attributes.measurement_class.arms_device_type') %><%= t('activerecord.attributes.measurement_class.measurement_group') %><%= t('action') %>
    <%= measurement_class.name %><%= measurement_class.arms_device_type %><%= measurement_class.measurement_group.name %> -
    - <%= link_to edit_admin_measurement_class_path(measurement_class.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_measurement_class_path(measurement_class.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + + + + + + + <%= render 'shared/ui/table/header/action' %> - <% end %> - -
    <%= t('activerecord.attributes.measurement_class.name') %><%= t('activerecord.attributes.measurement_class.arms_device_type') %><%= t('activerecord.attributes.measurement_class.measurement_group') %>
    + + + <% @measurement_classes.each do |measurement_class| %> + + <%= measurement_class.name %> + <%= measurement_class.arms_device_type %> + <%= measurement_class.measurement_group.name %> + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_measurement_class_path(measurement_class.id), + destroy_path: admin_measurement_class_path(measurement_class.id) %> + + <% end %> + + +
    diff --git a/app/views/admin/measurement_group/index.html.erb b/app/views/admin/measurement_group/index.html.erb index 7869bed..75b6b5e 100644 --- a/app/views/admin/measurement_group/index.html.erb +++ b/app/views/admin/measurement_group/index.html.erb @@ -27,33 +27,28 @@ <%= render 'shared/modal_button_add', path: new_admin_measurement_group_path, classes: "btn btn-primary w-100", text: t("b_add") %> -
    -
    - - - - - - - - - <% @measurement_groupes.each do |measurement_group| %> - - - +
    +
    +
    +
    <%= t('activerecord.attributes.measurement_group.name') %><%= t('action') %>
    <%= measurement_group.name %> -
    - <%= link_to edit_admin_measurement_group_path(measurement_group.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_measurement_group_path(measurement_group.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + + + + <%= render 'shared/ui/table/header/action' %> - <% end %> - -
    <%= t('activerecord.attributes.measurement_group.name') %>
    + + + <% @measurement_groupes.each do |measurement_group| %> + + <%= measurement_group.name %> + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_measurement_group_path(measurement_group.id), + destroy_path: admin_measurement_group_path(measurement_group.id) %> + + <% end %> + + +
    diff --git a/app/views/admin/room/index.html.erb b/app/views/admin/room/index.html.erb index 351440e..de7c39d 100644 --- a/app/views/admin/room/index.html.erb +++ b/app/views/admin/room/index.html.erb @@ -42,7 +42,8 @@ <%= t('activerecord.attributes.room.building') %> <%= t('activerecord.attributes.room.level') %> <%= t('activerecord.attributes.room.description') %> - <%= t('action') %> + + <%= render 'shared/ui/table/header/action'%> @@ -61,16 +62,10 @@ <% else %> <%= room.description %> <% end %> - -
    - <%= link_to edit_admin_room_path(room.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_room_path(room.id), method: :delete, class: "btn" do %> - - <% end %> -
    - + + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_room_path(room.id), + destroy_path: admin_room_path(room.id) %> <% end %> diff --git a/app/views/shared/index/_device.html.erb b/app/views/shared/index/_device.html.erb index 80aa2a9..d73cc3d 100644 --- a/app/views/shared/index/_device.html.erb +++ b/app/views/shared/index/_device.html.erb @@ -73,83 +73,87 @@ <%=t('b_download_pdf')%> <% end %> -
    -
    - - - - - - - - - - - - <% if current_user.admin? %> - - <% end %> - - - - - <% @devices.each do |device| %> - <% path = "#{show_path}#{device.id}" %> - - <% if device.status == Device::STATUS[:in_storage] %> - - <% elsif device.status == Device::STATUS[:expired] %> - - <% elsif device.status == Device::STATUS[:on_repair] %> - - <% elsif device.status == Device::STATUS[:in_stock] %> - - <% elsif device.status == Device::STATUS[:sended_to_inspection] %> - - <% end %> - - - - - - - <% if device.control_point.nil? %> - - - <% else %> - - - <% end %> +
    +
    +
    +
    <%=t('activerecord.attributes.device.tabel_id')%><%=t('activerecord.attributes.device.serial_id')%><%=t('activerecord.attributes.device.device_model')%><%=t('activerecord.attributes.device.year_of_production')%><%=t('activerecord.attributes.device.year_of_commissioning')%><%=t('activerecord.attributes.device.room')%><%=t('activerecord.attributes.device.control_point')%><%=t('activerecord.attributes.device.service')%><%=t('activerecord.attributes.device.date_of_inspection')%>
    <%= device.tabel_id %><%= device.serial_id %><%= device.device_model.name %><%= device.year_of_production %><%= device.year_of_commissioning %><%= "——" %><%= "——" %> - - <% device.control_point.each do |e| %> - <%= content_tag(:span, e.room.name, class: "btn btn-light btn-sm") %> - <% end %> - - - <% device.control_point.each do |e| %> - <%= content_tag(:span, e.name, class: "btn btn-light btn-sm") %> - <% end %> -
    + <%= render 'shared/ui/table/caption/all_items', + count: Device.count %> + + + + + + + + + + <% if current_user.admin? %> - - <% end %> - <% if device.inspection_expiration_status == Device::INSPECTION_EXPIRATION_STATUS[:verified] %> - - <% elsif device.inspection_expiration_status == Device::INSPECTION_EXPIRATION_STATUS[:prepare_to_inspection] %> - - <% else %> - + <% end %> + - <% end %> - -
    <%=t('activerecord.attributes.device.tabel_id')%><%=t('activerecord.attributes.device.serial_id')%><%=t('activerecord.attributes.device.device_model')%><%=t('activerecord.attributes.device.year_of_production')%><%=t('activerecord.attributes.device.year_of_commissioning')%><%=t('activerecord.attributes.device.room')%><%=t('activerecord.attributes.device.control_point')%><%= device.service.name %> - <%= device.last_successful_inspection %> - - <%= device.last_successful_inspection %> - - <%= device.last_successful_inspection %> - <%=t('activerecord.attributes.device.service')%><%=t('activerecord.attributes.device.date_of_inspection')%>
    + + + <% @devices.each do |device| %> + <% path = "#{show_path}#{device.id}" %> + + <% if device.status == Device::STATUS[:in_storage] %> + + <% elsif device.status == Device::STATUS[:expired] %> + + <% elsif device.status == Device::STATUS[:on_repair] %> + + <% elsif device.status == Device::STATUS[:in_stock] %> + + <% elsif device.status == Device::STATUS[:sended_to_inspection] %> + + <% end %> + + <%= device.tabel_id %> + <%= device.serial_id %> + <%= device.device_model.name %> + <%= device.year_of_production %> + <%= device.year_of_commissioning %> + <% if device.control_point.nil? %> + <%= "——" %> + <%= "——" %> + <% else %> + + + <% device.control_point.each do |e| %> + <%= content_tag(:span, e.room.name, class: "btn btn-light btn-sm") %> + <% end %> + + + + <% device.control_point.each do |e| %> + <%= content_tag(:span, e.name, class: "btn btn-light btn-sm") %> + <% end %> + + <% end %> + <% if current_user.admin? %> + <%= device.service.name %> + <% end %> + <% if device.inspection_expiration_status == Device::INSPECTION_EXPIRATION_STATUS[:verified] %> + + <%= device.last_successful_inspection %> + + <% elsif device.inspection_expiration_status == Device::INSPECTION_EXPIRATION_STATUS[:prepare_to_inspection] %> + + <%= device.last_successful_inspection %> + + <% else %> + + <%= device.last_successful_inspection %> + + <% end %> + + <% end %> + + +
    diff --git a/config/locales/en.yml b/config/locales/en.yml index 9a4abfb..b449d95 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -81,6 +81,11 @@ en: body: Eat some more of these... category: Public or URB-106 shared: + shared: + ui: + table: + caption: + all_items: "All: " show: device: send_to_inspection: Send to inspection diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 10afaa9..ff698fd 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -371,6 +371,10 @@ ru: title: Страница редактирования %{resource} we_need_your_current_password_to_confirm_your_changes: введите текущий пароль для подтверждения изменений shared: + ui: + table: + caption: + all_items: "Всего: " admin: form: channel: From cb3e1f450d881a17bfd15e91ff411bca712bd21b Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 19 Feb 2024 10:31:34 +0400 Subject: [PATCH 104/132] =?UTF-8?q?UPD:=20=D0=B8=D0=BC=D0=BF=D1=80=D1=83?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=20=D1=87=D0=B0=D1=81=D1=82=D1=8C=20=D0=B2?= =?UTF-8?q?=D1=8C=D1=8E=D1=88=D0=B5=D0=BA,=20=D0=B7=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=8F=D0=B5=D1=82=20=D1=87=D0=B0=D1=81=D1=82=D1=8C=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=B4=D0=B0=20=D0=BD=D0=B0=20=D0=BF=D0=B0=D1=80?= =?UTF-8?q?=D1=88=D0=B8=D0=B0=D0=BB=D1=8B=20(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/admin/manufacturer/index.html.erb | 2 + .../admin/measurement_class/index.html.erb | 2 + .../admin/measurement_group/index.html.erb | 2 + app/views/admin/organization/index.html.erb | 120 +++++++------- app/views/admin/room/index.html.erb | 76 ++++----- app/views/admin/service/index.html.erb | 91 ++++++----- .../admin/supplementary_kit/index.html.erb | 58 +++---- app/views/admin/users/index.html.erb | 96 ++++++------ app/views/shared/index/_inspection.html.erb | 148 ++++++++---------- 9 files changed, 297 insertions(+), 298 deletions(-) diff --git a/app/views/admin/manufacturer/index.html.erb b/app/views/admin/manufacturer/index.html.erb index 49e39d5..2bd9711 100644 --- a/app/views/admin/manufacturer/index.html.erb +++ b/app/views/admin/manufacturer/index.html.erb @@ -39,6 +39,8 @@
    + <%= render 'shared/ui/table/caption/all_items', + count: Manufacturer.count %> diff --git a/app/views/admin/measurement_class/index.html.erb b/app/views/admin/measurement_class/index.html.erb index bfa33ca..538a202 100644 --- a/app/views/admin/measurement_class/index.html.erb +++ b/app/views/admin/measurement_class/index.html.erb @@ -37,6 +37,8 @@
    <%= t('activerecord.attributes.manufacturer.name') %>
    + <%= render 'shared/ui/table/caption/all_items', + count: MeasurementClass.count %> diff --git a/app/views/admin/measurement_group/index.html.erb b/app/views/admin/measurement_group/index.html.erb index 75b6b5e..3c99a79 100644 --- a/app/views/admin/measurement_group/index.html.erb +++ b/app/views/admin/measurement_group/index.html.erb @@ -31,6 +31,8 @@
    <%= t('activerecord.attributes.measurement_class.name') %>
    + <%= render 'shared/ui/table/caption/all_items', + count: MeasurementGroup.count %> diff --git a/app/views/admin/organization/index.html.erb b/app/views/admin/organization/index.html.erb index 45d93c2..07ae7fb 100644 --- a/app/views/admin/organization/index.html.erb +++ b/app/views/admin/organization/index.html.erb @@ -37,65 +37,69 @@ <%= render 'shared/modal_button_add', path: new_admin_organization_path, classes: "btn btn-primary w-100", text: t("b_add") %> -
    -
    -
    <%= t('activerecord.attributes.measurement_group.name') %>
    - - - - - - - - - - - - - - <% @organizations.each do |organization| %> - - - - <% if organization.full_address.nil? %> - - <% else %> - - <% end %> - <% if organization.zip_code.nil? %> - - <% else %> - - <% end %> - <% if organization.phone.nil? %> - - <% else %> - - <% end %> - <% if organization.fax.nil? %> - - <% else %> - - <% end %> - <% if organization.email.nil? %> - - <% else %> - - <% end %> - +
    +
    +
    +
    <%= t('activerecord.attributes.organization.id') %><%= t('activerecord.attributes.organization.name') %><%= t('activerecord.attributes.organization.full_address') %><%= t('activerecord.attributes.organization.zip_code') %><%= t('activerecord.attributes.organization.phone') %><%= t('activerecord.attributes.organization.fax') %><%= t('activerecord.attributes.organization.email') %><%= t('action') %>
    <%= organization.id %><%= organization.name %><%= "——" %><%= organization.full_address %><%= "——" %><%= organization.zip_code %><%= "——" %><%= organization.phone %><%= "——" %><%= organization.fax %><%= "——" %><%= organization.email %> -
    - <%= link_to edit_admin_organization_path(organization.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_organization_path(organization.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + <%= render 'shared/ui/table/caption/all_items', + count: Organization.count %> + + + + + + + + + + - <% end %> - -
    <%= t('activerecord.attributes.organization.id') %><%= t('activerecord.attributes.organization.name') %><%= t('activerecord.attributes.organization.full_address') %><%= t('activerecord.attributes.organization.zip_code') %><%= t('activerecord.attributes.organization.phone') %><%= t('activerecord.attributes.organization.fax') %><%= t('activerecord.attributes.organization.email') %><%= t('action') %>
    + + + <% @organizations.each do |organization| %> + + <%= organization.id %> + <%= organization.name %> + <% if organization.full_address.nil? %> + <%= "——" %> + <% else %> + <%= organization.full_address %> + <% end %> + <% if organization.zip_code.nil? %> + <%= "——" %> + <% else %> + <%= organization.zip_code %> + <% end %> + <% if organization.phone.nil? %> + <%= "——" %> + <% else %> + <%= organization.phone %> + <% end %> + <% if organization.fax.nil? %> + <%= "——" %> + <% else %> + <%= organization.fax %> + <% end %> + <% if organization.email.nil? %> + <%= "——" %> + <% else %> + <%= organization.email %> + <% end %> + +
    + <%= link_to edit_admin_organization_path(organization.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> + + <% end %> + <%= button_to admin_organization_path(organization.id), method: :delete, class: "btn" do %> + + <% end %> +
    + + + <% end %> + + +
    diff --git a/app/views/admin/room/index.html.erb b/app/views/admin/room/index.html.erb index de7c39d..797bcb9 100644 --- a/app/views/admin/room/index.html.erb +++ b/app/views/admin/room/index.html.erb @@ -32,44 +32,46 @@ <%= render 'shared/modal_button_add', path: new_admin_room_path, classes: "btn btn-primary w-100", text: t("b_add") %> -
    -
    - - - - - - - - - - <%= render 'shared/ui/table/header/action'%> - - - - <% @rooms.each do |room| %> - - - - - <% if room.level.nil? %> - - <% else %> - - <% end %> - <% if room.description.nil? %> - - <% else %> - - <% end %> - - <%= render 'shared/ui/table/cell/action', - edit_path: edit_admin_room_path(room.id), - destroy_path: admin_room_path(room.id) %> +
    +
    +
    +
    <%= t('activerecord.attributes.room.id') %><%= t('activerecord.attributes.room.name') %><%= t('activerecord.attributes.room.building') %><%= t('activerecord.attributes.room.level') %><%= t('activerecord.attributes.room.description') %>
    <%= room.id %><%= room.name %><%= room.building.name %><%= "——" %><%= room.level %><%= "——" %><%= room.description %>
    + <%= render 'shared/ui/table/caption/all_items', + count: Room.count %> + + + + + + + + <%= render 'shared/ui/table/header/action'%> - <% end %> - -
    <%= t('activerecord.attributes.room.id') %><%= t('activerecord.attributes.room.name') %><%= t('activerecord.attributes.room.building') %><%= t('activerecord.attributes.room.level') %><%= t('activerecord.attributes.room.description') %>
    + + + <% @rooms.each do |room| %> + + <%= room.id %> + <%= room.name %> + <%= room.building.name %> + <% if room.level.nil? %> + <%= "——" %> + <% else %> + <%= room.level %> + <% end %> + <% if room.description.nil? %> + <%= "——" %> + <% else %> + <%= room.description %> + <% end %> + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_room_path(room.id), + destroy_path: admin_room_path(room.id) %> + + <% end %> + + +
    diff --git a/app/views/admin/service/index.html.erb b/app/views/admin/service/index.html.erb index 7c2cc5d..c32eab3 100644 --- a/app/views/admin/service/index.html.erb +++ b/app/views/admin/service/index.html.erb @@ -34,57 +34,54 @@ <%= render 'shared/modal_button_add', path: new_admin_service_path, classes: "btn btn-primary w-100", text: t("b_add") %> -
    -
    - - - - - - - - - - - - - <% @services.each do |service| %> - - - +
    +
    +
    +
    <%= t('activerecord.attributes.service.id') %><%= t('activerecord.attributes.service.name') %><%= t('activerecord.attributes.service.division') %><%= t('activerecord.attributes.service.organization') %><%= t('activerecord.attributes.service.building') %><%= t('action') %>
    <%= service.id %><%= service.name %>
    + <%= render 'shared/ui/table/caption/all_items', + count: Service.count %> + + + + + + + + <%= render 'shared/ui/table/header/action' %> + + + + <% @services.each do |service| %> + + + - <% if service.division.nil? %> - - <% else %> - - <% end %> + <% if service.division.nil? %> + + <% else %> + + <% end %> - <% if service.organization.nil? %> - - <% else %> - - <% end %> + <% if service.organization.nil? %> + + <% else %> + + <% end %> - <% if service.building.nil? %> - - <% else %> - - <% end %> + <% if service.building.nil? %> + + <% else %> + + <% end %> - - - <% end %> - -
    <%= t('activerecord.attributes.service.id') %><%= t('activerecord.attributes.service.name') %><%= t('activerecord.attributes.service.division') %><%= t('activerecord.attributes.service.organization') %><%= t('activerecord.attributes.service.building') %>
    <%= service.id %><%= service.name %><%= "——" %><%= service.division.name %><%= "——" %><%= service.division.name %><%= "——" %><%= service.organization.name %><%= "——" %><%= service.organization.name %><%= "——" %><%= service.building.name %><%= "——" %><%= service.building.name %> -
    - <%= link_to edit_admin_service_path(service.id), class: "btn", data: { action: "click->modal#open", turbo_stream: "" } do %> - - <% end %> - <%= button_to admin_service_path(service.id), method: :delete, class: "btn" do %> - - <% end %> -
    -
    + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_service_path(service.id), + destroy_path: admin_service_path(service.id) %> + + <% end %> + + +
    diff --git a/app/views/admin/supplementary_kit/index.html.erb b/app/views/admin/supplementary_kit/index.html.erb index f3dd2df..e58ff17 100644 --- a/app/views/admin/supplementary_kit/index.html.erb +++ b/app/views/admin/supplementary_kit/index.html.erb @@ -32,40 +32,42 @@ <%= render 'shared/modal_button_add', path: new_admin_supplementary_kit_path, classes: "btn btn-primary w-100 mb-3", text: t("b_add") %> -
    -
    - <% @supplementary_kits.each do |supplementary_kit| %> - <% sk_text = "Kit#{supplementary_kit.id}" %> -
    -
    -

    - -

    -
    -
    - <% device_components_in_this_sk = supplementary_kit.device_components %> - <% if device_components_in_this_sk.length > 0 %> -

    <%= t('activerecord.models.device_component')%>:

    - <%= render 'shared/show/supplementary_kit', device_components: device_components_in_this_sk %> - <% end %> -

    - <%= t('activerecord.attributes.supplementary_kit.description')%>: <%= supplementary_kit.description %> -

    -
    -
    - <%= link_to t('b_change'), edit_admin_supplementary_kit_path(supplementary_kit), class: "btn btn-primary", data: { action: "click->modal#open", turbo_stream: "" }%> -
    -
    - <%= button_to t('b_delete'), admin_supplementary_kit_path(supplementary_kit), method: :delete, class: "btn btn-danger"%> +
    +
    +
    + <% @supplementary_kits.each do |supplementary_kit| %> + <% sk_text = "Kit#{supplementary_kit.id}" %> +
    +
    +

    + +

    +
    +
    + <% device_components_in_this_sk = supplementary_kit.device_components %> + <% if device_components_in_this_sk.length > 0 %> +

    <%= t('activerecord.models.device_component')%>:

    + <%= render 'shared/show/supplementary_kit', device_components: device_components_in_this_sk %> + <% end %> +

    + <%= t('activerecord.attributes.supplementary_kit.description')%>: <%= supplementary_kit.description %> +

    +
    +
    + <%= link_to t('b_change'), edit_admin_supplementary_kit_path(supplementary_kit), class: "btn btn-primary", data: { action: "click->modal#open", turbo_stream: "" }%> +
    +
    + <%= button_to t('b_delete'), admin_supplementary_kit_path(supplementary_kit), method: :delete, class: "btn btn-danger"%> +
    + <% end %>
    - <% end %>
    diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb index b537902..7c11093 100644 --- a/app/views/admin/users/index.html.erb +++ b/app/views/admin/users/index.html.erb @@ -62,53 +62,57 @@ <%= t('b_add')%> <% end %>
    -
    -
    - - - - - - - - - - - - - - - - - - <% @users.each do |user| %> - - - - - - - - - - - - +
    +
    +
    +
    <%=t('activerecord.attributes.user.tabel_id')%><%=t('activerecord.attributes.user.last_name')%><%=t('activerecord.attributes.user.first_name')%><%=t('activerecord.attributes.user.second_name')%><%=t('activerecord.attributes.user.role')%><%=t('activerecord.attributes.user.email')%><%=t('activerecord.attributes.user.phone')%><%=t('activerecord.attributes.user.service')%><%=t('created_at')%><%=t('updated_at')%><%=t('action')%>
    <%= user.tabel_id %><%= user.last_name %><%= user.first_name %><%= user.second_name %><%= user.role %><%= user.email %><%= user.phone %><%= user.service.name %><%= formatted_date(user.created_at, :short_full) %><%= formatted_date(user.updated_at, :short_full) %> -
    - <%= link_to edit_admin_user_path(user), class: "btn" do %> - - <% end %> - <% unless user.id == current_user.id %> - <%= button_to admin_user_path(user), method: :delete, class: "btn" do %> - - <% end %> - <% end %> -
    -
    + <%= render 'shared/ui/table/caption/all_items', + count: User.count %> + + + + + + + + + + + + + <%= render 'shared/ui/table/header/action' %> - <% end %> - -
    <%=t('activerecord.attributes.user.tabel_id')%><%=t('activerecord.attributes.user.last_name')%><%=t('activerecord.attributes.user.first_name')%><%=t('activerecord.attributes.user.second_name')%><%=t('activerecord.attributes.user.role')%><%=t('activerecord.attributes.user.email')%><%=t('activerecord.attributes.user.phone')%><%=t('activerecord.attributes.user.service')%><%=t('created_at')%><%=t('updated_at')%>
    + + + <% @users.each do |user| %> + + <%= user.tabel_id %> + <%= user.last_name %> + <%= user.first_name %> + <%= user.second_name %> + <%= user.role %> + <%= user.email %> + <%= user.phone %> + <%= user.service.name %> + <%= formatted_date(user.created_at, :short_full) %> + <%= formatted_date(user.updated_at, :short_full) %> + +
    + <%= link_to edit_admin_user_path(user), class: "btn btn-sm" do %> + + <% end %> + <% unless user.id == current_user.id %> + <%= button_to admin_user_path(user), method: :delete, class: "btn btn-sm" do %> + + <% end %> + <% end %> +
    + + + <% end %> + + +
    diff --git a/app/views/shared/index/_inspection.html.erb b/app/views/shared/index/_inspection.html.erb index 5b6932c..d0a2a61 100644 --- a/app/views/shared/index/_inspection.html.erb +++ b/app/views/shared/index/_inspection.html.erb @@ -59,121 +59,105 @@
    <% if @inspections.count > 0 %> -
    -
    - - - - - - <% unless is_new_tasks %> - - <% end %> - - - <% if is_completed_tasks %> - - - <% end %> - - - - - - <% @inspections.each do |inspection| %> +
    +
    +
    +
    <%= t('.device_serial_id') %><%= t('.device_tabel_id') %><%= t('.assigned_user') %><%= t('.target') %><%= t('.state') %><%= t('.conclusion') %><%= t('.conclusion_date') %><%= t('.creator') %><%= t('action') %>
    + - - + + <% unless is_new_tasks %> - <% if inspection.performer.present? %> - - <% else %> - - <% end %> + <% end %> - - + + <% if is_completed_tasks %> - - <% if inspection.conclusion_date.present? %> - - <% else %> + + + <% end %> + + <%= render 'shared/ui/table/header/action'%> + + + + <% @inspections.each do |inspection| %> + + + + <% unless is_new_tasks %> + <% if inspection.performer.present? %> + + <% else %> + <% end %> <% end %> - <% end %> - - + + <% if is_completed_tasks %> + + <% if inspection.conclusion_date.present? %> + + <% else %> + + <% end %> + <% end %> + + - - <% end %> - -
    <%= inspection.device.serial_id %><%= inspection.device.tabel_id %><%= t('.device_serial_id') %><%= t('.device_tabel_id') %><%= inspection.performer.last_name + " " + inspection.performer.first_name %><%= t('.assigned_user') %><%= t("activerecord.attributes.inspection.#{inspection.type_target}") %><%= t("activerecord.attributes.inspection.#{inspection.state}") %><%= t('.target') %><%= t('.state') %><%= inspection.conclusion %><%= formatted_date(inspection.conclusion_date, :short_full) %><%= t('.conclusion') %><%= t('.conclusion_date') %><%= t('.creator') %>
    <%= inspection.device.serial_id %><%= inspection.device.tabel_id %><%= inspection.performer.last_name + " " + inspection.performer.first_name %><%= inspection.creator.last_name + " " + inspection.creator.first_name %> -
    -
    - <%= link_to inspection_path(id: inspection.id, previous_action: @previous_action), class: 'btn p-0' do %> +
    <%= t("activerecord.attributes.inspection.#{inspection.type_target}") %><%= t("activerecord.attributes.inspection.#{inspection.state}") %><%= inspection.conclusion %><%= formatted_date(inspection.conclusion_date, :short_full) %><%= inspection.creator.last_name + " " + inspection.creator.first_name %> +
    + <%= link_to inspection_path(id: inspection.id, previous_action: @previous_action), class: 'btn btn-sm' do %> - <% end %> -
    - <% if can? :accept_task, Inspection %> - <% unless path == all_tasks_inspection_index_path %> - <% case inspection.state %> - <% when "task_created"%> -
    + <% end %> + <% if can? :accept_task, Inspection %> + <% unless path == all_tasks_inspection_index_path %> + <% case inspection.state %> + <% when "task_created"%> <%= button_to accept_task_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> + method: :post, class: 'btn btn-sm' do %> <% end%> -
    - <% when "task_accepted"%> -
    + <% when "task_accepted"%> <%= button_to complete_verification_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> + method: :post, class: 'btn btn-sm' do %> <% end %> -
    -
    <%= button_to fail_verification_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> + method: :post, class: 'btn btn-sm' do %> <% end %> -
    - <% when "verification_failed"%> -
    + <% when "verification_failed"%> <%= button_to send_to_repair_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> + method: :post, class: 'btn btn-sm' do %> <% end %> -
    -
    <%= button_to close_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> + method: :post, class: 'btn btn-sm' do %> <% end %> -
    - <% when "sent_to_repair"%> -
    + <% when "sent_to_repair"%> <%= button_to return_from_repair_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> + method: :post, class: 'btn btn-sm' do %> <% end %> -
    - <% when "returned_from_repair"%> -
    + <% when "returned_from_repair"%> <%= button_to send_from_repair_to_verification_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> + method: :post, class: 'btn btn-sm' do %> <% end %> -
    -
    <%= button_to send_from_repair_to_close_inspection_path(inspection), - method: :post, class: 'btn p-0' do %> + method: :post, class: 'btn btn-sm' do %> <% end %> -
    + <% end %> <% end %> <% end %> - <% end %> - -
    +
    + + + <% end %> + + +
    From 67d1665280880b18137b61970353bc4ad38c7afb Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 19 Feb 2024 11:57:36 +0400 Subject: [PATCH 105/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=20react-=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B5=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/javascript/Components/Armstrong/Armstrong.jsx | 4 ++-- app/javascript/Components/Armstrong/Filter/Filter.jsx | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/javascript/Components/Armstrong/Armstrong.jsx b/app/javascript/Components/Armstrong/Armstrong.jsx index 1fc6287..75bd926 100644 --- a/app/javascript/Components/Armstrong/Armstrong.jsx +++ b/app/javascript/Components/Armstrong/Armstrong.jsx @@ -26,7 +26,7 @@ export default function Armstrong() { case 'normal': return ; case 'warning': - return ; + return ; case 'danger': return ; default: @@ -96,7 +96,7 @@ export default function Armstrong() { return (
    - + {isModalOpen && ( - + ); } From bbc36d738f81c21b1cc78fe5fccf090e03e409a8 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 19 Feb 2024 14:46:44 +0400 Subject: [PATCH 106/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BB=D0=BE=D0=BD=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=B2=20Server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/migrate/20240219101812_server_ip_address_rename.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20240219101812_server_ip_address_rename.rb diff --git a/db/migrate/20240219101812_server_ip_address_rename.rb b/db/migrate/20240219101812_server_ip_address_rename.rb new file mode 100644 index 0000000..1d61fd3 --- /dev/null +++ b/db/migrate/20240219101812_server_ip_address_rename.rb @@ -0,0 +1,5 @@ +class ServerIpAddressRename < ActiveRecord::Migration[7.0] + def change + rename_column :servers, :ip_adress, :ip_address + end +end From ac9785ba5b5d9a3c66a49090314959ecd609a331 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 19 Feb 2024 14:47:34 +0400 Subject: [PATCH 107/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D1=83=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20Con?= =?UTF-8?q?trolPoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/control_point_controller.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controllers/admin/control_point_controller.rb b/app/controllers/admin/control_point_controller.rb index 2ea2021..5d43457 100644 --- a/app/controllers/admin/control_point_controller.rb +++ b/app/controllers/admin/control_point_controller.rb @@ -29,8 +29,6 @@ def update def destroy assigned_models_count = - Room.where(control_point_id: params[:id]).count + - Device.where(control_point_id: params[:id]).count + Channel.where(control_point_id: params[:id]).count if assigned_models_count.zero? From 0daa10b5709c16a93bb411d3252c77cb5ff0fb6f Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 19 Feb 2024 14:48:05 +0400 Subject: [PATCH 108/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=D1=8C=D1=8E=D1=88=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=B8=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D0=BB?= =?UTF-8?q?=D0=B5=D1=80=D1=8B=20=D0=B4=D0=BB=D1=8F=20Server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/server_controller.rb | 25 +++- app/controllers/concerns/server_concern.rb | 55 ++++++++ app/models/server.rb | 11 +- .../admin/server/create.turbo_stream.erb | 1 + app/views/admin/server/edit.turbo_stream.erb | 3 + app/views/admin/server/index.html.erb | 125 ++++++++++++++++++ app/views/admin/server/new.turbo_stream.erb | 3 + .../admin/server/update.turbo_stream.erb | 1 + app/views/shared/admin/form/_server.html.erb | 12 ++ .../shared/admin/navbar/_asrc_navbar.html.erb | 2 +- config/locales/ru.yml | 9 ++ db/schema.rb | 4 +- 12 files changed, 246 insertions(+), 5 deletions(-) create mode 100644 app/controllers/concerns/server_concern.rb create mode 100644 app/views/admin/server/create.turbo_stream.erb create mode 100644 app/views/admin/server/edit.turbo_stream.erb create mode 100644 app/views/admin/server/new.turbo_stream.erb create mode 100644 app/views/admin/server/update.turbo_stream.erb create mode 100644 app/views/shared/admin/form/_server.html.erb diff --git a/app/controllers/admin/server_controller.rb b/app/controllers/admin/server_controller.rb index add1cdb..d30362c 100644 --- a/app/controllers/admin/server_controller.rb +++ b/app/controllers/admin/server_controller.rb @@ -1,3 +1,26 @@ class Admin::ServerController < ApplicationController - def index; end + include ServerConcern + + load_and_authorize_resource + # before_action :set_server, only: [:show, :edit, :update, :destroy] + + def index + server_index + end + + def new + @server = Server.new + end + + def create + server_create + end + + def update + server_update + end + + def destroy + server_destroy + end end diff --git a/app/controllers/concerns/server_concern.rb b/app/controllers/concerns/server_concern.rb new file mode 100644 index 0000000..ad9f783 --- /dev/null +++ b/app/controllers/concerns/server_concern.rb @@ -0,0 +1,55 @@ +module ServerConcern + extend ActiveSupport::Concern + + included do # rubocop:disable Metrics/BlockLength + def server_index + @query = Server.ransack(params[:q]) + @pagy, @servers = pagy(@query.result.order(:name)) + end + + def server_create + @server = Server.new(server_params) + + if @server.save + redirect_back(fallback_location: root_path) + else + render(:new, status: :unprocessable_entity) + end + end + + def server_update + if @server.update(server_params) + redirect_back(fallback_location: root_path) + else + render(:edit, status: :unprocessable_entity) + end + end + + def server_destroy + assigned_models_count = Channel.where(server_id: params[:id]).count + + if assigned_models_count.zero? + @server.destroy + else + flash[:error] = t('message.admin.server.delete.error') + end + redirect_to(admin_server_index_path) + end + + private + + def set_server + @server = Server.find(params[:id]) + end + + def server_params + params.require(:server).permit( + :name, + :ip_address, + :inventory_id, + :service_id, + :room_id, + ) + end + end +end diff --git a/app/models/server.rb b/app/models/server.rb index 73e12cf..092bce0 100644 --- a/app/models/server.rb +++ b/app/models/server.rb @@ -6,8 +6,17 @@ class Server < ApplicationRecord has_many :channels - validates :ip_adress, + validates :name, :service, :room, presence: true + validates :ip_address, presence: true, uniqueness: true, format: { with: Resolv::IPv4::Regex } + + def self.ransackable_attributes(_auth_object = nil) + ['created_at', 'id', 'inventory_id', 'ip_address', 'name', 'room_id', 'service_id', 'updated_at'] + end + + def self.ransackable_associations(_auth_object = nil) + ['channels', 'room', 'service'] + end end diff --git a/app/views/admin/server/create.turbo_stream.erb b/app/views/admin/server/create.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/server/create.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/admin/server/edit.turbo_stream.erb b/app/views/admin/server/edit.turbo_stream.erb new file mode 100644 index 0000000..3874fd9 --- /dev/null +++ b/app/views/admin/server/edit.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.server.edit') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/server", + locals: { path: admin_server_path(@server), server: @server } %> diff --git a/app/views/admin/server/index.html.erb b/app/views/admin/server/index.html.erb index e69de29..16d3282 100644 --- a/app/views/admin/server/index.html.erb +++ b/app/views/admin/server/index.html.erb @@ -0,0 +1,125 @@ +<% provide :page_title, "Службы" %> +<%= render 'shared/admin/navbar/admin', selected: "asrc" %> +<%= render 'shared/flash_alert' %> +
    +
    + <%= render 'shared/admin/navbar/asrc_navbar', selected: "server" %> +
    +
    +

    + +

    +
    + <%= search_form_for(@query, url: admin_server_index_path, method: :get, class: "rounded accordion-body") do |f| %> +
    + <%= f.label :name, class: "mb-1" %> + <%= f.search_field :name_cont, class:"form-control mb-2", placeholder:"Название" %> + <%= f.label :ip_address, class: "mb-1" %> + <%= f.search_field :ip_address_cont, class:"form-control mb-2", placeholder:"192.168.1.101" %> + <%= f.label :inventory_id, class: "mb-1"%> + <%= f.search_field :inventory_id_cont, class:"form-control mb-2", placeholder:"4321" %> + + <%= f.label :service_id, class: "mb-1" %> + <%= f.collection_select(:service_id_eq, Service.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class =>'form-select mb-2'}) %> + <%= f.label :room_id, class: "mb-1" %> + <%= f.collection_select(:room_id_eq, Service.all, + :id, :name, {:include_blank => t('combobox_blank')}, {:class =>'form-select mb-2'}) %> +
    + <%= f.submit t('b_accept'), class: 'btn btn-primary w-100 my-2'%> +
    + <%= t("b_clear")%> +
    + <% end %> +
    +
    +
    + <%= render 'shared/modal_button_add', path: new_admin_server_path, classes: "btn btn-primary w-100", text: t("b_add") %> +
    +
    +
    +
    +
    + <%= render 'shared/ui/table/caption/all_items', + count: Server.count %> + + + + + + + + + <%= render 'shared/ui/table/header/action' %> + + + + <% @servers.each do |server| %> + + + + + <% if server.ip_address.nil? %> + + <% else %> + + <% end %> + + <% if server.inventory_id.nil? %> + + <% else %> + + <% end %> + + <% if server.service.nil? %> + + <% else %> + + <% end %> + + <% if server.room.nil? %> + + <% else %> + + <% end %> + + <%= render 'shared/ui/table/cell/action', + edit_path: edit_admin_server_path(server.id), + destroy_path: admin_server_path(server.id) %> + + <% end %> + +
    <%= t('activerecord.attributes.server.id') %><%= t('activerecord.attributes.server.name') %><%= t('activerecord.attributes.server.ip_address') %><%= t('activerecord.attributes.server.inventory_id') %><%= t('activerecord.attributes.server.service') %><%= t('activerecord.attributes.server.room') %>
    <%= server.id %><%= server.name %><%= "——" %><%= server.ip_address %><%= "——" %><%= server.inventory_id %><%= "——" %><%= server.service.name %><%= "——" %><%= server.room.name %>
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + <%= pagination @pagy %> +
    +
    +
    +
    +
    + diff --git a/app/views/admin/server/new.turbo_stream.erb b/app/views/admin/server/new.turbo_stream.erb new file mode 100644 index 0000000..c4ce362 --- /dev/null +++ b/app/views/admin/server/new.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.update "modal-title", t('admin.server.new') %> +<%= turbo_stream.update "modal-body", partial: "shared/admin/form/server", + locals: { path: admin_server_index_path, server: @server } %> diff --git a/app/views/admin/server/update.turbo_stream.erb b/app/views/admin/server/update.turbo_stream.erb new file mode 100644 index 0000000..3ba4d15 --- /dev/null +++ b/app/views/admin/server/update.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.dispatch_event "modalClose" %> diff --git a/app/views/shared/admin/form/_server.html.erb b/app/views/shared/admin/form/_server.html.erb new file mode 100644 index 0000000..69d3663 --- /dev/null +++ b/app/views/shared/admin/form/_server.html.erb @@ -0,0 +1,12 @@ +<%= simple_form_for server, as: :server, url: path, + class: "form-group row" do |f| %> +
    + <%= f.input :name, placeholder: 'Название службы', input_html: {:tabindex => 1}%> + <%= f.input :ip_address, input_html: { :tabindex => 2}, include_blank: false %> + <%= f.input :inventory_id, input_html: { :tabindex => 3 }, include_blank: false %> + <%= f.association :service, input_html: { :tabindex => 4}, include_blank: false %> + <%= f.association :room, input_html: { :tabindex => 5}, include_blank: false %> +
    + + <%= render 'shared/form/actions', f:f %> +<% end %> diff --git a/app/views/shared/admin/navbar/_asrc_navbar.html.erb b/app/views/shared/admin/navbar/_asrc_navbar.html.erb index bb98cc0..60a4ff7 100644 --- a/app/views/shared/admin/navbar/_asrc_navbar.html.erb +++ b/app/views/shared/admin/navbar/_asrc_navbar.html.erb @@ -6,7 +6,7 @@ <% end %>
    - <%= link_to admin_channel_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'server' } text-start" do %> + <%= link_to admin_server_index_path, class: "btn btn-sm w-100 #{ 'btn-primary' if selected == 'server' } text-start" do %> <%= 'Серверы' %> <% end %>
    diff --git a/config/locales/ru.yml b/config/locales/ru.yml index ff698fd..0b0bf7a 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -325,6 +325,15 @@ ru: organization_id: Организация building: Здание building_id: Здание + server: + id: ID + name: Название + ip_address: IPv4 + inventory_id: № инв. + service: Служба + service_id: Служба + room: Расположение + room_id: Расположение user: tabel_id: Таб. № first_name: Имя diff --git a/db/schema.rb b/db/schema.rb index f193735..c294e57 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_02_14_111919) do +ActiveRecord::Schema[7.0].define(version: 2024_02_19_101812) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -225,7 +225,7 @@ create_table "servers", force: :cascade do |t| t.string "name" - t.string "ip_adress" + t.string "ip_address" t.integer "inventory_id" t.bigint "service_id", null: false t.bigint "room_id", null: false From d7107f06b5b7169ed8a77bf16f1a6bcfc7418f6e Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 19 Feb 2024 14:54:46 +0400 Subject: [PATCH 109/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BF=D0=B0=D0=B4=D0=B0=D1=8E?= =?UTF-8?q?=D1=89=D0=B8=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/factories/servers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/factories/servers.rb b/test/factories/servers.rb index 6e23ccd..55f5548 100644 --- a/test/factories/servers.rb +++ b/test/factories/servers.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory :server do name { 'MyString' } - ip_adress + ip_address { '127.0.0.1' } inventory_id { 1 } service { association :service } room { association :room } From 12693ecdc01eeca08cc4cdfdb83ab052085c4aef Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 20 Feb 2024 08:48:04 +0400 Subject: [PATCH 110/132] =?UTF-8?q?UPD:=20=D0=BE=D1=81=D1=82=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE?= =?UTF-8?q?=20=D1=83=D0=BD=D0=B8=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BC=D0=B5=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B2=20=D0=B2=D1=8C=D1=8E-=D1=82=D0=B0=D0=B1=D0=BB=D0=B8?= =?UTF-8?q?=D1=86=D0=B5=20Device?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/shared/index/_device.html.erb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/shared/index/_device.html.erb b/app/views/shared/index/_device.html.erb index d73cc3d..8ab8a97 100644 --- a/app/views/shared/index/_device.html.erb +++ b/app/views/shared/index/_device.html.erb @@ -122,9 +122,10 @@ <% else %> - <% device.control_point.each do |e| %> - <%= content_tag(:span, e.room.name, class: "btn btn-light btn-sm") %> - <% end %> + <% @rooms = device.control_point.map { |cp| cp.room.name } %> + <% @rooms.uniq.each do |e| %> + <%= content_tag(:span, e, class: "btn btn-light btn-sm") %> + <% end %> From 19224ab7216f691c31d95f6e75820e4d21a2eff7 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 20 Feb 2024 08:48:29 +0400 Subject: [PATCH 111/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BF=D1=80=D0=B8=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B8=20=D0=BA=D0=B0=D0=BD=D0=B0=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/concerns/channel_concern.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/controllers/concerns/channel_concern.rb b/app/controllers/concerns/channel_concern.rb index 8dbe3c3..bda688e 100644 --- a/app/controllers/concerns/channel_concern.rb +++ b/app/controllers/concerns/channel_concern.rb @@ -10,6 +10,9 @@ def channel_index def channel_create @channel = Channel.new(channel_params) + @channel.event_datetime = Time.now + @channel.is_online = true + if @channel.save redirect_back(fallback_location: root_path) else From 43628e903a5a7188bc062f423b4e8d48dc2b3740 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 20 Feb 2024 12:18:37 +0400 Subject: [PATCH 112/132] =?UTF-8?q?UPD:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86?= =?UTF-8?q?=D1=83=20DeviceModel,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D1=8F=D0=B5=D1=82=20=D0=BA=D0=BE=D0=BB=D0=BE=D0=BD=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BC=D0=B5=D0=B6=D0=BF=D0=BE=D0=B2=D0=B5=D1=80=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=D0=B0=20=D0=B8=20=D0=BD=D0=BE=D0=BC=D0=B5=D1=80?= =?UTF-8?q?=D0=B0=20=D0=B3=D0=BE=D1=81.=20=D1=80=D0=B5=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20240220052529_add_reg_column_to_device_model.rb | 8 ++++++++ .../20240220061932_change_inspection_interval_type.rb | 5 +++++ 2 files changed, 13 insertions(+) create mode 100644 db/migrate/20240220052529_add_reg_column_to_device_model.rb create mode 100644 db/migrate/20240220061932_change_inspection_interval_type.rb diff --git a/db/migrate/20240220052529_add_reg_column_to_device_model.rb b/db/migrate/20240220052529_add_reg_column_to_device_model.rb new file mode 100644 index 0000000..17a96a3 --- /dev/null +++ b/db/migrate/20240220052529_add_reg_column_to_device_model.rb @@ -0,0 +1,8 @@ +class AddRegColumnToDeviceModel < ActiveRecord::Migration[7.0] + def change + add_column :device_models, :gos_registry_id, :string, null: true + add_column :device_models, :inspection_interval, :float, default: 1.0, null: false + + remove_column :devices, :inspection_interval + end +end diff --git a/db/migrate/20240220061932_change_inspection_interval_type.rb b/db/migrate/20240220061932_change_inspection_interval_type.rb new file mode 100644 index 0000000..d990fe5 --- /dev/null +++ b/db/migrate/20240220061932_change_inspection_interval_type.rb @@ -0,0 +1,5 @@ +class ChangeInspectionIntervalType < ActiveRecord::Migration[7.0] + def change + change_column :device_models, :inspection_interval, :int, default: 1 + end +end From 957d4fae6b8f51f937377f88d05329bd1e67bc7e Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 20 Feb 2024 12:19:13 +0400 Subject: [PATCH 113/132] =?UTF-8?q?UPD:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=D1=8C=D1=8E=D1=88=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=B4=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=D0=B4?= =?UTF-8?q?=D0=BD=D1=8E=D1=8E=20=D0=BC=D0=B8=D0=B3=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/device_model_controller.rb | 20 ----- .../concerns/device_model_concern.rb | 4 + app/models/device_model.rb | 4 + app/views/admin/device_model/index.html.erb | 74 ++++++++++++------- app/views/layouts/application.html.erb | 10 +++ .../shared/admin/form/_device_model.html.erb | 40 ++++++---- config/locales/ru.yml | 2 + db/schema.rb | 5 +- 8 files changed, 97 insertions(+), 62 deletions(-) diff --git a/app/controllers/admin/device_model_controller.rb b/app/controllers/admin/device_model_controller.rb index cdec15e..6ae0040 100644 --- a/app/controllers/admin/device_model_controller.rb +++ b/app/controllers/admin/device_model_controller.rb @@ -45,24 +45,4 @@ def destroy def set_device_model @device_model = DeviceModel.find(params[:id]) end - - def device_model_params - params.require(:device_model).permit( - :name, - :measurement_group_id, - :measurement_class_id, - :measuring_unit, - :safety_class, - :accuracy_class, - :measurement_sensitivity, - :measurement_min, - :measurement_max, - :manufacturer_id, - :supplementary_kit_id, - :is_complete_device, - :is_tape_rolling_mechanism, - :doc_url, - :image_url, - ) - end end diff --git a/app/controllers/concerns/device_model_concern.rb b/app/controllers/concerns/device_model_concern.rb index 09ca0fd..e565cb2 100644 --- a/app/controllers/concerns/device_model_concern.rb +++ b/app/controllers/concerns/device_model_concern.rb @@ -28,6 +28,10 @@ def device_model_params :is_tape_rolling_mechanism, :doc_url, :image_url, + :calibration_min, + :calibration_max, + :gos_registry_id, + :inspection_interval, ) end end diff --git a/app/models/device_model.rb b/app/models/device_model.rb index e00020e..daaadff 100644 --- a/app/models/device_model.rb +++ b/app/models/device_model.rb @@ -33,6 +33,10 @@ def self.ransackable_attributes(_auth_object = nil) 'name', 'safety_class', 'updated_at', + 'gos_registry_id', + 'inspection_interval', + 'calibration_min', + 'calibration_min', ] end diff --git a/app/views/admin/device_model/index.html.erb b/app/views/admin/device_model/index.html.erb index 64fe22b..a0a90ee 100644 --- a/app/views/admin/device_model/index.html.erb +++ b/app/views/admin/device_model/index.html.erb @@ -14,35 +14,39 @@ <%= search_form_for(@query, url:admin_device_model_index_path, method: :get, data:{controller: "filter", 'filtrator_class': 'filtrator-class-index'}, class: "rounded accordion-body") do |f| %>
    - <%= f.label :name, class:"mb-2"%> - <%= f.search_field :name_cont, class: 'form-control', placeholder: 'Название' %> - <%= f.label :manufacturer_id, class:"mb-2" %> + <%= f.label :gos_registry_id, class: "mb-1" %> + <%= f.search_field :gos_registry_id_cont, class: "form-control mb-2", placeholder: "7595-80" %> + <%= f.label :name, class:"mb-1"%> + <%= f.search_field :name_cont, class: 'form-control mb-2', placeholder: 'Название' %> + <%= f.label :manufacturer_id, class:"mb-1" %> <%= f.collection_select(:manufacturer_id_eq, Manufacturer.all, - :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select'}) %> - <%= f.label :measurement_group_id, class:"mb-2" %> + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select mb-2'}) %> + <%= f.label :inspection_interval, class: "mb-1" %> + <%= f.collection_select :inspection_interval_eq, (1..10).map { |n| [n, n] }, :last, :first, {:include_blank => t('combobox_blank')}, { class: "form-select mb-2"} %> + <%= f.label :measurement_group_id, class:"mb-1" %> <%= f.collection_select(:measurement_group_id_eq, MeasurementGroup.all, - :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select', id:"filtrator-class-index", data: {'action': 'onchange -> filter#filter', 'to_filter_class': 'filter-class-index', 'filtrator_class': 'filtrator-class-index'}}) %> - <%= f.label :measurement_class_id, class:"mb-2" %> + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select mb-2', id:"filtrator-class-index", data: {'action': 'onchange -> filter#filter', 'to_filter_class': 'filter-class-index', 'filtrator_class': 'filtrator-class-index'}}) %> + <%= f.label :measurement_class_id, class:"mb-1" %> <%= f.collection_select(:measurement_class_id_eq, MeasurementClass.all, - :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select', id: "filter-class-index"}) %> - <%= f.label :measuring_unit, class:"mb-2" %> - <%= f.search_field :measuring_unit_cont, class: 'form-control', placeholder: 'мЗв/ч' %> - <%= f.label :measurement_sensitivity, class:"mb-2" %> - <%= f.search_field :measurement_sensitivity_eq, class: 'form-control', placeholder: '1.0' %> - <%= f.label :measurement_min, class:"mb-2" %> - <%= f.search_field :measurement_min_eq, class: 'form-control', placeholder: '1.0' %> - <%= f.label :measurement_max, class:"mb-2" %> - <%= f.search_field :measurement_max_eq, class: 'form-control', placeholder: '1.0' %> - <%= f.label :safety_class, class:"mb-2" %> - <%= f.search_field :safety_class_cont, class: 'form-control', placeholder: '3Н' %> - <%= f.label :accuracy_class, class:"mb-2" %> - <%= f.search_field :accuracy_class_eq, class: 'form-control', placeholder: '1.0' %> - <%= f.label :is_complete_device, class:"mb-2" %> + :id, :name, {:include_blank => t('combobox_blank')}, {:class=>'form-select mb-2', id: "filter-class-index"}) %> + <%= f.label :measuring_unit, class:"mb-1" %> + <%= f.search_field :measuring_unit_cont, class: 'form-control mb-2', placeholder: 'мЗв/ч' %> + <%= f.label :measurement_sensitivity, class:"mb-1" %> + <%= f.search_field :measurement_sensitivity_eq, class: 'form-control mb-2', placeholder: '1.0' %> + <%= f.label :measurement_min, class:"mb-1" %> + <%= f.search_field :measurement_min_eq, class: 'form-control mb-2', placeholder: '1.0' %> + <%= f.label :measurement_max, class:"mb-1" %> + <%= f.search_field :measurement_max_eq, class: 'form-control mb-2', placeholder: '1.0' %> + <%= f.label :safety_class, class:"mb-1" %> + <%= f.collection_select :safety_class_eq, (1..4).map { |n| ["#{n}Н", "#{n}Н"] }, :last, :first, {:include_blank => t('combobox_blank')}, { class: "form-select mb-2" } %> + <%= f.label :accuracy_class, class:"mb-1" %> + <%= f.search_field :accuracy_class_eq, class: 'form-control mb-2', placeholder: '1.0' %> + <%= f.label :is_complete_device, class:"mb-1" %> <%= f.select(:is_complete_device_eq, [['Да', true], ['Нет', false]], - {:include_blank => t('combobox_blank')}, {:class =>'form-select form-select'})%> - <%= f.label :is_tape_rolling_mechanism, class:"mb-2" %> - <%= f.select(:is_tape_rolling_mechanism_eq, [['Да', true], ['Нет', false]], - {:include_blank => t('combobox_blank')}, {:class =>'form-select form-select mb-2'})%> + {:include_blank => t('combobox_blank')}, {:class =>'form-select mb-2'})%> + <%= f.label :is_tape_rolling_mechanism, class:"mb-1" %> + <%= f.select(:is_tape_rolling_mechanism_eq, [['Да', true], ['Нет', false]], + {:include_blank => t('combobox_blank')}, {:class =>'form-select mb-2'})%> <%= f.submit t('b_accept'), class: 'col btn btn-primary w-100 my-2'%>
    @@ -63,8 +67,10 @@ count: DeviceModel.count %> + <%= t('activerecord.attributes.device_model.gos_registry_id')%> <%= t('activerecord.attributes.device_model.name')%> <%= t('activerecord.attributes.device_model.manufacturer')%> + <%= t('activerecord.attributes.device_model.inspection_interval')%> <%= t('activerecord.attributes.device_model.measurement_group')%> <%= t('activerecord.attributes.device_model.measurement_class')%> <%= t('activerecord.attributes.device_model.measuring_unit')%> @@ -80,8 +86,26 @@ <% @device_models.each do |device_model| %> + <% if device_model.gos_registry_id.nil? or device_model.gos_registry_id.empty? %> + <%= "——" %> + <% else %> + + + <%= device_model.gos_registry_id %> + + + <% end %> <%= device_model.name %> <%= device_model.manufacturer.name %> + <% if device_model.inspection_interval.nil? or device_model.inspection_interval.zero? %> + <%= "——" %> + <% else %> + + + <%= device_model.inspection_interval %> + + + <% end %> <%= device_model.measurement_group.name %> <%= device_model.measurement_class.name %> <%= device_model.measuring_unit %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 621e5f1..25a82b3 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -13,6 +13,16 @@ body::-webkit-scrollbar { display: none; } + + /* Disabling spinner for Chrome, Opera, and Safari */ + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + input[type=number] { + -moz-appearance: textfield; + } diff --git a/app/views/shared/admin/form/_device_model.html.erb b/app/views/shared/admin/form/_device_model.html.erb index d5f512c..8d3e1a6 100644 --- a/app/views/shared/admin/form/_device_model.html.erb +++ b/app/views/shared/admin/form/_device_model.html.erb @@ -1,4 +1,4 @@ -<%= simple_form_for device_model, as: :device_model, url: path, data: {controller: 'filter', 'filtrator_class': filtrator_class_id, +<%= simple_form_for device_model, as: :device_model, url: path, data: {controller: 'filter', 'filtrator_class': filtrator_class_id, 'prev_mc_val': prev_mc_val }, class: "form-group row" do |f| %>
    @@ -17,7 +17,9 @@
    -
    +
    + <%= f.input :gos_registry_id, placeholder: "7595-80", input_html: {:tabindex => 3}%> +
    @@ -26,7 +28,7 @@
    - <%= f.association :measurement_group, label: false, input_html: {:tabindex => 3, data: {'action': 'change->filter#filter', + <%= f.association :measurement_group, label: false, input_html: {:tabindex => 4, data: {'action': 'change->filter#filter', 'to_filter_class': to_filter_class_id, 'filtrator_class': filtrator_class_id}, id: filtrator_class_id}, include_blank: false %>
    @@ -40,7 +42,7 @@
    - <%= f.association :measurement_class, label:false, input_html: {tabindex: 4, id: to_filter_class_id}, include_blank: false %> + <%= f.association :measurement_class, label:false, input_html: {tabindex: 5, id: to_filter_class_id}, include_blank: false %>
    <%= render 'shared/modal_button_add', path: new_measurement_class_path, classes: "btn btn-ligth", text: nil %> @@ -48,36 +50,44 @@
    - <%= f.input :measuring_unit, placeholder: "мЗв/ч", input_html: {:tabindex => 5} %> + <%= f.input :measuring_unit, placeholder: "мЗв/ч", input_html: {:tabindex => 6} %>
    - <%= f.input :measurement_sensitivity, placeholder: "1.0", input_html: {:tabindex => 6} %> + <%= f.input :measurement_sensitivity, placeholder: "1.0", input_html: {:tabindex => 7} %>
    - <%= f.input :measurement_min, placeholder: "1.0", input_html: {:tabindex => 7} %> + <%= f.input :measurement_min, placeholder: "1.0", input_html: {:tabindex => 8} %>
    - <%= f.input :measurement_max, placeholder: "2.0", input_html: {:tabindex => 8} %> + <%= f.input :measurement_max, placeholder: "2.0", input_html: {:tabindex => 9} %>
    - <%= f.input :safety_class, placeholder: "3Н", input_html: {:tabindex => 9} %> + <%= f.input :accuracy_class, placeholder: "0.25", input_html: {:tabindex => 11} %> +
    +
    + <%= f.input :calibration_min, placeholder: "900"%>
    - <%= f.input :accuracy_class, placeholder: "0.25", input_html: {:tabindex => 10} %> + <%= f.input :calibration_max, placeholder: "1400"%>
    -
    -
    - <%= f.input :is_complete_device, input_html: {:tabindex => 11} %> - <%= f.input :is_tape_rolling_mechanism, input_html: {:tabindex => 12} %> + <%= f.label :inspection_interval, class: "mb-2" %> + <%= f.collection_select :inspection_interval, (1..10).map { |n| [n, n] }, :last, :first, {}, { class: "form-select" } %> +
    +
    + <%= f.label :safety_class, class: "mb-2" %> + <%= f.collection_select :safety_class, (1..4).map { |n| ["#{n}Н", "#{n}Н"] }, :last, :first, {}, { class: "form-select" } %> +
    +
    + <%= f.input :is_complete_device, input_html: {:tabindex => 12} %> + <%= f.input :is_tape_rolling_mechanism, input_html: {:tabindex => 13} %>
    -
    <%= render 'shared/form/actions', f:f %> <% end %> diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 0b0bf7a..f1f5ec1 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -264,6 +264,8 @@ ru: measurement_range: Диапазон изм. calibration_max: Макс. калибр. calibration_min: Мин. калибр. + inspection_interval: М/п инт. + gos_registry_id: № в ГР device_reg_group: name: Название manufacturer: diff --git a/db/schema.rb b/db/schema.rb index c294e57..c13a6fc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_02_19_101812) do +ActiveRecord::Schema[7.0].define(version: 2024_02_20_061932) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -96,6 +96,8 @@ t.datetime "updated_at", null: false t.float "calibration_min" t.float "calibration_max" + t.string "gos_registry_id" + t.integer "inspection_interval", default: 1, null: false t.index ["manufacturer_id"], name: "index_device_models_on_manufacturer_id" t.index ["measurement_class_id"], name: "index_device_models_on_measurement_class_id" t.index ["measurement_group_id"], name: "index_device_models_on_measurement_group_id" @@ -122,7 +124,6 @@ t.bigint "room_id" t.string "inspection_expiration_status", default: "prepare_to_inspection", null: false t.string "status", default: "in_stock", null: false - t.float "inspection_interval", default: 1.0, null: false t.index ["device_model_id"], name: "index_devices_on_device_model_id" t.index ["device_reg_group_id"], name: "index_devices_on_device_reg_group_id" t.index ["inventory_id"], name: "index_devices_on_inventory_id", unique: true From 40d91f5c0f40bcdd88ed37dfd1e6c7396ecf81be Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 20 Feb 2024 12:40:41 +0400 Subject: [PATCH 114/132] =?UTF-8?q?FIX:=20=D1=87=D0=B8=D0=BD=D0=B8=D1=82?= =?UTF-8?q?=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/javascript/Components/Armstrong/Modal/ModalComponent.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/Components/Armstrong/Modal/ModalComponent.jsx b/app/javascript/Components/Armstrong/Modal/ModalComponent.jsx index 583e8fc..d1d4d5e 100644 --- a/app/javascript/Components/Armstrong/Modal/ModalComponent.jsx +++ b/app/javascript/Components/Armstrong/Modal/ModalComponent.jsx @@ -8,7 +8,7 @@ export default function ModalComponent({ selectedId, show, pointName, handleClos const [chartData, setChartData] = useState([]); useEffect(() => { - ApiHelper(`http://0.0.0.0/api/v1/histories/${selectedId}`).then((result) => { + ApiHelper(`/api/v1/histories/${selectedId}`).then((result) => { setChartData(result); }); }, [selectedId]); From 6aa24319c4739757df2040effb2d9af3ca1f58b0 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 20 Feb 2024 14:40:54 +0400 Subject: [PATCH 115/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D1=83=20=D0=BF=D1=80=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8=20Device?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/device.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/device.rb b/app/models/device.rb index f2b2c14..cf19910 100644 --- a/app/models/device.rb +++ b/app/models/device.rb @@ -20,7 +20,6 @@ class Device < ApplicationRecord validates :serial_id, :tabel_id, presence: true, uniqueness: true validates :year_of_commissioning, :year_of_production, numericality: { in: 1900..current_year, message: year_error_msg }, allow_nil: true validates :year_of_production, presence: true - validates :inspection_interval, presence: true, numericality: { in: 0.1..10.0, message: inspection_interval_msg } STATUS = { verified: 'verified', From 3104835afbda7c197c0f0b6d7202a55780c180f7 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 4 Mar 2024 12:13:25 +0400 Subject: [PATCH 116/132] =?UTF-8?q?UPD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D1=8B?= =?UTF-8?q?=20=D0=BD=D0=B0=20API=20(start=5Fdatetime=20end=5Fdatetime)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/histories_controller.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/v1/histories_controller.rb b/app/controllers/api/v1/histories_controller.rb index df8d719..49854a5 100644 --- a/app/controllers/api/v1/histories_controller.rb +++ b/app/controllers/api/v1/histories_controller.rb @@ -3,18 +3,23 @@ class V1::HistoriesController < ApplicationController before_action :set_channel, only: [:show] def show - histories = History.select { |hs| hs.channel_id == @channel_id }.last(100) + histories = if params[:start_datetime].present? && params[:end_datetime].present? + History.where(event_datetime: params[:start_datetime]..params[:end_datetime], + channel_id: @channel.id) + else + History.select { |hs| hs.channel_id == @channel.id }.last(100) + end result = histories.sort { |a, b| a[:event_datetime] <=> b[:event_datetime] } - - render(json: result) + render(json: result, except: [:created_at, :updated_at]) end private def set_channel @channel = Channel.find(params[:id]) - @channel_id = @channel.id + rescue ActiveRecord::RecordNotFound => e + render(json: { error: e }, status: :not_found) end end end From ce40b2c941ba9b9ca0bccec697e714886662738a Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Mon, 4 Mar 2024 15:11:00 +0400 Subject: [PATCH 117/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20seeds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/seeds.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 4c30485..435151a 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -143,7 +143,8 @@ is_complete_device: false, is_tape_rolling_mechanism: false, doc_url: "https://www.doza.ru/catalog/handheld/124/", - image_url: nil + image_url: nil, + inspection_interval: rand(0.1..9.9) ) DeviceModel.create( @@ -160,7 +161,8 @@ is_complete_device: false, is_tape_rolling_mechanism: false, doc_url: "https://www.doza.ru/catalog/handheld/124/", - image_url: nil + image_url: nil, + inspection_interval: rand(0.1..9.9) ) 100.times do |i| @@ -178,7 +180,8 @@ is_complete_device: [true, false].sample, is_tape_rolling_mechanism: [true, false].sample, doc_url: "/this/is/doc/path/#{i}", - image_url: "/no/way/this/is/img#{i}" + image_url: "/no/way/this/is/img#{i}", + inspection_interval: rand(0.1..9.9) ) end @@ -237,7 +240,7 @@ 10.times do |i| Server.create( name: "server-#{i}", - ip_adress: "192.168.20.#{i}", + ip_address: "192.168.20.#{i}", inventory_id: i, service: Service.find_by(id: rand(1..10)), room: Room.find_by(id: rand(1..100)) @@ -295,7 +298,7 @@ # seed Device -100.times do |i| +300.times do |i| Device.create( inventory_id: i, serial_id: "#{i}-123-N", @@ -306,7 +309,6 @@ year_of_commissioning: 1991, supplementary_kit: SupplementaryKit.find_by(id: rand(1..20)), service: Service.find_by(id: rand(1..10)), - inspection_interval: rand(0.1..9.9) ) end @@ -317,7 +319,7 @@ description: "Возможное описание", room: Room.find_by(id: rand(1..99)), service: Service.find_by(id: rand(1..10)), - device: Device.find_by(id: rand(1..100)), + device: Device.find_by(id: i + 1), ) end @@ -368,14 +370,14 @@ 100.times do |i| Channel.create( channel_id: "#{i}", - control_point: ControlPoint.find_by(id: rand(1..100)), + control_point: ControlPoint.find_by(id: i + 1), server: Server.find_by(id: rand(1..10)), service: Service.find_by(id: rand(1..10)), location_description: "Description", self_background: 1.1, pre_emergency_limit: 2.2, emergency_limit: 3.3, - consumptiom: 1.0, + consumption: 1.0, conversion_coefficient: 0.0, event_system_value: 0.0, event_not_system_value: 0.0, From 3d511ea7c3a989aa981c8a597874479c57dd0d0f Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 27 Mar 2024 09:06:36 +0400 Subject: [PATCH 118/132] =?UTF-8?q?UPD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20Faker=20=D0=B2=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=20=D0=B8=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D1=81=D0=B8=D0=B4=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 2 + Gemfile.lock | 8 ++ db/seeds.rb | 266 +++++++++++---------------------------------------- 3 files changed, 67 insertions(+), 209 deletions(-) diff --git a/Gemfile b/Gemfile index 2d6fe2a..858cc7a 100644 --- a/Gemfile +++ b/Gemfile @@ -71,6 +71,8 @@ group :development, :test do gem 'factory_bot_rails' gem 'rubocop' + gem 'rubocop-rails' + gem 'faker' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 8ae3b86..04d4982 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -114,6 +114,8 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) + faker (3.3.0) + i18n (>= 1.8.11, < 2) fugit (1.8.1) et-orbi (~> 1, >= 1.2.7) raabro (~> 1.4) @@ -236,6 +238,10 @@ GEM unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.26.0) parser (>= 3.2.1.0) + rubocop-rails (2.22.1) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.33.0, < 2.0) ruby-progressbar (1.11.0) rubyzip (2.3.2) selenium-webdriver (4.8.0) @@ -315,6 +321,7 @@ DEPENDENCIES debug devise (~> 4.2) factory_bot_rails + faker importmap-rails jbuilder jsbundling-rails @@ -328,6 +335,7 @@ DEPENDENCIES ransack (~> 4.0) responders (~> 3.1) rubocop + rubocop-rails selenium-webdriver sidekiq (~> 7.1, >= 7.1.1) sidekiq-cron (~> 1.10, >= 1.10.1) diff --git a/db/seeds.rb b/db/seeds.rb index 435151a..05ee704 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,175 +1,63 @@ # seed Manufacturers +require 'faker' -Manufacturer.create( - name: "НПП ДОЗА", - adress: "124498, город Москва, город Зеленоград, Георгиевский проспект, дом 5, этаж 2, комната 49, ООО НПП «Доза»", - phone: "+7 (495) 777-84-85", - email: "info@doza.ru", - site_url: "https://www.doza.ru/" -) - -Manufacturer.create( - name: "ОАО Пятигорский завод Импульс", - adress: "357500, Россия, Ставропольский край, г. Пятигорск, ул. Малыгина, 5, ОАО «Пятигорский завод «Импульс»", - phone: "+7 (879) 333-65-14", - email: "contact@pzi.ru", - site_url: "http://pzi.ru/" -) - -10.times do |i| +15.times do |index| Manufacturer.create( - name: "Manufacturer_name_#{i}", - adress: "г. Ульяновск, Ленина #{i}", - phone: "+7 (999) 999-01-#{i}", - email: "man_email#{i}@mail.ru", - site_url: "https://site_#{i}.com" + name: Faker::Company.name, + adress: Faker::Address.full_address, + phone: Faker::PhoneNumber.cell_phone_with_country_code, + email: Faker::Internet.email, + site_url: Faker::Internet.url ) end # seed Reg Group +10.times do |index| + DeviceRegGroup.create(name: Faker::Company.name) +end -DeviceRegGroup.create(name: "ГАН") -DeviceRegGroup.create(name: "РосТехНадзор") -DeviceRegGroup.create(name: "Без группы") +meas_grp = ['Температурный', 'Рад. контроль', 'Электроизмеритель'] +meas_cls = ['Термометр', 'МЭД', 'Альфа', 'Бета', 'Гамма', 'Вольтметр', 'Амперметр'] # seed MeasurementGroup - -MeasurementGroup.create(name: "Температурный") -MeasurementGroup.create(name: "Рад. контроль") -MeasurementGroup.create(name: "Электроизмеритель") +3.times { |index| MeasurementGroup.create(name: meas_grp[index]) } # seed MeasurementClass - -MeasurementClass.create( - name: "Термометр", - measurement_group: MeasurementGroup.find_by(name: "Температурный"), - arms_device_type: nil -) - -MeasurementClass.create( - name: "МЭД", - measurement_group: MeasurementGroup.find_by(name: "Рад. контроль"), - arms_device_type: 1 -) - -MeasurementClass.create( - name: "Альфа", - measurement_group: MeasurementGroup.find_by(name: "Рад. контроль"), - arms_device_type: 1 -) - -MeasurementClass.create( - name: "Бета", - measurement_group: MeasurementGroup.find_by(name: "Рад. контроль"), - arms_device_type: 1 -) - -MeasurementClass.create( - name: "Гамма", - measurement_group: MeasurementGroup.find_by(name: "Рад. контроль"), - arms_device_type: 1 -) - -MeasurementClass.create( - name: "Активность Аэрозолей", - measurement_group: MeasurementGroup.find_by(name: "Рад. контроль"), - arms_device_type: 3 -) - -MeasurementClass.create( - name: "Активность Газов", - measurement_group: MeasurementGroup.find_by(name: "Рад. контроль"), - arms_device_type: 3 -) - -MeasurementClass.create( - name: "Радиометр/Дозиметр", - measurement_group: MeasurementGroup.find_by(name: "Рад. контроль"), - arms_device_type: nil -) - -MeasurementClass.create( - name: "Амперметр", - measurement_group: MeasurementGroup.find_by(name: "Электроизмеритель"), - arms_device_type: nil -) - -MeasurementClass.create( - name: "Вольтметр", - measurement_group: MeasurementGroup.find_by(name: "Электроизмеритель"), - arms_device_type: nil -) +10.times do |index| + MeasurementClass.create( + name: meas_cls.sample, + measurement_group: MeasurementGroup.find_by(name: meas_grp.sample), + arms_device_type: rand(1..4) + ) +end # seed SupplementaryKits - -SupplementaryKit.create( - name: "МКС-АТ1117М-комплект-1", - serial_id: "310", - description: "Набор для приборов типа МКС-АТ1117М с тремя БД (Бета, Альфа, Альфа)" -) - 20.times do |i| SupplementaryKit.create( - name: "Kit №#{i}", - serial_id: "#{i * 100}-E250-FF", - description: "Набор для приборов типа МКС-1117-#{i}" + name: "#{Faker::Device.model_name}-kit-#{i}", + serial_id: Faker::Device.serial, + description: Faker::Books::Dune.quote ) end 100.times do |i| DeviceComponent.create( - name: "Device Component #{i}", - serial_id: "#{i * 100}-E250-FF", + name: Faker::ElectricalComponents.active, + serial_id: Faker::Device.serial, measurement_min: i.to_f / 2.0, measurement_max: i.to_f * 2.0, measuring_unit: "мЗв/ч", - description: "Набор для теста", + description: Faker::Restaurant.description, supplementary_kit: SupplementaryKit.find_by(id: rand(1..20)) ) end -# seed DeviceModel - -DeviceModel.create( - name: "МКС-АТ1117М (БОИ)", - measurement_group: MeasurementGroup.find_by(name: "Рад. контроль"), - measurement_class: MeasurementClass.find_by(name: "Радиометр/Дозиметр"), - measuring_unit: "Зв", - safety_class: "3Н", - accuracy_class: 0.2, - measurement_sensitivity: nil, - measurement_min: 0.001, - measurement_max: 1.0, - manufacturer: Manufacturer.find_by(name: "НПП ДОЗА"), - is_complete_device: false, - is_tape_rolling_mechanism: false, - doc_url: "https://www.doza.ru/catalog/handheld/124/", - image_url: nil, - inspection_interval: rand(0.1..9.9) -) - -DeviceModel.create( - name: "МКС-АТ1117М (БДПБ-01)", - measurement_group: MeasurementGroup.find_by(name: "Рад. контроль"), - measurement_class: MeasurementClass.find_by(name: "Радиометр/Дозиметр"), - measuring_unit: "мин-1·см-2", - safety_class: "3Н", - accuracy_class: 0.2, - measurement_sensitivity: nil, - measurement_min: 1, - measurement_max: 500000, - manufacturer: Manufacturer.find_by(name: "НПП ДОЗА"), - is_complete_device: false, - is_tape_rolling_mechanism: false, - doc_url: "https://www.doza.ru/catalog/handheld/124/", - image_url: nil, - inspection_interval: rand(0.1..9.9) -) +# seed DeviceModel 100.times do |i| DeviceModel.create( - name: "Model_name_#{i}", - measurement_group: MeasurementGroup.find_by(name: "Температурный"), - measurement_class: MeasurementClass.find_by(name: "Термометр"), + name: Faker::Device.model_name, + measurement_group: MeasurementGroup.find_by(id: rand(1..3)), + measurement_class: MeasurementClass.find_by(id: rand(1..10)), measuring_unit: "мЗв/ч", safety_class: "#{i}Н", accuracy_class: i.to_f, @@ -179,28 +67,26 @@ manufacturer: Manufacturer.find_by(id: rand(1..10)), is_complete_device: [true, false].sample, is_tape_rolling_mechanism: [true, false].sample, - doc_url: "/this/is/doc/path/#{i}", - image_url: "/no/way/this/is/img#{i}", + doc_url: Faker::Internet.url, + image_url: Faker::Internet.url, inspection_interval: rand(0.1..9.9) ) end # seed Organization - Organization.create( - name: "АО ГНЦ НИИАР", - full_address: "Западное шоссе 9", - zip_code: "9001", - phone: "5-55-55", - fax: "5-55-55", - email: "niiar@niiar.ru" + name: Faker::Company.name, + full_address: Faker::Address.full_address, + zip_code: Faker::Address.zip_code, + phone: Faker::PhoneNumber.cell_phone_with_country_code, + fax: Faker::PhoneNumber.cell_phone_with_country_code, + email: Faker::Internet.email ) # seed Division - 10.times do |i| Division.create( - name: "Division-#{i}", + name: Faker::FunnyName.name, organization: Organization.first ) end @@ -240,7 +126,7 @@ 10.times do |i| Server.create( name: "server-#{i}", - ip_address: "192.168.20.#{i}", + ip_address: Faker::Internet.ip_v4_address, inventory_id: i, service: Service.find_by(id: rand(1..10)), room: Room.find_by(id: rand(1..100)) @@ -248,65 +134,39 @@ end # seed User - User.create( tabel_id: 0, - first_name: 'Admin', - last_name: 'Adminov', - second_name: 'Adminovich', + first_name: 'Service', + last_name: 'Administrator', + second_name: nil, email: 'admin@admin.ru', password: '12345678', role: 'admin', service: Service.find_by(id: rand(1..10)) ) -10.times do |i| - User.create( - tabel_id: 72000 + i, - first_name: "First#{i}", - last_name: "Last#{i}", - email: "engineer#{i}@email.ru", - password: "123456789#{i}", - role: 'engineer', - service: Service.find_by(id: rand(1..10)) - ) -end - -10.times do |i| - User.create( - tabel_id: 82000 + i, - first_name: "First#{i}", - last_name: "Last#{i}", - email: "inspector#{i}@email.ru", - password: "123456789#{i}", - role: 'inspector', - service: Service.find_by(id: rand(1..10)) - ) -end - -10.times do |i| +30.times do |i| User.create( - tabel_id: 92000 + i, - first_name: "First#{i}", - last_name: "Last#{i}", - email: "dosimetrist#{i}@email.ru", + tabel_id: 10000 + i, + first_name: Faker::Name.first_name, + last_name: Faker::Name.last_name, + email: Faker::Internet.email, password: "123456789#{i}", - role: 'dosimetrist', + role: ['engineer', 'inspector', 'dosimetrist'].sample, service: Service.find_by(id: rand(1..10)) ) end # seed Device - 300.times do |i| Device.create( inventory_id: i, - serial_id: "#{i}-123-N", + serial_id: Faker::Device.serial, tabel_id: i, device_model: DeviceModel.find_by(id: rand(1..100)), device_reg_group: DeviceRegGroup.find_by(id: rand(1..5)), - year_of_production: 1990, - year_of_commissioning: 1991, + year_of_production: rand(1990..2020), + year_of_commissioning: rand(1991..2020), supplementary_kit: SupplementaryKit.find_by(id: rand(1..20)), service: Service.find_by(id: rand(1..10)), ) @@ -315,8 +175,8 @@ # seed ControlPoints 200.times do |i| ControlPoint.create( - name: "Точка контроля #{i}", - description: "Возможное описание", + name: Faker::Space.star_cluster, + description: Faker::Space.star, room: Room.find_by(id: rand(1..99)), service: Service.find_by(id: rand(1..10)), device: Device.find_by(id: i + 1), @@ -324,27 +184,16 @@ end # seed Post - 10.times do |i| Post.create( user: User.first, - title: "This is post title №#{i}", - body: "Some who have read the book, or at any rate have reviewed it, found it boring, absurd, or contemptible, and I have no cause to complain, since I have similar opinions of their works, or of the kinds of writing that they evidently prefer.", + title: Faker::Movies::HarryPotter.spell, + body: Faker::Movies::HarryPotter.quote, category: "Public" ) end -2.times do |i| - Post.create( - user: User.first, - title: "This is post title №#{i}", - body: "Some who have read the book, or at any rate have reviewed it, found it boring, absurd, or contemptible, and I have no cause to complain, since I have similar opinions of their works, or of the kinds of writing that they evidently prefer.", - category: "УРБ-106" - ) -end - # seed Inspection - 20.times do |i| Inspection.create( device: Device.find_by_id(rand(1..1000)), @@ -366,7 +215,6 @@ end # seed Channel - 100.times do |i| Channel.create( channel_id: "#{i}", @@ -401,7 +249,7 @@ ) History.create( - channel_id: Channel.last, + channel_id: Channel.second, event_impulse_value: rand(0.0..100.0), event_system_value: Time.now, event_not_system_value: rand(0.0..100.0), From 3118c31a1624ab863b162730d75a6f88e01f7ef8 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 27 Mar 2024 09:09:25 +0400 Subject: [PATCH 119/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB?= =?UTF-8?q?=D0=B5=D0=BC=D1=83=20=D1=81=20=D0=BC=D0=B5=D0=B4=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=BC=20=D0=B2=D1=8B=D0=B7=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=BC=20=D0=BD=D0=B0=20api/v1/{:id}/history?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/histories_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/histories_controller.rb b/app/controllers/api/v1/histories_controller.rb index 49854a5..bcfcb05 100644 --- a/app/controllers/api/v1/histories_controller.rb +++ b/app/controllers/api/v1/histories_controller.rb @@ -7,7 +7,7 @@ def show History.where(event_datetime: params[:start_datetime]..params[:end_datetime], channel_id: @channel.id) else - History.select { |hs| hs.channel_id == @channel.id }.last(100) + History.where(channel_id: @channel.id).last(100) end result = histories.sort { |a, b| a[:event_datetime] <=> b[:event_datetime] } From b45874288500cda1f19b5df7cf82149ba2bdb212 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 27 Mar 2024 15:03:20 +0400 Subject: [PATCH 120/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20Makefile=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=83=D0=B4=D0=BE=D0=B1=D1=81=D1=82=D0=B2=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8225958 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +setup: + bundle install + yarn install + yarn build + yarn build:css + bin/rails db:drop db:create db:migrate db:seed + +start: + bundle install + yarn install + yarn build + yarn build:css + rm ./tmp/pids/server.pid + bin/dev + +clean: + bin/rails db:drop db:create db:migrate db:seed + +test: + bin/rails test From 20927f32ae2289cdba75091d3988596a5867e31a Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 27 Mar 2024 15:03:55 +0400 Subject: [PATCH 121/132] =?UTF-8?q?UPD:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20Seed,=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20Dockerfile=20=D0=B8=20Gemfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Gemfile | 1 - Gemfile.lock | 5 ----- db/seeds.rb | 8 ++++---- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 570028b..4cfcbf6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,4 +28,4 @@ ADD . $RAILS_ROOT ENV PATH=$RAILS_ROOT/bin:${PATH} EXPOSE 3000 -CMD ["bin/prod"] +CMD ["make start"] diff --git a/Gemfile b/Gemfile index 858cc7a..4d16162 100644 --- a/Gemfile +++ b/Gemfile @@ -71,7 +71,6 @@ group :development, :test do gem 'factory_bot_rails' gem 'rubocop' - gem 'rubocop-rails' gem 'faker' end diff --git a/Gemfile.lock b/Gemfile.lock index 04d4982..e05b6fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -238,10 +238,6 @@ GEM unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.26.0) parser (>= 3.2.1.0) - rubocop-rails (2.22.1) - activesupport (>= 4.2.0) - rack (>= 1.1) - rubocop (>= 1.33.0, < 2.0) ruby-progressbar (1.11.0) rubyzip (2.3.2) selenium-webdriver (4.8.0) @@ -335,7 +331,6 @@ DEPENDENCIES ransack (~> 4.0) responders (~> 3.1) rubocop - rubocop-rails selenium-webdriver sidekiq (~> 7.1, >= 7.1.1) sidekiq-cron (~> 1.10, >= 1.10.1) diff --git a/db/seeds.rb b/db/seeds.rb index 05ee704..4b4a75d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -241,17 +241,17 @@ 100.times do |i| History.create( - channel_id: Channel.first, + channel: Channel.first, event_impulse_value: rand(0.0..100.0), - event_system_value: Time.now, + event_system_value: rand(0.0..100.0), event_not_system_value: rand(0.0..100.0), event_datetime: Time.now, ) History.create( - channel_id: Channel.second, + channel: Channel.second, event_impulse_value: rand(0.0..100.0), - event_system_value: Time.now, + event_system_value: rand(0.0..100.0), event_not_system_value: rand(0.0..100.0), event_datetime: Time.now, ) From 44e9c305f96f580f2684f735d9039c985d373000 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 27 Mar 2024 22:55:25 +0400 Subject: [PATCH 122/132] =?UTF-8?q?UPD:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=B2=D1=8B=D0=B7=D0=BE=D0=B2=20API?= =?UTF-8?q?=20=D0=BD=D0=B0=20index=20=D1=80=D0=BE=D1=83=D1=82=20=D1=81=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/histories_controller.rb | 10 ++++++++++ config/routes.rb | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/histories_controller.rb b/app/controllers/api/v1/histories_controller.rb index bcfcb05..4a4fc69 100644 --- a/app/controllers/api/v1/histories_controller.rb +++ b/app/controllers/api/v1/histories_controller.rb @@ -2,6 +2,16 @@ module Api class V1::HistoriesController < ApplicationController before_action :set_channel, only: [:show] + def index + histories = if params[:start_datetime].present? && params[:end_datetime].present? + History.where(event_datetime: params[:start_datetime]..params[:end_datetime]) + else + [] + end + + render(json: histories, except: [:id, :event_impulse_value, :event_not_system_value, :created_at, :updated_at]) + end + def show histories = if params[:start_datetime].present? && params[:end_datetime].present? History.where(event_datetime: params[:start_datetime]..params[:end_datetime], diff --git a/config/routes.rb b/config/routes.rb index 1b8afcd..0d009d1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,7 +9,7 @@ namespace :api do namespace :v1, defaults: {format: 'json'} do resources :filters, :armstrong, only: :index - resources :histories, only: :show + resources :histories, only: %i[index show] end end From 6a00937539cf13358e7fdec3bc3534c3e83a2ccd Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Wed, 27 Mar 2024 23:20:46 +0400 Subject: [PATCH 123/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BC=D0=B8=D0=B4=D0=B4=D0=BB=D0=B2?= =?UTF-8?q?=D0=B0=D1=80=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=80=D0=B3=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BB=D0=B8=D1=87=D0=B5=D1=81=D1=82=D0=B2=D0=B0=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=BE=D0=B2=20=D0=BD=D0=B0=20API?= =?UTF-8?q?=20(1=20=D1=80=D0=B0=D0=B7=20=D0=B2=2010=20=D1=81=D0=B5=D0=BA?= =?UTF-8?q?=D1=83=D0=BD=D0=B4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/middleware/rate_limiter.rb | 33 +++++++++++++++++++++++++++++++++ config/application.rb | 2 ++ 2 files changed, 35 insertions(+) create mode 100644 app/middleware/rate_limiter.rb diff --git a/app/middleware/rate_limiter.rb b/app/middleware/rate_limiter.rb new file mode 100644 index 0000000..d0bf812 --- /dev/null +++ b/app/middleware/rate_limiter.rb @@ -0,0 +1,33 @@ +class RateLimiter + RATE_LIMIT = 10.seconds + RATE_LIMIT_MESSAGE = { code: 429, message: 'Too many requests. Please try again later.' }.to_json + + def initialize(app) + @app = app + @request_counts = {} + end + + def call(env) + request = Rack::Request.new(env) + + if request.path.start_with?('/api') + ip_address = request.ip + + @request_counts[ip_address] ||= { count: 0, last_request_at: nil } + + if @request_counts[ip_address][:last_request_at] && + @request_counts[ip_address][:last_request_at] > Time.now - RATE_LIMIT + @request_counts[ip_address][:count] += 1 + else + @request_counts[ip_address][:count] = 1 + @request_counts[ip_address][:last_request_at] = Time.now + end + + if @request_counts[ip_address][:count] > 1 + return [429, { 'Content-Type' => 'application/json' }, [RATE_LIMIT_MESSAGE]] + end + end + + @app.call(env) + end +end diff --git a/config/application.rb b/config/application.rb index 9dc30f8..b790125 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,6 +1,7 @@ require_relative "boot" require "rails/all" +require_relative '../app/middleware/rate_limiter' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. @@ -20,5 +21,6 @@ class Application < Rails::Application # # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") + config.middleware.use RateLimiter end end From 2c65f6ec28569898dbeb75fbf3018a95db963dd2 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 28 Mar 2024 11:46:22 +0400 Subject: [PATCH 124/132] =?UTF-8?q?UPD:=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=8F=D0=B5=D1=82=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=D0=B0=20=D0=BB=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B0=20rubocop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .rubocop.yml | 362 +++++++++--------- app/controllers/admin/users_controller.rb | 7 +- .../concerns/organization_concern.rb | 4 + .../users/registrations_controller.rb | 4 + app/controllers/users/sessions_controller.rb | 5 + app/models/channel.rb | 12 +- app/models/device.rb | 1 - 7 files changed, 212 insertions(+), 183 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 256b6a9..eb0fd5f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,5 @@ AllCops: + SuggestExtensions: false Exclude: - db/**/* - config/**/* @@ -6,6 +7,9 @@ AllCops: - bin/* - vendor/**/* +Metrics/PerceivedComplexity: + Enabled: false + Bundler/OrderedGems: Enabled: false @@ -14,60 +18,60 @@ Naming/AccessorMethodName: Enabled: false Style/Alias: - Description: 'Use alias_method instead of alias.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' + Description: "Use alias_method instead of alias." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#alias-method" Enabled: false Style/ArrayJoin: - Description: 'Use Array#join instead of Array#*.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join' + Description: "Use Array#join instead of Array#*." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#array-join" Enabled: false Style/AsciiComments: - Description: 'Use only ascii symbols in comments.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' + Description: "Use only ascii symbols in comments." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#english-comments" Enabled: false Naming/AsciiIdentifiers: - Description: 'Use only ascii symbols in identifiers.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' + Description: "Use only ascii symbols in identifiers." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#english-identifiers" Enabled: false Style/Attr: - Description: 'Checks for uses of Module#attr.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' + Description: "Checks for uses of Module#attr." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#attr" Enabled: false Metrics/BlockNesting: - Description: 'Avoid excessive block nesting' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' + Description: "Avoid excessive block nesting" + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count" Enabled: false Style/CaseEquality: - Description: 'Avoid explicit use of the case equality operator(===).' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' + Description: "Avoid explicit use of the case equality operator(===)." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-case-equality" Enabled: false Style/CharacterLiteral: - Description: 'Checks for uses of character literals.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals' + Description: "Checks for uses of character literals." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-character-literals" Enabled: false Style/ClassAndModuleChildren: - Description: 'Checks style of children classes and modules.' + Description: "Checks style of children classes and modules." Enabled: false Metrics/ClassLength: - Description: 'Avoid classes longer than 100 lines of code.' + Description: "Avoid classes longer than 100 lines of code." Enabled: false Metrics/ModuleLength: - Description: 'Avoid modules longer than 100 lines of code.' + Description: "Avoid modules longer than 100 lines of code." Enabled: false Style/ClassVars: - Description: 'Avoid the use of class variables.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars' + Description: "Avoid the use of class variables." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-class-vars" Enabled: false Style/CollectionMethods: @@ -79,25 +83,25 @@ Style/CollectionMethods: find_all: select Style/ColonMethodCall: - Description: 'Do not use :: for method call.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' + Description: "Do not use :: for method call." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#double-colons" Enabled: false Style/CommentAnnotation: Description: >- - Checks formatting of special comments - (TODO, FIXME, OPTIMIZE, HACK, REVIEW). - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' + Checks formatting of special comments + (TODO, FIXME, OPTIMIZE, HACK, REVIEW). + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#annotate-keywords" Enabled: false Metrics/AbcSize: Description: >- - A calculated magnitude based on number of assignments, - branches, and conditions. + A calculated magnitude based on number of assignments, + branches, and conditions. Enabled: false Metrics/BlockLength: - CountComments: true # count full line comments? + CountComments: true # count full line comments? Max: 40 ExcludedMethods: [elastic_query_parameters, parameters] Exclude: @@ -109,31 +113,31 @@ Metrics/BlockLength: Metrics/CyclomaticComplexity: Description: >- - A complexity metric that is strongly correlated to the number - of test cases needed to validate a method. + A complexity metric that is strongly correlated to the number + of test cases needed to validate a method. Enabled: false Style/PreferredHashMethods: - Description: 'Checks use of `has_key?` and `has_value?` Hash methods.' - StyleGuide: '#hash-key' + Description: "Checks use of `has_key?` and `has_value?` Hash methods." + StyleGuide: "#hash-key" Enabled: false Style/Documentation: - Description: 'Document classes and non-namespace modules.' + Description: "Document classes and non-namespace modules." Enabled: false Style/DoubleNegation: - Description: 'Checks for uses of double negation (!!).' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang' + Description: "Checks for uses of double negation (!!)." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-bang-bang" Enabled: false Style/EachWithObject: - Description: 'Prefer `each_with_object` over `inject` or `reduce`.' + Description: "Prefer `each_with_object` over `inject` or `reduce`." Enabled: false Style/EmptyLiteral: - Description: 'Prefer literals to Array.new/Hash.new/String.new.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' + Description: "Prefer literals to Array.new/Hash.new/String.new." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#literal-array-hash" Enabled: false # Checks whether the source file has a utf-8 encoding comment or not @@ -143,13 +147,13 @@ Style/Encoding: Enabled: false Style/EvenOdd: - Description: 'Favor the use of Fixnum#even? && Fixnum#odd?' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' + Description: "Favor the use of Fixnum#even? && Fixnum#odd?" + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#predicate-methods" Enabled: false Naming/FileName: - Description: 'Use snake_case for source file names.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' + Description: "Use snake_case for source file names." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#snake-case-files" Enabled: false Style/FrozenStringLiteralComment: @@ -159,192 +163,192 @@ Style/FrozenStringLiteralComment: Enabled: false Lint/FlipFlop: - Description: 'Checks for flip flops' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' + Description: "Checks for flip flops" + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-flip-flops" Enabled: false Style/FormatString: - Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' + Description: "Enforce the use of Kernel#sprintf, Kernel#format or String#%." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#sprintf" Enabled: false Style/GlobalVars: - Description: 'Do not introduce global variables.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars' - Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html' + Description: "Do not introduce global variables." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#instance-vars" + Reference: "http://www.zenspider.com/Languages/Ruby/QuickRef.html" Enabled: false Style/GuardClause: - Description: 'Check for conditionals that can be replaced with guard clauses' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' + Description: "Check for conditionals that can be replaced with guard clauses" + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals" Enabled: false Style/IfUnlessModifier: Description: >- - Favor modifier if/unless usage when you have a - single-line body. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier' + Favor modifier if/unless usage when you have a + single-line body. + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier" Enabled: false Style/IfWithSemicolon: - Description: 'Do not use if x; .... Use the ternary operator instead.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs' + Description: "Do not use if x; .... Use the ternary operator instead." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs" Enabled: false Style/InlineComment: - Description: 'Avoid inline comments.' + Description: "Avoid inline comments." Enabled: false Style/Lambda: - Description: 'Use the new lambda literal syntax for single-line blocks.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line' + Description: "Use the new lambda literal syntax for single-line blocks." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#lambda-multi-line" Enabled: false Style/LambdaCall: - Description: 'Use lambda.call(...) instead of lambda.(...).' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call' + Description: "Use lambda.call(...) instead of lambda.(...)." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#proc-call" Enabled: false Style/LineEndConcatenation: Description: >- - Use \ instead of + or << to concatenate two string literals at - line end. + Use \ instead of + or << to concatenate two string literals at + line end. Enabled: false Layout/LineLength: - Description: 'Limit lines to 80 characters.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' + Description: "Limit lines to 80 characters." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#80-character-limits" Max: 140 Metrics/MethodLength: - Description: 'Avoid methods longer than 10 lines of code.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' + Description: "Avoid methods longer than 10 lines of code." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#short-methods" Enabled: false Style/ModuleFunction: - Description: 'Checks for usage of `extend self` in modules.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function' + Description: "Checks for usage of `extend self` in modules." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#module-function" Enabled: false Style/MultilineBlockChain: - Description: 'Avoid multi-line chains of blocks.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' + Description: "Avoid multi-line chains of blocks." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#single-line-blocks" Enabled: false Style/NegatedIf: Description: >- - Favor unless over if for negative conditions - (or control flow or). - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives' + Favor unless over if for negative conditions + (or control flow or). + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#unless-for-negatives" Enabled: false Style/NegatedWhile: - Description: 'Favor until over while for negative conditions.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives' + Description: "Favor until over while for negative conditions." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#until-for-negatives" Enabled: false Style/Next: - Description: 'Use `next` to skip iteration instead of a condition at the end.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' + Description: "Use `next` to skip iteration instead of a condition at the end." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals" Enabled: false Style/NilComparison: - Description: 'Prefer x.nil? to x == nil.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' + Description: "Prefer x.nil? to x == nil." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#predicate-methods" Enabled: false Style/Not: - Description: 'Use ! instead of not.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not' + Description: "Use ! instead of not." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#bang-not-not" Enabled: false Style/NumericLiterals: Description: >- - Add underscores to large numeric literals to improve their - readability. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' + Add underscores to large numeric literals to improve their + readability. + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics" Enabled: false Style/OneLineConditional: Description: >- - Favor the ternary operator(?:) over - if/then/else/end constructs. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' + Favor the ternary operator(?:) over + if/then/else/end constructs. + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#ternary-operator" Enabled: false Naming/BinaryOperatorParameterName: - Description: 'When defining binary operators, name the argument other.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' + Description: "When defining binary operators, name the argument other." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#other-arg" Enabled: false Metrics/ParameterLists: - Description: 'Avoid parameter lists longer than three or four parameters.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' + Description: "Avoid parameter lists longer than three or four parameters." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#too-many-params" Enabled: false Style/PercentLiteralDelimiters: - Description: 'Use `%`-literal delimiters consistently' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' + Description: "Use `%`-literal delimiters consistently" + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#percent-literal-braces" Enabled: false Style/PerlBackrefs: - Description: 'Avoid Perl-style regex back references.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' + Description: "Avoid Perl-style regex back references." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers" Enabled: false Naming/PredicateName: - Description: 'Check the names of predicate methods.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' + Description: "Check the names of predicate methods." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark" ForbiddenPrefixes: - is_ Exclude: - spec/**/* Style/Proc: - Description: 'Use proc instead of Proc.new.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc' + Description: "Use proc instead of Proc.new." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#proc" Enabled: false Style/RaiseArgs: - Description: 'Checks the arguments passed to raise/fail.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages' + Description: "Checks the arguments passed to raise/fail." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#exception-class-messages" Enabled: false Style/RegexpLiteral: - Description: 'Use / or %r around regular expressions.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' + Description: "Use / or %r around regular expressions." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#percent-r" Enabled: false Style/SelfAssignment: Description: >- - Checks for places where self-assignment shorthand should have - been used. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment' + Checks for places where self-assignment shorthand should have + been used. + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#self-assignment" Enabled: false Style/SingleLineBlockParams: - Description: 'Enforces the names of some block params.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks' + Description: "Enforces the names of some block params." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#reduce-blocks" Enabled: false Style/SingleLineMethods: - Description: 'Avoid single-line methods.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods' + Description: "Avoid single-line methods." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-single-line-methods" Enabled: false Style/SignalException: - Description: 'Checks for proper usage of fail and raise.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method' + Description: "Checks for proper usage of fail and raise." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#fail-method" Enabled: false Style/SpecialGlobalVars: - Description: 'Avoid Perl-style global variables.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms' + Description: "Avoid Perl-style global variables." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms" Enabled: false Style/StringLiterals: - Description: 'Checks if uses of quotes match the configured preference.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals' + Description: "Checks if uses of quotes match the configured preference." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#consistent-string-literals" EnforcedStyle: single_quotes Enabled: true @@ -352,8 +356,8 @@ Style/SymbolArray: EnforcedStyle: brackets Style/TrailingCommaInArguments: - Description: 'Checks for trailing comma in argument lists.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' + Description: "Checks for trailing comma in argument lists." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas" EnforcedStyleForMultiline: comma SupportedStylesForMultiline: - comma @@ -362,8 +366,8 @@ Style/TrailingCommaInArguments: Enabled: true Style/TrailingCommaInArrayLiteral: - Description: 'Checks for trailing comma in array literals.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' + Description: "Checks for trailing comma in array literals." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas" EnforcedStyleForMultiline: comma SupportedStylesForMultiline: - comma @@ -372,8 +376,8 @@ Style/TrailingCommaInArrayLiteral: Enabled: true Style/TrailingCommaInHashLiteral: - Description: 'Checks for trailing comma in hash literals.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' + Description: "Checks for trailing comma in hash literals." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas" EnforcedStyleForMultiline: comma SupportedStylesForMultiline: - comma @@ -382,36 +386,36 @@ Style/TrailingCommaInHashLiteral: Enabled: true Style/TrivialAccessors: - Description: 'Prefer attr_* methods to trivial readers/writers.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family' + Description: "Prefer attr_* methods to trivial readers/writers." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#attr_family" Enabled: false Style/VariableInterpolation: Description: >- - Don't interpolate global, instance and class variables - directly in strings. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate' + Don't interpolate global, instance and class variables + directly in strings. + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#curlies-interpolate" Enabled: false Style/WhenThen: - Description: 'Use when x then ... for one-line cases.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases' + Description: "Use when x then ... for one-line cases." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#one-line-cases" Enabled: false Style/WhileUntilModifier: Description: >- - Favor modifier while/until usage when you have a - single-line body. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' + Favor modifier while/until usage when you have a + single-line body. + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier" Enabled: false Style/WordArray: - Description: 'Use %w or %W for arrays of words.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' + Description: "Use %w or %W for arrays of words." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#percent-w" EnforcedStyle: brackets Style/MethodCallWithArgsParentheses: - Description: 'Use parentheses for method calls with arguments.' + Description: "Use parentheses for method calls with arguments." Enabled: true IgnoredMethods: - require @@ -426,37 +430,37 @@ Style/MethodCallWithArgsParentheses: # Layout Layout/ParameterAlignment: - Description: 'Here we check if the parameters on a multi-line method call or definition are aligned.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' + Description: "Here we check if the parameters on a multi-line method call or definition are aligned." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-double-indent" Enabled: false Layout/ConditionPosition: Description: >- - Checks for condition placed in a confusing position relative to - the keyword. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' + Checks for condition placed in a confusing position relative to + the keyword. + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#same-line-condition" Enabled: false Layout/DotPosition: - Description: 'Checks the position of the dot in multi-line method calls.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' + Description: "Checks the position of the dot in multi-line method calls." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains" EnforcedStyle: trailing Layout/ExtraSpacing: - Description: 'Do not use unnecessary spacing.' + Description: "Do not use unnecessary spacing." Enabled: true Layout/MultilineOperationIndentation: Description: >- - Checks indentation of binary operations that span more than - one line. + Checks indentation of binary operations that span more than + one line. Enabled: true EnforcedStyle: indented Layout/MultilineMethodCallIndentation: Description: >- - Checks indentation of method calls with the dot operator - that span more than one line. + Checks indentation of method calls with the dot operator + that span more than one line. Enabled: true EnforcedStyle: indented @@ -469,20 +473,20 @@ Layout/InitialIndentation: Lint/AmbiguousOperator: Description: >- - Checks for ambiguous operators in the first argument of a - method invocation without parentheses. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args' + Checks for ambiguous operators in the first argument of a + method invocation without parentheses. + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#parens-as-args" Enabled: false Lint/AmbiguousRegexpLiteral: Description: >- - Checks for ambiguous regexp literals in the first argument of - a method invocation without parenthesis. + Checks for ambiguous regexp literals in the first argument of + a method invocation without parenthesis. Enabled: false Lint/AssignmentInCondition: Description: "Don't use assignment in conditions." - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition' + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition" Enabled: false Lint/CircularArgumentReference: @@ -490,78 +494,78 @@ Lint/CircularArgumentReference: Enabled: false Lint/DeprecatedClassMethods: - Description: 'Check for deprecated class method calls.' + Description: "Check for deprecated class method calls." Enabled: false Lint/DuplicateHashKey: - Description: 'Check for duplicate keys in hash literals.' + Description: "Check for duplicate keys in hash literals." Enabled: false Lint/EachWithObjectArgument: - Description: 'Check for immutable argument given to each_with_object.' + Description: "Check for immutable argument given to each_with_object." Enabled: false Lint/ElseLayout: - Description: 'Check for odd code arrangement in an else block.' + Description: "Check for odd code arrangement in an else block." Enabled: false Lint/FormatParameterMismatch: - Description: 'The number of parameters to format/sprint must match the fields.' + Description: "The number of parameters to format/sprint must match the fields." Enabled: false Lint/SuppressedException: Description: "Don't suppress exception." - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions" Enabled: false Lint/LiteralAsCondition: - Description: 'Checks of literals used in conditions.' + Description: "Checks of literals used in conditions." Enabled: false Lint/LiteralInInterpolation: - Description: 'Checks for literals used in interpolation.' + Description: "Checks for literals used in interpolation." Enabled: false Lint/Loop: Description: >- - Use Kernel#loop with break rather than begin/end/until or - begin/end/while for post-loop tests. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break' + Use Kernel#loop with break rather than begin/end/until or + begin/end/while for post-loop tests. + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#loop-with-break" Enabled: false Lint/NestedMethodDefinition: - Description: 'Do not use nested method definitions.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods' + Description: "Do not use nested method definitions." + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#no-nested-methods" Enabled: false Lint/NonLocalExitFromIterator: - Description: 'Do not use return in iterator to cause non-local exit.' + Description: "Do not use return in iterator to cause non-local exit." Enabled: false Lint/ParenthesesAsGroupedExpression: Description: >- - Checks for method calls with a space before the opening - parenthesis. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' + Checks for method calls with a space before the opening + parenthesis. + StyleGuide: "https://github.com/bbatsov/ruby-style-guide#parens-no-spaces" Enabled: false Lint/RequireParentheses: Description: >- - Use parentheses in the method call to avoid confusion - about precedence. + Use parentheses in the method call to avoid confusion + about precedence. Enabled: false Lint/UnderscorePrefixedVariableName: - Description: 'Do not use prefix `_` for a variable that is used.' + Description: "Do not use prefix `_` for a variable that is used." Enabled: false Lint/RedundantCopDisableDirective: Description: >- - Checks for rubocop:disable comments that can be removed. - Note: this cop is not disabled when disabling all cops. - It must be explicitly disabled. + Checks for rubocop:disable comments that can be removed. + Note: this cop is not disabled when disabling all cops. + It must be explicitly disabled. Enabled: false Lint/Void: - Description: 'Possible use of operator/literal/variable in void context.' + Description: "Possible use of operator/literal/variable in void context." Enabled: false diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index c49f9f0..b554fd3 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -59,7 +59,12 @@ def create def destroy user = User.find(params[:id]) - assigned_inspections_count = Inspection.where(creator_id: params[:id]).or(Inspection.where(performer_id: params[:id])).count + user.posts.count + assigned_inspections_count = + Inspection. + where(creator_id: params[:id]). + or(Inspection.where(performer_id: params[:id])). + count + user.posts. + count if assigned_inspections_count.zero? @user.destroy diff --git a/app/controllers/concerns/organization_concern.rb b/app/controllers/concerns/organization_concern.rb index 6abbc74..99f9da0 100644 --- a/app/controllers/concerns/organization_concern.rb +++ b/app/controllers/concerns/organization_concern.rb @@ -1,6 +1,8 @@ module OrganizationConcern extend ActiveSupport::Concern + # rubocop:disable Metrics/BlockLength + included do def organization_index @query = Organization.ransack(params[:q]) @@ -57,4 +59,6 @@ def organization_params ) end end + + # rubocop:enable Metrics/BlockLength end diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index 608f5f0..bb652a4 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -4,6 +4,8 @@ class Users::RegistrationsController < Devise::RegistrationsController before_action :configure_sign_up_params, only: [:create] before_action :configure_account_update_params, only: [:update] + # rubocop:disable Lint/UselessMethodDefinition + # GET /resource/sign_up def new super @@ -59,4 +61,6 @@ def destroy # def after_inactive_sign_up_path_for(resource) # super(resource) # end + + # rubocop:enable Lint/UselessMethodDefinition end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 8a5fc3d..cd3cc69 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -3,6 +3,9 @@ class Users::SessionsController < Devise::SessionsController # before_action :configure_sign_in_params, only: [:create] skip_before_action :verify_authenticity_token, only: [:create] + + # rubocop:disable Lint/UselessMethodDefinition + # GET /resource/sign_in def new super @@ -28,4 +31,6 @@ def destroy def configure_sign_in_params devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute]) end + + # rubocop:enable Lint/UselessMethodDefinition end diff --git a/app/models/channel.rb b/app/models/channel.rb index 54477c8..06fff9f 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -14,7 +14,15 @@ def self.ransackable_associations(_auth_object = nil) end def self.ransackable_attributes(_auth_object = nil) - ['channel_id', 'consumption', 'control_point_id', 'conversion_coefficient', 'created_at', 'emergency_limit', 'event_count', - 'event_datetime', 'event_error_count', 'event_impulse_value', 'event_not_system_value', 'event_system_value', 'id', 'is_online', 'is_special_control', 'location_description', 'pre_emergency_limit', 'self_background', 'server_id', 'service_id', 'state', 'updated_at'] + [ + 'channel_id', 'consumption', 'control_point_id', + 'conversion_coefficient', 'created_at', + 'emergency_limit', 'event_count', 'event_datetime', + 'event_error_count', 'event_impulse_value', 'event_not_system_value', + 'event_system_value', 'id', 'is_online', + 'is_special_control', 'location_description', 'pre_emergency_limit', + 'self_background', 'server_id', 'service_id', + 'state', 'updated_at' + ] end end diff --git a/app/models/device.rb b/app/models/device.rb index cf19910..e056e9f 100644 --- a/app/models/device.rb +++ b/app/models/device.rb @@ -5,7 +5,6 @@ class Device < ApplicationRecord current_year = Date.today.year year_error_msg = "должен быть больше 1900 и меньше или равен #{current_year}" - inspection_interval_msg = 'должен быть от 0.1 (раз в месяц) до 10.0 (раз в 10 лет)' belongs_to :device_model belongs_to :device_reg_group From b6e66c8bc38777b8cf0c017356682eb2b1a985d0 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 28 Mar 2024 11:47:03 +0400 Subject: [PATCH 125/132] =?UTF-8?q?ADD:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BB=D0=B8=D0=BC=D0=B8=D1=82=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=20=D0=B2?= =?UTF-8?q?=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BE?= =?UTF-8?q?=D0=BA=D0=BD=D0=B0=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=BE=20=D0=B2=D1=81=D0=B5=D0=B9=20=D1=82=D0=B0=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D1=86=D0=B5=20History?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/histories_controller.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/histories_controller.rb b/app/controllers/api/v1/histories_controller.rb index 4a4fc69..478eb49 100644 --- a/app/controllers/api/v1/histories_controller.rb +++ b/app/controllers/api/v1/histories_controller.rb @@ -3,10 +3,15 @@ class V1::HistoriesController < ApplicationController before_action :set_channel, only: [:show] def index - histories = if params[:start_datetime].present? && params[:end_datetime].present? + datetime_interval = diff_hours(params[:start_datetime], params[:end_datetime]) + histories = if params[:start_datetime].present? && params[:end_datetime].present? && datetime_interval < 2 History.where(event_datetime: params[:start_datetime]..params[:end_datetime]) else - [] + { + code: 429, + message: 'Parameters is not a valid or range of the datetime is too long.', + hint: 'The interval between start_datetime and end_datetime should be no more than two hours.', + }.to_json end render(json: histories, except: [:id, :event_impulse_value, :event_not_system_value, :created_at, :updated_at]) @@ -31,5 +36,12 @@ def set_channel rescue ActiveRecord::RecordNotFound => e render(json: { error: e }, status: :not_found) end + + # Returns the integer value of the difference + # between the start and end datetime values + def diff_hours(start_date, end_date) + diff_in_seconds = end_date.to_time - start_date.to_time + (diff_in_seconds / (60**2)).to_i + end end end From b6626807519fa6c186f6ce95bb802022fd08e864 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 28 Mar 2024 11:55:31 +0400 Subject: [PATCH 126/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BD=D0=B0=20=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BD=D1=8B=D0=B9=20HTTP=20=D0=BA=D0=BE=D0=B4=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B2=D0=B5=D1=82=20=D0=BF=D1=80=D0=B8=20=D0=BD=D0=B5?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D1=8C=D0=BD=D0=BE=D0=BC=20?= =?UTF-8?q?=D0=BE=D0=BA=D0=BD=D0=B5=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/histories_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/histories_controller.rb b/app/controllers/api/v1/histories_controller.rb index 478eb49..ef355f5 100644 --- a/app/controllers/api/v1/histories_controller.rb +++ b/app/controllers/api/v1/histories_controller.rb @@ -8,8 +8,8 @@ def index History.where(event_datetime: params[:start_datetime]..params[:end_datetime]) else { - code: 429, - message: 'Parameters is not a valid or range of the datetime is too long.', + code: 403, + message: 'Forbidden. Parameters is not a valid or range of the datetime is too long.', hint: 'The interval between start_datetime and end_datetime should be no more than two hours.', }.to_json end From a841c88766edfb24d81d35c5bb17adf2e4b29df1 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Thu, 28 Mar 2024 12:07:08 +0400 Subject: [PATCH 127/132] =?UTF-8?q?FIX:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D1=83=20=D0=BF=D1=80=D0=B8=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D0=B5=20=D0=BD=D0=B0=20History=20=D0=B1=D0=B5=D0=B7=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/api/v1/histories_controller.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/histories_controller.rb b/app/controllers/api/v1/histories_controller.rb index ef355f5..9271b77 100644 --- a/app/controllers/api/v1/histories_controller.rb +++ b/app/controllers/api/v1/histories_controller.rb @@ -40,8 +40,10 @@ def set_channel # Returns the integer value of the difference # between the start and end datetime values def diff_hours(start_date, end_date) - diff_in_seconds = end_date.to_time - start_date.to_time - (diff_in_seconds / (60**2)).to_i + if start_date && end_date + diff_in_seconds = end_date.to_time - start_date.to_time + (diff_in_seconds / (60**2)).to_i + end end end end From b995c956d07bf5c8bf0bfbd9bf46d6f19605212d Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 4 Jun 2024 12:58:22 +0400 Subject: [PATCH 128/132] =?UTF-8?q?UPD:=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BF=D0=BE=D1=81=D1=82=D1=8B,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B8=20=D0=BB=D0=B0=D0=B9=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .rubocop.yml | 5 +- .slim-lint.yml | 9 ++++ Gemfile | 5 ++ Gemfile.lock | 18 +++++++ Makefile | 9 ++++ app/controllers/application_controller.rb | 4 +- app/controllers/inspection_controller.rb | 8 ++-- .../posts/application_controller.rb | 9 ++++ app/controllers/posts/comments_controller.rb | 24 ++++++++++ app/controllers/posts/likes_controller.rb | 24 ++++++++++ ...post_controller.rb => posts_controller.rb} | 17 ++++--- app/helpers/posts/comments_helper.rb | 2 + app/helpers/posts/likes_helper.rb | 2 + .../rate_limiter.rb | 0 app/middlewares/set_locale_middleware.rb | 26 ++++++++++ app/models/concerns/export_pdf.rb | 4 +- app/models/post.rb | 7 +++ app/models/post_comment.rb | 10 ++++ app/models/post_like.rb | 4 ++ app/views/post/_form.html.erb | 12 ----- app/views/post/edit.html.erb | 1 - app/views/post/index.html.erb | 33 ------------- app/views/post/new.html.erb | 1 - app/views/post/show.html.erb | 28 ----------- .../posts/comments/_child_comment.html.slim | 15 ++++++ .../posts/comments/_comment_block.html.slim | 14 ++++++ .../posts/comments/_comment_form.html.slim | 5 ++ .../posts/comments/_replay_form.html.slim | 4 ++ app/views/posts/edit.html.slim | 1 + app/views/posts/index.html.slim | 15 ++++++ app/views/posts/likes/_like_button.html.slim | 11 +++++ app/views/posts/new.html.slim | 1 + app/views/posts/shared/_form.html.slim | 9 ++++ app/views/posts/shared/_post_footer.html.slim | 17 +++++++ app/views/posts/shared/_post_header.html.slim | 11 +++++ app/views/posts/shared/_posts.html.slim | 10 ++++ app/views/posts/show.html.slim | 19 ++++++++ app/views/shared/index/_device.html.erb | 2 +- config/application.rb | 16 ++----- config/locales/en.yml | 29 ++++++++---- config/locales/ru.yml | 22 ++++++--- config/routes.rb | 10 +++- .../20240604043443_create_post_comments.rb | 10 ++++ .../20240604043539_create_post_likes.rb | 10 ++++ ...0604044035_add_ancestry_to_post_comment.rb | 6 +++ .../20240604044212_add_likes_count_to_post.rb | 5 ++ ...240604044252_add_comments_count_to_post.rb | 5 ++ ...54207_add_content_field_to_post_comment.rb | 5 ++ db/schema.rb | 29 +++++++++++- db/seeds.rb | 47 ++++++++++++++++--- .../posts/comments_controller_test.rb | 7 +++ .../posts/likes_controller_test.rb | 7 +++ ...oller_test.rb => posts_controller_test.rb} | 2 +- test/factories/post_comments.rb | 6 +++ test/factories/post_likes.rb | 6 +++ test/factories/posts.rb | 2 +- test/models/post_comment_test.rb | 7 +++ test/models/post_like_test.rb | 7 +++ 58 files changed, 504 insertions(+), 130 deletions(-) create mode 100644 .slim-lint.yml create mode 100644 app/controllers/posts/application_controller.rb create mode 100644 app/controllers/posts/comments_controller.rb create mode 100644 app/controllers/posts/likes_controller.rb rename app/controllers/{post_controller.rb => posts_controller.rb} (69%) create mode 100644 app/helpers/posts/comments_helper.rb create mode 100644 app/helpers/posts/likes_helper.rb rename app/{middleware => middlewares}/rate_limiter.rb (100%) create mode 100644 app/middlewares/set_locale_middleware.rb create mode 100644 app/models/post_comment.rb create mode 100644 app/models/post_like.rb delete mode 100644 app/views/post/_form.html.erb delete mode 100644 app/views/post/edit.html.erb delete mode 100644 app/views/post/index.html.erb delete mode 100644 app/views/post/new.html.erb delete mode 100644 app/views/post/show.html.erb create mode 100644 app/views/posts/comments/_child_comment.html.slim create mode 100644 app/views/posts/comments/_comment_block.html.slim create mode 100644 app/views/posts/comments/_comment_form.html.slim create mode 100644 app/views/posts/comments/_replay_form.html.slim create mode 100644 app/views/posts/edit.html.slim create mode 100644 app/views/posts/index.html.slim create mode 100644 app/views/posts/likes/_like_button.html.slim create mode 100644 app/views/posts/new.html.slim create mode 100644 app/views/posts/shared/_form.html.slim create mode 100644 app/views/posts/shared/_post_footer.html.slim create mode 100644 app/views/posts/shared/_post_header.html.slim create mode 100644 app/views/posts/shared/_posts.html.slim create mode 100644 app/views/posts/show.html.slim create mode 100644 db/migrate/20240604043443_create_post_comments.rb create mode 100644 db/migrate/20240604043539_create_post_likes.rb create mode 100644 db/migrate/20240604044035_add_ancestry_to_post_comment.rb create mode 100644 db/migrate/20240604044212_add_likes_count_to_post.rb create mode 100644 db/migrate/20240604044252_add_comments_count_to_post.rb create mode 100644 db/migrate/20240604054207_add_content_field_to_post_comment.rb create mode 100644 test/controllers/posts/comments_controller_test.rb create mode 100644 test/controllers/posts/likes_controller_test.rb rename test/controllers/{post_controller_test.rb => posts_controller_test.rb} (95%) create mode 100644 test/factories/post_comments.rb create mode 100644 test/factories/post_likes.rb create mode 100644 test/models/post_comment_test.rb create mode 100644 test/models/post_like_test.rb diff --git a/.rubocop.yml b/.rubocop.yml index eb0fd5f..09544de 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -6,6 +6,8 @@ AllCops: - node_modules/**/* - bin/* - vendor/**/* + NewCops: enable + TargetRubyVersion: "3.2" Metrics/PerceivedComplexity: Enabled: false @@ -103,7 +105,6 @@ Metrics/AbcSize: Metrics/BlockLength: CountComments: true # count full line comments? Max: 40 - ExcludedMethods: [elastic_query_parameters, parameters] Exclude: - "spec/**/*" - "db/migrate/*" @@ -417,7 +418,7 @@ Style/WordArray: Style/MethodCallWithArgsParentheses: Description: "Use parentheses for method calls with arguments." Enabled: true - IgnoredMethods: + IgnoreMacros: - require - require_relative - raise diff --git a/.slim-lint.yml b/.slim-lint.yml new file mode 100644 index 0000000..513061c --- /dev/null +++ b/.slim-lint.yml @@ -0,0 +1,9 @@ +AllCops: + NewCops: enable + TargetRubyVersion: "3.2" + +linters: + LineLength: + max: 120 + RedundantDiv: + enabled: false diff --git a/Gemfile b/Gemfile index 4d16162..6fceb51 100644 --- a/Gemfile +++ b/Gemfile @@ -56,6 +56,7 @@ gem 'sidekiq', '~> 7.1', '>= 7.1.1' gem 'sidekiq-cron', '~> 1.10', '>= 1.10.1' gem 'sidekiq-failures', '~> 1.0', '>= 1.0.4' +gem 'ancestry', '~> 4.3', '>= 4.3.3' gem 'pagy', '~> 6.0' gem 'ransack', '~> 4.0' gem 'responders', '~> 3.1' @@ -66,6 +67,10 @@ gem 'prawn-table', '~> 0.2.2' gem 'devise', '~> 4.2' gem 'cancancan', '~> 3.5' gem 'tzinfo' +gem 'slim', '~> 5.2', '>= 5.2.1' +gem 'slim_lint', '~> 0.27.0' +gem 'slim-rails', '~> 3.6', '>= 3.6.3' + group :development, :test do gem 'debug', platforms: [:mri, :mingw, :x64_mingw] diff --git a/Gemfile.lock b/Gemfile.lock index e05b6fb..cc1d902 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -73,6 +73,8 @@ GEM tzinfo (~> 2.0) addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) + ancestry (4.3.3) + activerecord (>= 5.2.6) ast (2.4.2) bcrypt (3.1.18) bindex (0.8.1) @@ -258,6 +260,16 @@ GEM simple_form (5.2.0) actionpack (>= 5.2) activemodel (>= 5.2) + slim (5.2.1) + temple (~> 0.10.0) + tilt (>= 2.1.0) + slim-rails (3.6.3) + actionpack (>= 3.1) + railties (>= 3.1) + slim (>= 3.0, < 6.0, != 5.0.0) + slim_lint (0.27.0) + rubocop (>= 1.0, < 2.0) + slim (>= 3.0, < 6.0) sprockets (4.2.0) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) @@ -274,7 +286,9 @@ GEM state_machines-activemodel (>= 0.8.0) stimulus-rails (1.2.1) railties (>= 6.0.0) + temple (0.10.3) thor (1.2.1) + tilt (2.3.0) timeout (0.3.1) ttfunk (1.7.0) turbo-rails (1.3.3) @@ -309,6 +323,7 @@ PLATFORMS DEPENDENCIES active_model_serializers (~> 0.10.13) + ancestry (~> 4.3, >= 4.3.3) bcrypt (~> 3.1, >= 3.1.18) bootsnap cancancan (~> 3.5) @@ -336,6 +351,9 @@ DEPENDENCIES sidekiq-cron (~> 1.10, >= 1.10.1) sidekiq-failures (~> 1.0, >= 1.0.4) simple_form (~> 5.2) + slim (~> 5.2, >= 5.2.1) + slim-rails (~> 3.6, >= 3.6.3) + slim_lint (~> 0.27.0) sprockets-rails state_machines (~> 0.5.0) state_machines-activerecord (~> 0.8.0) diff --git a/Makefile b/Makefile index 8225958..3d45e89 100644 --- a/Makefile +++ b/Makefile @@ -16,5 +16,14 @@ start: clean: bin/rails db:drop db:create db:migrate db:seed + +check: test lint + +lint: + bundle exec rubocop -a +# bundle exec slim-lint app/views/ + test: bin/rails test + +.PHONY: test diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b0cb009..b6ea9cc 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -15,13 +15,13 @@ def current_ability private - def set_time_zone(&block) + def set_time_zone(&) timezone = if current_user.nil? 'UTC' else current_user.timezone end - Time.use_zone(timezone, &block) + Time.use_zone(timezone, &) end protected diff --git a/app/controllers/inspection_controller.rb b/app/controllers/inspection_controller.rb index 86f6300..4d88120 100644 --- a/app/controllers/inspection_controller.rb +++ b/app/controllers/inspection_controller.rb @@ -16,12 +16,12 @@ def tasks(condition, method_name) @query = Inspection.ransack(params[:q]) @query.sorts = ['updated_at desc'] @previous_action = method_name - if method_name != :service_tasks - @pagy, @inspections = pagy(@query.result.where(condition). - includes(:device, :creator, :performer)) - else + if method_name == :service_tasks @pagy, @inspections = pagy(@query.result.joins(:device).where(condition). includes(:device, :creator, :performer)) + else + @pagy, @inspections = pagy(@query.result.where(condition). + includes(:device, :creator, :performer)) end end diff --git a/app/controllers/posts/application_controller.rb b/app/controllers/posts/application_controller.rb new file mode 100644 index 0000000..1bf1408 --- /dev/null +++ b/app/controllers/posts/application_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Posts + class ApplicationController < ApplicationController + def resource_post + @resource_post ||= Post.find(params[:post_id]) + end + end +end diff --git a/app/controllers/posts/comments_controller.rb b/app/controllers/posts/comments_controller.rb new file mode 100644 index 0000000..3baa8d6 --- /dev/null +++ b/app/controllers/posts/comments_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Posts + class CommentsController < ApplicationController + before_action :authenticate_user!, only: [:create] + + def create + @comment = resource_post.comments.build(comments_params) + @comment.user = current_user + + if @comment.save + redirect_to(resource_post, notice: t('.success')) + else + redirect_to(resource_post, alert: @comment.errors.full_messages.join('. ')) + end + end + + private + + def comments_params + params.require(:post_comment).permit(:content, :parent_id) + end + end +end diff --git a/app/controllers/posts/likes_controller.rb b/app/controllers/posts/likes_controller.rb new file mode 100644 index 0000000..3f65abe --- /dev/null +++ b/app/controllers/posts/likes_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Posts + class LikesController < ApplicationController + before_action :authenticate_user! + before_action :set_like, only: [:destroy] + + def create + resource_post.likes.find_or_create_by(user: current_user) + redirect_to(resource_post) + end + + def destroy + @like&.destroy + redirect_to(resource_post) + end + + private + + def set_like + @like = resource_post.likes.find_by(user: current_user) + end + end +end diff --git a/app/controllers/post_controller.rb b/app/controllers/posts_controller.rb similarity index 69% rename from app/controllers/post_controller.rb rename to app/controllers/posts_controller.rb index 16d1abf..7140242 100644 --- a/app/controllers/post_controller.rb +++ b/app/controllers/posts_controller.rb @@ -1,12 +1,17 @@ -class PostController < ApplicationController +class PostsController < ApplicationController before_action :authenticate_user! before_action :set_post, only: [:show, :edit, :update, :destroy] load_and_authorize_resource def index - @query = Post.ransack(params[:q]) - @query.sorts = ['updated_at desc'] - @pagy, @posts = pagy(@query.result) + @pagy, @posts = pagy(Post.includes(:user).all) + end + + def show + @post = Post.find(params[:id]) + @post_comments = @post.comments.includes(:user) + @user_likes = @post.likes.find_by(user_id: current_user.id) if current_user + @comment = PostComment.new end def new @@ -23,7 +28,7 @@ def create if @post.save redirect_to(post_path(@post.id)) else - render(:new) + render(:new, status: :unprocessable_entity) end end @@ -37,7 +42,7 @@ def update def destroy @post.destroy - redirect_to(home_index_path) + redirect_to(root_path) end private diff --git a/app/helpers/posts/comments_helper.rb b/app/helpers/posts/comments_helper.rb new file mode 100644 index 0000000..7775235 --- /dev/null +++ b/app/helpers/posts/comments_helper.rb @@ -0,0 +1,2 @@ +module Posts::CommentsHelper +end diff --git a/app/helpers/posts/likes_helper.rb b/app/helpers/posts/likes_helper.rb new file mode 100644 index 0000000..6e294d2 --- /dev/null +++ b/app/helpers/posts/likes_helper.rb @@ -0,0 +1,2 @@ +module Posts::LikesHelper +end diff --git a/app/middleware/rate_limiter.rb b/app/middlewares/rate_limiter.rb similarity index 100% rename from app/middleware/rate_limiter.rb rename to app/middlewares/rate_limiter.rb diff --git a/app/middlewares/set_locale_middleware.rb b/app/middlewares/set_locale_middleware.rb new file mode 100644 index 0000000..97b55eb --- /dev/null +++ b/app/middlewares/set_locale_middleware.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class SetLocaleMiddleware + def initialize(app) + @app = app + end + + # BEGIN + def call(env) + request = Rack::Request.new(env) + + locale = extract_locale(request) || I18n.default_locale + I18n.locale = locale.downcase.to_sym + + status, headers, response = @app.call(env) + headers['Content-Language'] = locale + + [status, headers, response] + end + + def extract_locale(request) + locale = request.env['HTTP_ACCEPT_LANGUAGE']&.scan(/^[a-z]{2}/)&.first + + I18n.available_locales.map(&:to_s).include?(locale) ? locale : nil + end +end diff --git a/app/models/concerns/export_pdf.rb b/app/models/concerns/export_pdf.rb index d062225..d7d3d28 100644 --- a/app/models/concerns/export_pdf.rb +++ b/app/models/concerns/export_pdf.rb @@ -5,8 +5,8 @@ module ExportPdf module ClassMethods def to_pdf - require 'prawn' - require 'prawn/table' + require('prawn') + require('prawn/table') options = { position: :center, column_widths: [100, 100, 200, 100, 100], width: 600 } diff --git a/app/models/post.rb b/app/models/post.rb index 87e190e..0b9a20f 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -1,7 +1,14 @@ class Post < ApplicationRecord belongs_to :user + has_many :comments, class_name: 'PostComment', dependent: :destroy + has_many :likes, class_name: 'PostLike', dependent: :destroy + validates :user, presence: true + validates :title, presence: true, length: { minimum: 5, maximum: 255 } + validates :body, presence: true, length: { minimum: 200, maximum: 4000 } + + scope :latest, -> { order(created_at: :desc) } def formatted_date(param) I18n.l(param, format: :long) diff --git a/app/models/post_comment.rb b/app/models/post_comment.rb new file mode 100644 index 0000000..72ae0ed --- /dev/null +++ b/app/models/post_comment.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class PostComment < ApplicationRecord + belongs_to :post, counter_cache: :comments_count + belongs_to :user + + has_ancestry + + validates :content, presence: true +end diff --git a/app/models/post_like.rb b/app/models/post_like.rb new file mode 100644 index 0000000..ec7bea6 --- /dev/null +++ b/app/models/post_like.rb @@ -0,0 +1,4 @@ +class PostLike < ApplicationRecord + belongs_to :user + belongs_to :post, counter_cache: :likes_count +end diff --git a/app/views/post/_form.html.erb b/app/views/post/_form.html.erb deleted file mode 100644 index 61cd560..0000000 --- a/app/views/post/_form.html.erb +++ /dev/null @@ -1,12 +0,0 @@ -<%= simple_form_for @post, as: :post, url: path, data: { turbo: false }, class: "form-group row" do |f| %> - <%= f.input :category, placeholder: t(".category") %> - <%= f.input :title, placeholder: t(".title") %> - <%= f.input :body, placeholder: t(".body") %> - <% if current_user.admin? %> - <%= f.association :user, :label_method => lambda { |user| "#{user.last_name} #{user.first_name}" }, value_method: :id, include_blank: false %> - <% end %> -
    - <%= f.button :submit, t("b_save"), class: "btn btn-primary" %> - <%= f.button :submit, t("b_back"), formaction: root_path, formmethod: "get" %> -
    -<% end %> diff --git a/app/views/post/edit.html.erb b/app/views/post/edit.html.erb deleted file mode 100644 index 8a72357..0000000 --- a/app/views/post/edit.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= render 'form', path: post_path %> diff --git a/app/views/post/index.html.erb b/app/views/post/index.html.erb deleted file mode 100644 index 2f9cfaf..0000000 --- a/app/views/post/index.html.erb +++ /dev/null @@ -1,33 +0,0 @@ -<% if user_signed_in? %> - <% if can? :manage, Post %> - <%= link_to new_post_path, class: "btn btn-primary my-3 w-100" do %> - <%= t('.b_new_post') %> - <% end %> - <% end %> -<% end %> -<% @posts.each do |post| %> -
    -
    - <%= post.category %> -
    -
    -
    <%= post.title %>
    -

    - <%= truncate(sanitize(post.body), length: 200, omission: " #{link_to(t('.more'), post_path(post))}", escape: false) %>

    - <%= link_to t(".b_read"), post_path(post), class: "btn btn-primary" %> -
    -
    -<% end %> -
    -
    -
    -
    -
    -
    -
    - <%= pagination @pagy %> -
    -
    -
    -
    -
    diff --git a/app/views/post/new.html.erb b/app/views/post/new.html.erb deleted file mode 100644 index 63dea9d..0000000 --- a/app/views/post/new.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= render 'form', path: post_index_path %> diff --git a/app/views/post/show.html.erb b/app/views/post/show.html.erb deleted file mode 100644 index ba0327e..0000000 --- a/app/views/post/show.html.erb +++ /dev/null @@ -1,28 +0,0 @@ -

    <%= @post.title %>

    -
    -

    - - <%= t('.published') %>: - <%= @post.formatted_date(@post.created_at) %> - - - <%= t('.updated') %>: - <%= @post.formatted_date(@post.updated_at) %> - -

    -

    - - <%= t('.author') %>: - <%= "#{@post.user.first_name} #{@post.user.last_name}" %>

    - -

    <%= @post.body %>

    -<% if can? :manage, Post %> -
    -
    - <%= link_to t('b_change'), edit_post_path(@post), class: "btn btn-primary"%> -
    -
    - <%= button_to t('b_delete'), post_path(@post), method: :delete, class: "btn btn-danger"%> -
    - <% end %> -
    diff --git a/app/views/posts/comments/_child_comment.html.slim b/app/views/posts/comments/_child_comment.html.slim new file mode 100644 index 0000000..dd716f3 --- /dev/null +++ b/app/views/posts/comments/_child_comment.html.slim @@ -0,0 +1,15 @@ +.ms-4.mt-4 + - child_comment.each do |comment| + .card.border-light.mb-4 + .card-header + span.me-2.i.bi.bi-arrow-90deg-up + span.me-2 = comment.user.email + span = I18n.l(comment.created_at) + .card-body + p = simple_format(comment.content) + - if current_user + .card-footer + = render 'posts/comments/replay_form', comment: + + - if comment.children.any? + = render 'posts/comments/child_comment', child_comment: comment.children diff --git a/app/views/posts/comments/_comment_block.html.slim b/app/views/posts/comments/_comment_block.html.slim new file mode 100644 index 0000000..9e5cd05 --- /dev/null +++ b/app/views/posts/comments/_comment_block.html.slim @@ -0,0 +1,14 @@ +- comments.each do |comment| + - if comment.is_root? + .card.mb-4 + .card-header + span.me-2 = comment.user.email + span = I18n.l(comment.created_at) + .card-body + p = simple_format(comment.content) + - if current_user + .card-footer + = render 'posts/comments/replay_form', comment: + + - if comment.children.any? + = render 'posts/comments/child_comment', child_comment: comment.children diff --git a/app/views/posts/comments/_comment_form.html.slim b/app/views/posts/comments/_comment_form.html.slim new file mode 100644 index 0000000..d590cc3 --- /dev/null +++ b/app/views/posts/comments/_comment_form.html.slim @@ -0,0 +1,5 @@ +.my-4 + = simple_form_forcomment, url: post_comments_path(post, comment) do |f| + = f.input :parent_id, as: :hidden + = f.input :content, label: false, input_html: { rows: 3 } + = f.button :submit, class: 'btn btn-primary' diff --git a/app/views/posts/comments/_replay_form.html.slim b/app/views/posts/comments/_replay_form.html.slim new file mode 100644 index 0000000..0a6f71c --- /dev/null +++ b/app/views/posts/comments/_replay_form.html.slim @@ -0,0 +1,4 @@ +a.small.text-muted data-bs-toggle='collapse' href="#new_post_comment-#{comment.id}" + .mb-2 = t('.link_text') +.collapse data-bs-target="new_post_comment-#{comment.id}" id="new_post_comment-#{comment.id}" + = render 'posts/comments/comment_form', post: comment.post, comment: comment.children.new diff --git a/app/views/posts/edit.html.slim b/app/views/posts/edit.html.slim new file mode 100644 index 0000000..7e2be89 --- /dev/null +++ b/app/views/posts/edit.html.slim @@ -0,0 +1 @@ += render 'posts/shared/form', path: post_path diff --git a/app/views/posts/index.html.slim b/app/views/posts/index.html.slim new file mode 100644 index 0000000..8efc11f --- /dev/null +++ b/app/views/posts/index.html.slim @@ -0,0 +1,15 @@ +.container.my-4.px-0 + - if user_signed_in? + - if can? :manage, Post + = link_to new_post_path, class: "btn btn-primary my-3 w-100" do + .i.bi.bi-plus-square + span.ms-2 = t('.b_new_post') + + - if @posts.count.zero? + p = t('.no_posts') + + .div#post-section + = render 'posts/shared/posts' + + .div#pagination-section + .d-flex.justify-content-center == pagy_bootstrap_nav(@pagy) diff --git a/app/views/posts/likes/_like_button.html.slim b/app/views/posts/likes/_like_button.html.slim new file mode 100644 index 0000000..19d88f7 --- /dev/null +++ b/app/views/posts/likes/_like_button.html.slim @@ -0,0 +1,11 @@ +- if current_user + - if likes.present? + = link_to post_like_path(post), data: { turbo_method: :delete } + span.i.bi.bi-heart-fill.text-danger.me-2 + - else + = link_to post_likes_path(post), data: { turbo_method: :post } + span.i.bi.bi-heart.text-danger.me-2 +- else + span.i.bi.bi-heart.text-danger.me-2 + +span = post.likes_count diff --git a/app/views/posts/new.html.slim b/app/views/posts/new.html.slim new file mode 100644 index 0000000..1d80bc0 --- /dev/null +++ b/app/views/posts/new.html.slim @@ -0,0 +1 @@ += render 'posts/shared/form', path: posts_path diff --git a/app/views/posts/shared/_form.html.slim b/app/views/posts/shared/_form.html.slim new file mode 100644 index 0000000..70cb4b3 --- /dev/null +++ b/app/views/posts/shared/_form.html.slim @@ -0,0 +1,9 @@ += simple_form_for @post, as: :post, url: path, data: { turbo: false }, class: "form-group row" do |f| + = f.input :category, placeholder: t(".category") + = f.input :title, placeholder: t(".title") + = f.input :body, placeholder: t(".body"), input_html: { rows: 10 } + - if current_user.admin? + = f.association :user, :label_method => lambda { |user| "#{user.last_name} #{user.first_name}" }, value_method: :id, include_blank: false + .div + = f.button :submit, t("b_save"), class: "btn btn-primary" + = f.button :submit, t("b_back"), formaction: root_path, formmethod: "get" diff --git a/app/views/posts/shared/_post_footer.html.slim b/app/views/posts/shared/_post_footer.html.slim new file mode 100644 index 0000000..9963d58 --- /dev/null +++ b/app/views/posts/shared/_post_footer.html.slim @@ -0,0 +1,17 @@ +.row + .col + .small.text-muted.mss-auto + span.me-2 = post.user.email + span = "#{t('.published')} #{distance_of_time_in_words_to_now(post.created_at)} #{t('.ago')}" + .col.justify-content-end + .text-end.small.text-muted.ms-auto + - if post.comments_count.zero? + span.i.bi.bi-chat-square-text.me-2 + -else + span.i.bi.bi-chat-square-text.text-primary.me-2 + span.me-2 = post.comments_count + - if local_assigns[:selected] == 'post' + = render 'posts/likes/like_button', post:, likes: likes + - if local_assigns[:selected] == 'post-index' + span.i.bi.bi-heart-half.text-danger.me-2 + span = post.likes_count diff --git a/app/views/posts/shared/_post_header.html.slim b/app/views/posts/shared/_post_header.html.slim new file mode 100644 index 0000000..0d8435b --- /dev/null +++ b/app/views/posts/shared/_post_header.html.slim @@ -0,0 +1,11 @@ +.row + .col-10 + h1.mb-4.mb-lg-5 = @post.title + .col.justify-content-end.text-end + - if can? :manage, Post + span.me-2 + = link_to edit_post_path(@post) do + i.bi.bi-pencil + span + = link_to post_path(@post), data: { turbo_method: :delete, turbo_confirm: t('.delete_confirm') } do + i.bi.bi-trash3.text-danger diff --git a/app/views/posts/shared/_posts.html.slim b/app/views/posts/shared/_posts.html.slim new file mode 100644 index 0000000..0352e87 --- /dev/null +++ b/app/views/posts/shared/_posts.html.slim @@ -0,0 +1,10 @@ +- @posts.latest.each do |post| + .col-md-12 + .card.mb-3 + .card-header = post.category + .card-body + h5.card-title = link_to post.title, post_path(post), { class: 'nav-link link-dark' } + .card-text.mb-2 = link_to post_path(post), { class: 'nav-link link-dark' } do + = simple_format(truncate(post.body, length: 200, omission: "#{ link_to('[...]', post_path(post))}", escape: false)) + hr + = render 'posts/shared/post_footer', post:, selected: 'post-index' diff --git a/app/views/posts/show.html.slim b/app/views/posts/show.html.slim new file mode 100644 index 0000000..9fe623d --- /dev/null +++ b/app/views/posts/show.html.slim @@ -0,0 +1,19 @@ +.container.my-4.px-0 + section#breadcrump + ol.breadcrumb + li.breadcrumb-item + = link_to t('.all_posts'), root_path + li.breadcrumb-item.active + = @post.category + section#post-section + = render 'posts/shared/post_header', post: @post + p.mb-2 = simple_format(@post.body) + = render 'posts/shared/post_footer', post: @post, likes: @user_likes, selected: 'post' + hr + section#left-comment-section + .lead.display-6.my-2 = t('.comments') + - if current_user + = render 'posts/comments/comment_form', post: @post, comment: @comment + + section#comment-section + = render 'posts/comments/comment_block', comments: @post_comments diff --git a/app/views/shared/index/_device.html.erb b/app/views/shared/index/_device.html.erb index 8ab8a97..1627aea 100644 --- a/app/views/shared/index/_device.html.erb +++ b/app/views/shared/index/_device.html.erb @@ -112,7 +112,7 @@ <% end %> <%= device.tabel_id %> - <%= device.serial_id %> + <%= truncate(device.serial_id, length: 10) %> <%= device.device_model.name %> <%= device.year_of_production %> <%= device.year_of_commissioning %> diff --git a/config/application.rb b/config/application.rb index b790125..4181cb5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,26 +1,20 @@ require_relative "boot" require "rails/all" -require_relative '../app/middleware/rate_limiter' +require './app/middlewares/set_locale_middleware' +require './app/middlewares/rate_limiter' -# Require the gems listed in Gemfile, including any gems -# you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module ArmsWebapp class Application < Rails::Application - # Initialize configuration defaults for originally generated Rails version. config.load_defaults 7.0 config.active_job.queue_adapter = :sidekiq + config.generators.template_engine = :slim config.i18n.available_locales = [:ru, :en] config.i18n.default_locale = :ru - # Configuration for the application, engines, and railties goes here. - # - # These settings can be overridden in specific environments using the files - # in config/environments, which are processed later. - # - # config.time_zone = "Central Time (US & Canada)" - # config.eager_load_paths << Rails.root.join("extras") + + config.middleware.use SetLocaleMiddleware config.middleware.use RateLimiter end end diff --git a/config/locales/en.yml b/config/locales/en.yml index b449d95..a497753 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -67,25 +67,34 @@ en: device_component: new: add_device_component: Add device - post: + posts: index: b_new_post: New post b_read: Read more more: (more...) + comments: + replay_form: + link_text: replay show: published: Created updated: Updated author: Author - form: - title: Recipe for uranium pancakes - body: Eat some more of these... - category: Public or URB-106 - shared: + comments: Comments shared: - ui: - table: - caption: - all_items: "All: " + post_footer: + published: published + ago: ago + post_header: + delete_confirm: Delete the post irrevocably? + form: + title: Recipe for uranium pancakes + body: Eat some more of these... + category: Public or URB-106 + shared: + ui: + table: + caption: + all_items: "All: " show: device: send_to_inspection: Send to inspection diff --git a/config/locales/ru.yml b/config/locales/ru.yml index f1f5ec1..94cf31c 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -313,7 +313,7 @@ ru: building_id: Здание level: Уровень description: Описание - post: + posts: title: Заголовок body: Текст записи user: Автор @@ -524,19 +524,29 @@ ru: device_component: new: add_device_component: Добавить компонент - post: + posts: index: b_new_post: Новый пост b_read: Читать далее more: (далее...) + comments: + replay_form: + link_text: ответить show: published: Опубликован updated: Обновлен author: Автор - form: - title: Рецепт урановых оладьев - body: Съешь еще немного этих... - category: Публичный или УРБ-106 + comments: Комментарии + shared: + post_footer: + published: опубликовано + ago: назад + post_header: + delete_confirm: Безвозвратно удалить пост? + form: + title: Рецепт урановых оладьев + body: Съешь еще немного этих... + category: Публичный или УРБ-106 admin: users: index: diff --git a/config/routes.rb b/config/routes.rb index 0d009d1..565edb6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,7 @@ Rails.application.routes.draw do mount Sidekiq::Web => '/admin/sidekiq' - root 'post#index' + root 'posts#index' post 'device/download', to: 'device#download' get '/licenses/:locale', to: 'licenses#show', as: 'license' @@ -13,6 +13,13 @@ end end + resources :posts do + scope module: :posts do + resources :comments, only: %i[index edit create update destroy] + resources :likes, only: %i[create destroy] + end + end + namespace :admin do get '', to: 'admin#index' resources :users @@ -52,7 +59,6 @@ post :send_from_repair_to_close, :to => 'inspection#send_from_repair_to_close', :on => :member end - resources :post get :armstrong, to: 'controllers#react' end diff --git a/db/migrate/20240604043443_create_post_comments.rb b/db/migrate/20240604043443_create_post_comments.rb new file mode 100644 index 0000000..e794821 --- /dev/null +++ b/db/migrate/20240604043443_create_post_comments.rb @@ -0,0 +1,10 @@ +class CreatePostComments < ActiveRecord::Migration[7.0] + def change + create_table :post_comments do |t| + t.references :post, null: false, foreign_key: true + t.references :user, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20240604043539_create_post_likes.rb b/db/migrate/20240604043539_create_post_likes.rb new file mode 100644 index 0000000..e0c8d1f --- /dev/null +++ b/db/migrate/20240604043539_create_post_likes.rb @@ -0,0 +1,10 @@ +class CreatePostLikes < ActiveRecord::Migration[7.0] + def change + create_table :post_likes do |t| + t.references :post, null: false, foreign_key: true + t.references :user, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20240604044035_add_ancestry_to_post_comment.rb b/db/migrate/20240604044035_add_ancestry_to_post_comment.rb new file mode 100644 index 0000000..9fb13ef --- /dev/null +++ b/db/migrate/20240604044035_add_ancestry_to_post_comment.rb @@ -0,0 +1,6 @@ +class AddAncestryToPostComment < ActiveRecord::Migration[7.0] + def change + add_column :post_comments, :ancestry, :string + add_index :post_comments, :ancestry + end +end diff --git a/db/migrate/20240604044212_add_likes_count_to_post.rb b/db/migrate/20240604044212_add_likes_count_to_post.rb new file mode 100644 index 0000000..bad4b9e --- /dev/null +++ b/db/migrate/20240604044212_add_likes_count_to_post.rb @@ -0,0 +1,5 @@ +class AddLikesCountToPost < ActiveRecord::Migration[7.0] + def change + add_column :posts, :likes_count, :integer, default: 0 + end +end diff --git a/db/migrate/20240604044252_add_comments_count_to_post.rb b/db/migrate/20240604044252_add_comments_count_to_post.rb new file mode 100644 index 0000000..5235f67 --- /dev/null +++ b/db/migrate/20240604044252_add_comments_count_to_post.rb @@ -0,0 +1,5 @@ +class AddCommentsCountToPost < ActiveRecord::Migration[7.0] + def change + add_column :posts, :comments_count, :integer, default: 0 + end +end diff --git a/db/migrate/20240604054207_add_content_field_to_post_comment.rb b/db/migrate/20240604054207_add_content_field_to_post_comment.rb new file mode 100644 index 0000000..e3d6d7a --- /dev/null +++ b/db/migrate/20240604054207_add_content_field_to_post_comment.rb @@ -0,0 +1,5 @@ +class AddContentFieldToPostComment < ActiveRecord::Migration[7.0] + def change + add_column :post_comments, :content, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index c13a6fc..028240b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_02_20_061932) do +ActiveRecord::Schema[7.0].define(version: 2024_06_04_054207) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -204,6 +204,27 @@ t.datetime "updated_at", null: false end + create_table "post_comments", force: :cascade do |t| + t.bigint "post_id", null: false + t.bigint "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "ancestry" + t.text "content" + t.index ["ancestry"], name: "index_post_comments_on_ancestry" + t.index ["post_id"], name: "index_post_comments_on_post_id" + t.index ["user_id"], name: "index_post_comments_on_user_id" + end + + create_table "post_likes", force: :cascade do |t| + t.bigint "post_id", null: false + t.bigint "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["post_id"], name: "index_post_likes_on_post_id" + t.index ["user_id"], name: "index_post_likes_on_user_id" + end + create_table "posts", force: :cascade do |t| t.bigint "user_id", null: false t.string "title" @@ -211,6 +232,8 @@ t.string "category" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "likes_count", default: 0 + t.integer "comments_count", default: 0 t.index ["user_id"], name: "index_posts_on_user_id" end @@ -300,6 +323,10 @@ add_foreign_key "inspections", "users", column: "creator_id" add_foreign_key "inspections", "users", column: "performer_id" add_foreign_key "measurement_classes", "measurement_groups" + add_foreign_key "post_comments", "posts" + add_foreign_key "post_comments", "users" + add_foreign_key "post_likes", "posts" + add_foreign_key "post_likes", "users" add_foreign_key "posts", "users" add_foreign_key "rooms", "buildings" add_foreign_key "servers", "rooms" diff --git a/db/seeds.rb b/db/seeds.rb index 4b4a75d..6af49fd 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -145,7 +145,7 @@ service: Service.find_by(id: rand(1..10)) ) -30.times do |i| +50.times do |i| User.create( tabel_id: 10000 + i, first_name: Faker::Name.first_name, @@ -184,15 +184,50 @@ end # seed Post -10.times do |i| +300.times do |i| Post.create( user: User.first, - title: Faker::Movies::HarryPotter.spell, - body: Faker::Movies::HarryPotter.quote, - category: "Public" + title: Faker::Lorem.sentence, + body: Faker::Lorem.paragraphs.join("\n"), + category: %w[Public УРБ-106 РИК ЦСМИТ].sample + ) +end + +60.times do + post_comment = PostComment.create( + user: User.find_by(id: rand(1..10)), + post: Post.find_by(id: rand(1..30)), + content: Faker::Books::Lovecraft.paragraphs.join("\n"), + parent_id: nil + ) + + 20.times do + PostComment.create( + user: User.find_by(id: rand(1..10)), + post: post_comment.post, + content: Faker::Books::Lovecraft.paragraphs.join("\n"), + parent_id: post_comment.id + ) + end +end + +20.times do + post = Post.joins(:comments).where.not(comments: { id: nil }).sample + + PostComment.create( + user: User.find_by(id: rand(1..10)), + post:, + content: Faker::Books::Lovecraft.paragraphs.join("\n"), + parent_id: post.comments.sample.id ) end +(1..30).each do |post_id| + (1..10).each do |user_id| + PostLike.create!(post_id:, user_id:) + end +end + # seed Inspection 20.times do |i| Inspection.create( @@ -209,7 +244,7 @@ performer: User.find_by_id(rand(1..20)), type_target: 'regular', state: 'verification_successful', - conclusion_date: rand(1.year.ago..Time.now), + conclusion_date: rand(10.year.ago..Time.now), conclusion: "Всё прекрасно! №#{i}" ) end diff --git a/test/controllers/posts/comments_controller_test.rb b/test/controllers/posts/comments_controller_test.rb new file mode 100644 index 0000000..f8c1542 --- /dev/null +++ b/test/controllers/posts/comments_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class Posts::CommentsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/posts/likes_controller_test.rb b/test/controllers/posts/likes_controller_test.rb new file mode 100644 index 0000000..5f6e186 --- /dev/null +++ b/test/controllers/posts/likes_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class Posts::LikesControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/post_controller_test.rb b/test/controllers/posts_controller_test.rb similarity index 95% rename from test/controllers/post_controller_test.rb rename to test/controllers/posts_controller_test.rb index ac31a1c..baaa8d1 100644 --- a/test/controllers/post_controller_test.rb +++ b/test/controllers/posts_controller_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class PostControllerTest < ActionController::TestCase +class PostsControllerTest < ActionController::TestCase include Devise::Test::ControllerHelpers def setup diff --git a/test/factories/post_comments.rb b/test/factories/post_comments.rb new file mode 100644 index 0000000..edad5be --- /dev/null +++ b/test/factories/post_comments.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :post_comment do + post { nil } + user { nil } + end +end diff --git a/test/factories/post_likes.rb b/test/factories/post_likes.rb new file mode 100644 index 0000000..30e9b64 --- /dev/null +++ b/test/factories/post_likes.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :post_like do + post { nil } + user { nil } + end +end diff --git a/test/factories/posts.rb b/test/factories/posts.rb index 1f11b1f..c296506 100644 --- a/test/factories/posts.rb +++ b/test/factories/posts.rb @@ -2,7 +2,7 @@ factory :post do user { association :user } title { 'MyString' } - body { 'MyText' } + body { Faker::Lorem.paragraphs(number: 4).join('\n') } category { 'MyString' } end end diff --git a/test/models/post_comment_test.rb b/test/models/post_comment_test.rb new file mode 100644 index 0000000..13c9970 --- /dev/null +++ b/test/models/post_comment_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class PostCommentTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/post_like_test.rb b/test/models/post_like_test.rb new file mode 100644 index 0000000..bbaaa1a --- /dev/null +++ b/test/models/post_like_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class PostLikeTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 885cbf18397b42d1513ef486458a1662f3f571a9 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 4 Jun 2024 13:23:14 +0400 Subject: [PATCH 129/132] =?UTF-8?q?UPD:=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BF=D0=BE=D1=81=D1=82=D1=8B,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B8=20=D0=BB=D0=B0=D0=B9=D0=BA=D0=B8=20(1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/posts/comments/_comment_form.html.slim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/posts/comments/_comment_form.html.slim b/app/views/posts/comments/_comment_form.html.slim index d590cc3..163e060 100644 --- a/app/views/posts/comments/_comment_form.html.slim +++ b/app/views/posts/comments/_comment_form.html.slim @@ -1,5 +1,5 @@ .my-4 - = simple_form_forcomment, url: post_comments_path(post, comment) do |f| + = simple_form_for comment, url: post_comments_path(post, comment) do |f| = f.input :parent_id, as: :hidden = f.input :content, label: false, input_html: { rows: 3 } = f.button :submit, class: 'btn btn-primary' From d4dd7575403817db7727387ebeb1ff309be19167 Mon Sep 17 00:00:00 2001 From: Alexander Veselov Date: Tue, 4 Jun 2024 15:33:37 +0400 Subject: [PATCH 130/132] =?UTF-8?q?UPD:=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BF=D0=BE=D1=81=D1=82=D1=8B,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B8=20=D0=BB=D0=B0=D0=B9=D0=BA=D0=B8=20(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/layouts/application.html.erb | 14 +++++++------- config/locales/en.yml | 1 + config/locales/ru.yml | 4 +++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 25a82b3..0402870 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -26,15 +26,15 @@ -
    -
    +
    +