Merge pull request #398 from warpc/223-build_list_notify_and_duration_updates
223 build list notify and duration updates
This commit is contained in:
commit
ba9184cc49
|
@ -94,7 +94,6 @@ class BuildListsController < ApplicationController
|
|||
else
|
||||
@build_list.status = BuildList::FAILED_PUBLISH
|
||||
end
|
||||
@build_list.notified_at = Time.current
|
||||
@build_list.save
|
||||
|
||||
render :nothing => true, :status => 200
|
||||
|
@ -106,7 +105,6 @@ class BuildListsController < ApplicationController
|
|||
@item.save
|
||||
|
||||
@build_list.container_path = params[:container_path]
|
||||
@build_list.notified_at = Time.current
|
||||
@build_list.save
|
||||
|
||||
render :nothing => true, :status => 200
|
||||
|
@ -114,7 +112,6 @@ class BuildListsController < ApplicationController
|
|||
|
||||
def pre_build
|
||||
@build_list.status = BuildServer::BUILD_STARTED
|
||||
@build_list.notified_at = Time.current
|
||||
@build_list.save
|
||||
|
||||
render :nothing => true, :status => 200
|
||||
|
@ -123,7 +120,6 @@ class BuildListsController < ApplicationController
|
|||
def post_build
|
||||
@build_list.status = params[:status]
|
||||
@build_list.container_path = params[:container_path]
|
||||
@build_list.notified_at = Time.current
|
||||
@build_list.save
|
||||
|
||||
render :nothing => true, :status => 200
|
||||
|
@ -134,7 +130,6 @@ class BuildListsController < ApplicationController
|
|||
def circle_build
|
||||
@build_list.is_circle = true
|
||||
@build_list.container_path = params[:container_path]
|
||||
@build_list.notified_at = Time.current
|
||||
@build_list.save
|
||||
|
||||
render :nothing => true, :status => 200
|
||||
|
@ -147,7 +142,6 @@ class BuildListsController < ApplicationController
|
|||
@build_list.set_items(ActiveSupport::JSON.decode(params[:items]))
|
||||
@build_list.is_circle = (params[:is_circular].to_i != 0)
|
||||
@build_list.bs_id = params[:id]
|
||||
@build_list.notified_at = Time.current
|
||||
@build_list.save
|
||||
|
||||
render :nothing => true, :status => 200
|
||||
|
|
|
@ -139,7 +139,7 @@ class ActivityFeedObserver < ActiveRecord::Observer
|
|||
ActivityFeed.create(
|
||||
:user => User.find(recipient),
|
||||
:kind => 'build_list_notification',
|
||||
:data => {:task_num => record.bs_id, :build_list_id => record.id, :status => record.status, :notified_at => record.notified_at,
|
||||
:data => {:task_num => record.bs_id, :build_list_id => record.id, :status => record.status, :updated_at => record.updated_at,
|
||||
:project_id => record.project_id, :project_name => record.project.name, :project_owner => record.project.owner.uname,
|
||||
:user_name => record.user.name, :user_email => record.user.email, :user_id => record.user_id}
|
||||
)
|
||||
|
|
|
@ -66,22 +66,16 @@ class BuildList < ActiveRecord::Base
|
|||
scope :scoped_to_project_version, lambda {|project_version| where(:project_version => project_version) }
|
||||
scope :scoped_to_is_circle, lambda {|is_circle| where(:is_circle => is_circle) }
|
||||
scope :for_creation_date_period, lambda{|start_date, end_date|
|
||||
if start_date && end_date
|
||||
where(["#{table_name}.created_at BETWEEN ? AND ?", start_date, end_date])
|
||||
elsif start_date && !end_date
|
||||
where(["#{table_name}.created_at >= ?", start_date])
|
||||
elsif !start_date && end_date
|
||||
where(["#{table_name}.created_at <= ?", end_date])
|
||||
end
|
||||
scoped = BuildList.scoped
|
||||
scoped = scoped.where(["created_at >= ?", start_date]) if start_date
|
||||
scoped = scoped.where(["created_at <= ?", end_date]) if end_date
|
||||
scoped
|
||||
}
|
||||
scope :for_notified_date_period, lambda{|start_date, end_date|
|
||||
if start_date && end_date
|
||||
where(["notified_at BETWEEN ? AND ?", start_date, end_date])
|
||||
elsif start_date && !end_date
|
||||
where(["notified_at >= ?", start_date])
|
||||
elsif !start_date && end_date
|
||||
where(["notified_at <= ?", end_date])
|
||||
end
|
||||
scoped = BuildList.scoped
|
||||
scoped = scoped.where(["updated_at >= ?", start_date]) if start_date
|
||||
scoped = scoped.where(["updated_at <= ?", end_date]) if end_date
|
||||
scoped
|
||||
}
|
||||
scope :scoped_to_project_name, lambda {|project_name| joins(:project).where('projects.name LIKE ?', "%#{project_name}%")}
|
||||
|
||||
|
@ -136,6 +130,14 @@ class BuildList < ActiveRecord::Base
|
|||
{:project => project.name, :version => project_version, :arch => arch.name}.inspect
|
||||
end
|
||||
|
||||
def current_duration
|
||||
(Time.now - started_at).to_i
|
||||
end
|
||||
|
||||
def human_current_duration
|
||||
I18n.t("layout.build_lists.human_current_duration", {:hours => (current_duration/360).to_i, :minutes => (current_duration/60).to_i})
|
||||
end
|
||||
|
||||
def human_duration
|
||||
I18n.t("layout.build_lists.human_duration", {:hours => (duration/360).to_i, :minutes => (duration/60).to_i})
|
||||
end
|
||||
|
|
|
@ -22,8 +22,8 @@ class BuildList::Filter
|
|||
if @options[:created_at_start] || @options[:created_at_end]
|
||||
build_lists = build_lists.for_creation_date_period(@options[:created_at_start], @options[:created_at_end])
|
||||
end
|
||||
if @options[:notified_at_start] || @options[:notified_at_end]
|
||||
build_lists = build_lists.for_notified_date_period(@options[:notified_at_start], @options[:notified_at_end])
|
||||
if @options[:updated_at_start] || @options[:updated_at_end]
|
||||
build_lists = build_lists.for_notified_date_period(@options[:updated_at_start], @options[:updated_at_end])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -47,8 +47,8 @@ class BuildList::Filter
|
|||
:status => nil,
|
||||
:created_at_start => nil,
|
||||
:created_at_end => nil,
|
||||
:notified_at_start => nil,
|
||||
:notified_at_end => nil,
|
||||
:updated_at_start => nil,
|
||||
:updated_at_end => nil,
|
||||
:arch_id => nil,
|
||||
:is_circle => nil,
|
||||
:project_version => nil,
|
||||
|
@ -60,8 +60,8 @@ class BuildList::Filter
|
|||
@options[:status] = @options[:status].present? ? @options[:status].to_i : nil
|
||||
@options[:created_at_start] = build_date_from_params(:created_at_start, @options)
|
||||
@options[:created_at_end] = build_date_from_params(:created_at_end, @options)
|
||||
@options[:notified_at_start] = build_date_from_params(:notified_at_start, @options)
|
||||
@options[:notified_at_end] = build_date_from_params(:notified_at_end, @options)
|
||||
@options[:updated_at_start] = build_date_from_params(:updated_at_start, @options)
|
||||
@options[:updated_at_end] = build_date_from_params(:updated_at_end, @options)
|
||||
@options[:project_version] = @options[:project_version].presence
|
||||
@options[:arch_id] = @options[:arch_id].present? ? @options[:arch_id].to_i : nil
|
||||
@options[:is_circle] = @options[:is_circle].present? ? @options[:is_circle] == "1" : nil
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- encoding : utf-8 -*-
|
||||
class BuildList::Item < ActiveRecord::Base
|
||||
|
||||
belongs_to :build_list
|
||||
belongs_to :build_list, :touch => true
|
||||
|
||||
attr_protected :build_list_id
|
||||
|
||||
|
|
|
@ -6,7 +6,14 @@ class BuildListObserver < ActiveRecord::Observer
|
|||
record.started_at = Time.now if record.status == BuildServer::BUILD_STARTED
|
||||
if [BuildServer::BUILD_ERROR, BuildServer::SUCCESS].include? record.status
|
||||
# stores time interval beetwin build start and finish in seconds
|
||||
record.duration = (Time.now - record.started_at).to_i
|
||||
record.duration = record.current_duration
|
||||
|
||||
if record.status == BuildServer::SUCCESS
|
||||
# Update project average build time
|
||||
build_count = record.project.build_count
|
||||
new_av_time = ( record.project.average_build_time * build_count + record.duration ) / ( build_count + 1 )
|
||||
record.project.update_attributes({ :average_build_time => new_av_time, :build_count => build_count + 1 }, :without_protection => true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -203,6 +203,11 @@ class Project < ActiveRecord::Base
|
|||
recipients
|
||||
end
|
||||
|
||||
def human_average_build_time
|
||||
time = average_build_time
|
||||
I18n.t("layout.projects.human_average_build_time", {:hours => (time/360).to_i, :minutes => (time/60).to_i})
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def build_path(dir)
|
||||
|
|
|
@ -22,26 +22,11 @@
|
|||
%h3= t("layout.activity_feed.my_builds_by_day")
|
||||
%table{:cellpadding => "0", :cellspacing => "0"}
|
||||
%tbody
|
||||
%tr
|
||||
%td.first
|
||||
= link_to t("layout.build_lists.statuses.#{:build_published}"), build_lists_path(:filter => {:status => BuildList::BUILD_PUBLISHED, :notified_at => midnight})
|
||||
%td= BuildList.for_status(BuildList::BUILD_PUBLISHED).for_user(current_user).for_notified_date_period(midnight, nil).count
|
||||
%tr
|
||||
%td.first
|
||||
= link_to t("layout.build_lists.statuses.#{:success}"), build_lists_path(:filter => {:status => BuildServer::SUCCESS, :notified_at => midnight})
|
||||
%td= BuildList.for_status(BuildServer::SUCCESS).for_user(current_user).for_notified_date_period(midnight, nil).count
|
||||
%tr
|
||||
%td.first
|
||||
= link_to t("layout.build_lists.statuses.#{:build_started}"), build_lists_path(:filter => {:status => BuildServer::BUILD_STARTED, :notified_at => midnight})
|
||||
%td= BuildList.for_status(BuildServer::BUILD_STARTED).for_user(current_user).for_notified_date_period(midnight, nil).count
|
||||
%tr
|
||||
%td.first
|
||||
= link_to t("layout.build_lists.statuses.#{:build_pending}"), build_lists_path(:filter => {:status => BuildList::BUILD_PENDING, :notified_at => midnight})
|
||||
%td= BuildList.for_status(BuildList::BUILD_PENDING).for_user(current_user).for_notified_date_period(midnight, nil).count
|
||||
%tr
|
||||
%td.first
|
||||
= link_to t("layout.build_lists.statuses.#{:build_error}"), build_lists_path(:filter => {:status => BuildServer::BUILD_ERROR, :notified_at => midnight})
|
||||
%td= BuildList.for_status(BuildServer::BUILD_ERROR).for_user(current_user).for_notified_date_period(midnight, nil).count
|
||||
- ['BuildList::BUILD_PUBLISHED', 'BuildServer::SUCCESS', 'BuildServer::BUILD_STARTED', 'BuildList::BUILD_PENDING', 'BuildServer::BUILD_ERROR'].each do |state|
|
||||
%tr
|
||||
%td.first
|
||||
= link_to t("layout.build_lists.statuses.#{state.demodulize.downcase}"), build_lists_path(:filter => {:status => state.constantize, :updated_at => midnight})
|
||||
%td= BuildList.for_status(state.constantize).for_user(current_user).for_notified_date_period(midnight, nil).count
|
||||
%tr
|
||||
%td.first
|
||||
= link_to t("layout.activity_feed.all_my_builds"), build_lists_path
|
||||
|
|
|
@ -15,5 +15,5 @@
|
|||
- else ['failed', t("layout.build_lists.statuses.#{BuildList::HUMAN_STATUSES[status]}")]
|
||||
= raw t("notifications.bodies.build_status.#{message}", :error => error)
|
||||
.both
|
||||
%span.date= notified_at
|
||||
%span.date= updated_at
|
||||
.both
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
%td= build_list.arch.name
|
||||
%td= link_to build_list.user.try(:fullname), build_list.user
|
||||
%td= link_to image_tag('x.png', :class => 'delete-row', :id => "delete-row#{build_list_counter}"), cancel_build_list_path(build_list), :method => :put, :confirm => t('layout.confirm') if build_list.can_cancel? and can?(:cancel, build_list)
|
||||
%td= build_list.notified_at
|
||||
%td= build_list.updated_at
|
||||
|
|
|
@ -47,10 +47,10 @@
|
|||
.date_select= f.date_select(:created_at_start, :include_blank => true, :selected => @filter.created_at_start)
|
||||
%h3.small= t("layout.build_lists.created_at_end")
|
||||
.date_select= f.date_select(:created_at_end, :include_blank => true, :selected => @filter.created_at_end)
|
||||
%h3.small= t("layout.build_lists.notified_at_start")
|
||||
.date_select= f.date_select(:notified_at_start, :include_blank => true, :selected => @filter.notified_at_start)
|
||||
%h3.small= t("layout.build_lists.notified_at_end")
|
||||
.date_select= f.date_select(:notified_at_end, :include_blank => true, :selected => @filter.notified_at_end)
|
||||
%h3.small= t("layout.build_lists.updated_at_start")
|
||||
.date_select= f.date_select(:updated_at_start, :include_blank => true, :selected => @filter.updated_at_start)
|
||||
%h3.small= t("layout.build_lists.updated_at_end")
|
||||
.date_select= f.date_select(:updated_at_end, :include_blank => true, :selected => @filter.updated_at_end)
|
||||
%h3.small= t("layout.build_lists.project_name_search")
|
||||
= f.text_field :project_name
|
||||
%h3.small= t("layout.build_lists.bs_id_search")
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
%td= link_to (platform_build_list.bs_id.present? ? platform_build_list.bs_id : t("layout.build_lists.bs_id_not_set")), platform_build_list
|
||||
%td= platform_build_list.human_status
|
||||
%td= link_to platform_build_list.project.name, platform_build_list.project
|
||||
%td= platform_build_list.notified_at
|
||||
%td= platform_build_list.updated_at
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
%th.lpadding16= t("activerecord.attributes.build_list.arch")
|
||||
%th.lpadding16= t("activerecord.attributes.build_list.user")
|
||||
%th= t("layout.build_lists.action")
|
||||
%th.lpadding16= t("activerecord.attributes.build_list.notified_at")
|
||||
%th.lpadding16= t("activerecord.attributes.build_list.updated_at")
|
||||
%tbody= render @build_lists
|
||||
.both
|
||||
|
||||
|
|
|
@ -46,8 +46,8 @@
|
|||
.leftside.width125= t("activerecord.attributes.build_list.arch")
|
||||
.leftside= @build_list.arch.name
|
||||
.both
|
||||
.leftside.width125= t("activerecord.attributes.build_list.notified_at")
|
||||
.leftside= @build_list.notified_at
|
||||
.leftside.width125= t("activerecord.attributes.build_list.updated_at")
|
||||
.leftside= @build_list.updated_at
|
||||
.both
|
||||
.leftside.width125= t("activerecord.attributes.build_list.is_circle")
|
||||
.leftside= t("layout.#{@build_list.is_circle?}_")
|
||||
|
@ -57,6 +57,13 @@
|
|||
.leftside.width125
|
||||
.leftside= @build_list.human_duration
|
||||
.both
|
||||
- if !@build_list.finished? && @build_list.started_at
|
||||
%br
|
||||
.leftside.width125
|
||||
.leftside
|
||||
= "#{@build_list.human_current_duration} / #{@build_list.project.human_average_build_time}"
|
||||
.both
|
||||
|
||||
.hr
|
||||
%h3= t("layout.build_lists.items_header")
|
||||
- if @item_groups.blank?
|
||||
|
|
|
@ -14,10 +14,9 @@ en:
|
|||
arch_id: Architecture
|
||||
arch: Architecture
|
||||
is_circle: Recurrent build
|
||||
notified_at: Notified at
|
||||
updated_at: Notified at
|
||||
additional_repos: Additional repositories
|
||||
include_repos: Included repositories
|
||||
updated_at: Updated on
|
||||
created_at: Created on
|
||||
pl: Repository for package storage
|
||||
pl_id: Repository for package storage
|
||||
|
@ -45,8 +44,8 @@ en:
|
|||
current: Curent
|
||||
created_at_start: "Build to start on:"
|
||||
created_at_end: "Build to start until:"
|
||||
notified_at_start: "Last update from BS on:"
|
||||
notified_at_end: " Last update from BS until:"
|
||||
updated_at_start: "Last update from BS on:"
|
||||
updated_at_end: " Last update from BS until:"
|
||||
bs_id_search: 'Search by Id'
|
||||
project_name_search: Search by project name
|
||||
bs_id_not_set: Id has not been configured yet
|
||||
|
@ -61,6 +60,7 @@ en:
|
|||
action: Action
|
||||
new_header: New build
|
||||
main_data: Main data
|
||||
human_current_duration: Build currently takes %{hours} h. %{minutes} min.
|
||||
human_duration: Builded in %{hours} h. %{minutes} min.
|
||||
|
||||
ownership:
|
||||
|
|
|
@ -14,10 +14,9 @@ ru:
|
|||
arch_id: Архитектура
|
||||
arch: Архитектура
|
||||
is_circle: Циклическая сборка
|
||||
notified_at: Информация получена
|
||||
updated_at: Информация получена
|
||||
additional_repos: Дополнительные репозитории
|
||||
include_repos: Подключаемые репозитории
|
||||
updated_at: Обновлен
|
||||
created_at: Создан
|
||||
pl: Репозиторий для сохранения пакетов
|
||||
pl_id: Репозиторий для сохранения пакетов
|
||||
|
@ -44,8 +43,8 @@ ru:
|
|||
current: Текущие
|
||||
created_at_start: "Время постановки на сборку с:"
|
||||
created_at_end: "Время постановки на сборку по:"
|
||||
notified_at_start: "Время последнего обновления от BS с:"
|
||||
notified_at_end: "Время последнего обновления от BS по:"
|
||||
updated_at_start: "Время последнего обновления от BS с:"
|
||||
updated_at_end: "Время последнего обновления от BS по:"
|
||||
bs_id_search: 'Поиск по Id'
|
||||
project_name_search: Поиск по названию проекта
|
||||
bs_id_not_set: Id еще не присвоен
|
||||
|
@ -60,6 +59,8 @@ ru:
|
|||
action: Действие
|
||||
new_header: Новая сборка
|
||||
main_data: Основные данные
|
||||
|
||||
human_current_duration: Сборка длится уже %{hours} ч. %{minutes} мин.
|
||||
human_duration: Собрано за %{hours} ч. %{minutes} мин.
|
||||
|
||||
ownership:
|
||||
|
|
|
@ -33,6 +33,7 @@ en:
|
|||
sections: Sections
|
||||
has_issue_description: Tracker adds a lightweight issue management system tightly integrated with your repository.
|
||||
has_wiki_description: Wikis are the simplest way to allow other users to contribute content. Any user can create and edit pages for documentation, examples, support or anything you wish.
|
||||
human_average_build_time: Expected time is %{hours} h. %{minutes} min.
|
||||
|
||||
git_help:
|
||||
cloning: Cloning the repository
|
||||
|
|
|
@ -33,6 +33,7 @@ ru:
|
|||
sections: Разделы
|
||||
has_issue_description: Трэкер предоставляет лекговесный менеджер для задач по разработке Вашего проекта.
|
||||
has_wiki_description: Wiki - это самый простой способ предоставить другим вносить свой вклад в развитие Вашего проекта. Каждый пользователь нашего сервиса может использовать Wiki для документирования, примеров, поддержки или всего другого, в чем у Вас появится необходимость.
|
||||
human_average_build_time: 'Ожидаемое время: %{hours} ч. %{minutes} мин.'
|
||||
|
||||
diff_show_header: "%{files} с %{additions} и %{deletions}."
|
||||
about_subheader: "О проекте"
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
class BuildAverageTime < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :projects, :average_build_time, :integer, :null => false, :default => 0
|
||||
add_column :projects, :build_count, :integer, :null => false, :default => 0
|
||||
end
|
||||
|
||||
ActivityFeed.where(:kind => 'build_list_notification').destroy_all
|
||||
|
||||
end
|
28
db/schema.rb
28
db/schema.rb
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20120411142354) do
|
||||
ActiveRecord::Schema.define(:version => 20120413102757) do
|
||||
|
||||
create_table "activity_feeds", :force => true do |t|
|
||||
t.integer "user_id", :null => false
|
||||
|
@ -190,7 +190,7 @@ ActiveRecord::Schema.define(:version => 20120411142354) do
|
|||
t.string "owner_type"
|
||||
t.string "visibility", :default => "open", :null => false
|
||||
t.string "platform_type", :default => "main", :null => false
|
||||
t.string "distrib_type"
|
||||
t.string "distrib_type", :null => false
|
||||
end
|
||||
|
||||
add_index "platforms", ["name"], :name => "index_platforms_on_name", :unique => true, :case_sensitive => false
|
||||
|
@ -257,27 +257,31 @@ ActiveRecord::Schema.define(:version => 20120411142354) do
|
|||
t.datetime "updated_at"
|
||||
t.integer "owner_id"
|
||||
t.string "owner_type"
|
||||
t.string "visibility", :default => "open"
|
||||
t.string "visibility", :default => "open"
|
||||
t.text "description"
|
||||
t.string "ancestry"
|
||||
t.boolean "has_issues", :default => true
|
||||
t.boolean "has_wiki", :default => false
|
||||
t.boolean "has_issues", :default => true
|
||||
t.string "srpm_file_name"
|
||||
t.string "srpm_content_type"
|
||||
t.integer "srpm_file_size"
|
||||
t.datetime "srpm_updated_at"
|
||||
t.string "default_branch", :default => "master"
|
||||
t.boolean "is_rpm", :default => true
|
||||
t.boolean "has_wiki", :default => false
|
||||
t.string "default_branch", :default => "master"
|
||||
t.boolean "is_rpm", :default => true
|
||||
t.integer "average_build_time", :default => 0, :null => false
|
||||
t.integer "build_count", :default => 0, :null => false
|
||||
end
|
||||
|
||||
add_index "projects", ["owner_id"], :name => "index_projects_on_name_and_owner_id_and_owner_type", :unique => true
|
||||
|
||||
create_table "register_requests", :force => true do |t|
|
||||
t.string "name"
|
||||
t.string "email"
|
||||
t.string "token"
|
||||
t.boolean "approved", :default => false
|
||||
t.boolean "rejected", :default => false
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "interest"
|
||||
t.text "more"
|
||||
end
|
||||
|
@ -339,9 +343,6 @@ ActiveRecord::Schema.define(:version => 20120411142354) do
|
|||
t.string "uname"
|
||||
t.string "role"
|
||||
t.string "language", :default => "en"
|
||||
t.string "confirmation_token"
|
||||
t.datetime "confirmed_at"
|
||||
t.datetime "confirmation_sent_at"
|
||||
t.integer "own_projects_count", :default => 0, :null => false
|
||||
t.datetime "reset_password_sent_at"
|
||||
t.text "professional_experience"
|
||||
|
@ -355,6 +356,9 @@ ActiveRecord::Schema.define(:version => 20120411142354) do
|
|||
t.integer "failed_attempts", :default => 0
|
||||
t.string "unlock_token"
|
||||
t.datetime "locked_at"
|
||||
t.string "confirmation_token"
|
||||
t.datetime "confirmed_at"
|
||||
t.datetime "confirmation_sent_at"
|
||||
end
|
||||
|
||||
add_index "users", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true
|
||||
|
|
Loading…
Reference in New Issue