diff --git a/app/assets/javascripts/admin-manifest.coffee b/app/assets/javascripts/admin-manifest.coffee
index 759e78c5a..51bc5f419 100644
--- a/app/assets/javascripts/admin-manifest.coffee
+++ b/app/assets/javascripts/admin-manifest.coffee
@@ -9,5 +9,6 @@
#= require shared/jquery.validate.bootstrap
#= require jquery-ui/datepicker
#= require select2
+#= require jquery.doubleScroll
#= require shared/general
#= require admin/application
diff --git a/app/assets/javascripts/admin/application.coffee b/app/assets/javascripts/admin/application.coffee
index 256d2e0fa..bdc1a74bb 100644
--- a/app/assets/javascripts/admin/application.coffee
+++ b/app/assets/javascripts/admin/application.coffee
@@ -16,3 +16,35 @@ $(document).on 'page:change', ->
$(this).validate()
$('[data-toggle="popover"]').popover()
+
+
+
+
+ # doublescroll
+ $('[data-doublescroll]').doubleScroll({
+ onlyIfScroll: false,
+ scrollCss:
+ 'overflow-x': 'auto'
+ 'overflow-y': 'hidden'
+ contentCss:
+ 'overflow-x': 'auto'
+ 'overflow-y': 'hidden'
+ resetOnWindowResize: true
+ })
+
+ positionSlider = ->
+ for scroll in document.querySelectorAll('[data-doublescroll]')
+ wrapper = scroll.previousSibling
+ if $(scroll).offset().top < $(window).scrollTop()
+ wrapper.style.position = 'fixed'
+ wrapper.style.top = '-5px'
+ else
+ wrapper.style.position = 'relative'
+ wrapper.style.top = '0'
+ return
+
+ positionSlider()
+ $(window).scroll(positionSlider).resize positionSlider
+ #due .report-table width: auto top scrollbar appears after resize so we do fake resize action
+ $(window).resize()
+
diff --git a/app/controllers/admin/contact_versions_controller.rb b/app/controllers/admin/contact_versions_controller.rb
new file mode 100644
index 000000000..215d6203c
--- /dev/null
+++ b/app/controllers/admin/contact_versions_controller.rb
@@ -0,0 +1,46 @@
+class Admin::ContactVersionsController < AdminController
+ load_and_authorize_resource
+
+ def index
+ params[:q] ||= {}
+
+ @q = ContactVersion.search(params[:q])
+ @versions = @q.result.page(params[:page])
+ search_params = params[:q].deep_dup
+
+ whereS = "1=1"
+
+ search_params.each do |key, value|
+ next if value.empty?
+ case key
+ when 'event'
+ whereS += " AND event = '#{value}'"
+ else
+ whereS += create_where_string(key, value)
+ end
+ end
+
+ versions = ContactVersion.includes(:item).where(whereS)
+ @q = versions.search(params[:q])
+ @versions = @q.result.page(params[:page])
+ @versions = @versions.per(params[:results_per_page]) if params[:results_per_page].to_i > 0
+
+ end
+
+ def show
+ per_page = 7
+ @version = ContactVersion.find(params[:id])
+ @q = ContactVersion.where(item_id: @version.item_id).order(created_at: :asc).search
+ @versions = @q.result.page(params[:page])
+ @versions = @versions.per(per_page)
+ end
+
+ def search
+ render json: ContactVersion.search_by_query(params[:q])
+ end
+
+ def create_where_string(key, value)
+ " AND object->>'#{key}' ~ '#{value}'"
+ end
+
+end
diff --git a/app/controllers/admin/domain_versions_controller.rb b/app/controllers/admin/domain_versions_controller.rb
index 44a2087cd..0d85e047f 100644
--- a/app/controllers/admin/domain_versions_controller.rb
+++ b/app/controllers/admin/domain_versions_controller.rb
@@ -1,18 +1,61 @@
class Admin::DomainVersionsController < AdminController
load_and_authorize_resource
- # rubocop:disable Style/GuardClause
def index
- @domain = Domain.where(id: params[:domain_id]).includes({versions: :item}).first
- @versions = @domain.versions
+ params[:q] ||= {}
+
+ @q = DomainVersion.includes(:item).search(params[:q])
+ @versions = @q.result.page(params[:page])
+ search_params = params[:q].deep_dup
+
+ if search_params[:registrant]
+ registrant = Contact.find_by(name: search_params[:registrant])
+ search_params.delete(:registrant)
+ end
+
+ if search_params[:registrar]
+ registrar = Registrar.find_by(name: search_params[:registrar])
+ search_params.delete(:registrar)
+ end
+
+ whereS = "1=1"
+
+ search_params.each do |key, value|
+ next if value.empty?
+ case key
+ when 'event'
+ whereS += " AND event = '#{value}'"
+ else
+ whereS += create_where_string(key, value)
+ end
+ end
+
+ whereS += " AND object->>'registrant_id' = '#{registrant.id}'" if registrant
+ whereS += " AND object->>'registrar_id' = '#{registrar.id}'" if registrar
+
+ versions = DomainVersion.includes(:item).where(whereS)
+ @q = versions.search(params[:q])
+ @versions = @q.result.page(params[:page])
+ @versions = @versions.per(params[:results_per_page]) if params[:results_per_page].to_i > 0
+ render "admin/domain_versions/archive"
- # Depricated it had to load legal document. We may do it by parsing and adding link.
- # if @domain.pending_json.present?
- # frame = Nokogiri::XML(@domain.pending_json['frame'])
- # @pending_user = User.find(@domain.pending_json['current_user_id'])
- # @pending_domain = Epp::Domain.find(@domain.id)
- # @pending_domain.update(frame, @pending_user, false)
- # end
end
- # rubocop:enable Style/GuardClause
+
+ def show
+ per_page = 7
+ @version = DomainVersion.find(params[:id])
+ @q = DomainVersion.where(item_id: @version.item_id).order(created_at: :desc).search
+ @versions = @q.result.page(params[:page])
+ @versions = @versions.per(per_page)
+ end
+
+ def search
+ render json: DomainVersion.search_by_query(params[:q])
+ end
+
+ def create_where_string(key, value)
+ " AND object->>'#{key}' ~ '#{value}'"
+ end
+
+
end
diff --git a/app/controllers/admin/domains_controller.rb b/app/controllers/admin/domains_controller.rb
index e619c4da9..f7914353e 100644
--- a/app/controllers/admin/domains_controller.rb
+++ b/app/controllers/admin/domains_controller.rb
@@ -77,6 +77,11 @@ class Admin::DomainsController < AdminController
redirect_to [:admin, @domain]
end
+ def versions
+ @domain = Domain.where(id: params[:domain_id]).includes({versions: :item}).first
+ @versions = @domain.versions
+ end
+
private
def set_domain
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 098c10d9b..7cee5b69e 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -72,4 +72,15 @@ module ApplicationHelper
link_to(title, url_for(sort: {param_name => order}), class: "sort_link #{order}")
end
+
+ def changing_css_class(version, *attrs)
+ return unless version
+ css_class = "text-warning"
+
+ if attrs.size == 1
+ version.object_changes.to_h[attrs.first] && css_class
+ else
+ version.object_changes.to_h.slice(*attrs).any? && css_class
+ end
+ end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 61f1edb2e..82ffd7c2e 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -95,6 +95,7 @@ class Ability
can :manage, ReservedDomain
can :manage, ZonefileSetting
can :manage, DomainVersion
+ can :manage, ContactVersion
can :manage, Pricelist
can :manage, User
can :manage, ApiUser
diff --git a/app/models/concerns/versions.rb b/app/models/concerns/versions.rb
index 768cf021e..49c4298f2 100644
--- a/app/models/concerns/versions.rb
+++ b/app/models/concerns/versions.rb
@@ -3,6 +3,7 @@ module Versions
extend ActiveSupport::Concern
included do
+ attr_accessor :version_loader
has_paper_trail class_name: "#{model_name}Version"
# add creator and updator
@@ -55,4 +56,23 @@ module Versions
domains.each(&:touch_with_version)
end
end
+
+ module ClassMethods
+ def all_versions_for(ids, time)
+ ver_klass = paper_trail_version_class
+ from_history = ver_klass.where(item_id: ids.to_a).
+ order(:item_id).
+ preceding(time + 1, true).
+ select("distinct on (item_id) #{ver_klass.table_name}.*").
+ map do |ver|
+ o = new(ver.object)
+ o.version_loader = ver
+ ver.object_changes.to_h.each { |k, v| o[k]=v[-1] }
+ o
+ end
+ not_in_history = where(id: (ids.to_a - from_history.map(&:id)))
+
+ from_history + not_in_history
+ end
+ end
end
diff --git a/app/views/admin/contact_versions/index.haml b/app/views/admin/contact_versions/index.haml
new file mode 100644
index 000000000..68e74c547
--- /dev/null
+++ b/app/views/admin/contact_versions/index.haml
@@ -0,0 +1,87 @@
+= render 'shared/title', name: t(:contacts)
+
+.row
+ .col-md-12
+ = search_form_for [:admin, @q], html: { style: 'margin-bottom: 0;', class: 'js-form', autocomplete: 'off' } do |f|
+ .row
+ .col-md-3
+ .form-group
+ = f.label :name
+ = f.search_field :name, value: params[:q][:name], class: 'form-control', placeholder: t(:name)
+ .col-md-3
+ .form-group
+ = f.label :id
+ = f.search_field :id, value: params[:q][:id], class: 'form-control', placeholder: t(:id)
+ .col-md-3
+ .form-group
+ = f.label :ident
+ = f.search_field :ident, value: params[:q][:ident], class: 'form-control', placeholder: t(:ident)
+ .col-md-3
+ .form-group
+ = f.label :phone
+ = f.search_field :phone, value: params[:q][:phone], class: 'form-control', placeholder: t(:phone)
+ .row
+ .col-md-3
+ .col-md-3
+ .form-group
+ = label_tag :action
+ = select_tag '[q][event]', options_for_select([['Update', 'update'], ['Destroy', 'destroy']], params[:q][:event]), { include_blank:true, multiple: false, placeholder: t(:choose), class: 'form-control js-combobox' }
+ .col-md-3
+ .form-group
+ = label_tag t(:results_per_page)
+ = text_field_tag :results_per_page, params[:results_per_page], class: 'form-control', placeholder: t(:results_per_page)
+ .col-md-3{style: 'padding-top: 25px;'}
+ %button.btn.btn-primary
+
+ %span.glyphicon.glyphicon-search
+
+ %button.btn.btn-default.js-reset-form
+ = t(:clear_fields)
+%hr
+
+
+.row
+ .col-md-12
+ .table-responsive
+ %table.table.table-hover.table-bordered.table-condensed
+ %thead
+ %tr
+ %th{class: 'col-xs-2'}
+ = t(:name)
+ %th{class: 'col-xs-2'}
+ = t(:id)
+ %th{class: 'col-xs-2'}
+ = t(:ident)
+ %th{class: 'col-xs-2'}
+ = t(:phone)
+ %th{class: 'col-xs-2'}
+ = t(:registrar)
+ %th{class: 'col-xs-2'}
+ = t(:action_date)
+ %th{class: 'col-xs-2'}
+ = t(:action)
+ %tbody
+ - @versions.each do |version|
+ - if version.reify
+ %tr
+ %td= link_to(version.reify.name, admin_contact_version_path(version.id))
+ %td= version.reify.code
+ %td= ident_for(version.reify)
+ %td= version.reify.phone
+ %td
+ - if version.reify.registrar
+ = link_to(version.reify.registrar, admin_registrar_path(version.reify.registrar))
+ %td= l(version.created_at, format: :short)
+ %td= version.event
+
+
+.row
+ .col-md-6
+ = paginate @versions
+ .col-md-6.text-right
+
+
+:coffee
+ $(".js-reset-form").on "click", (e) ->
+ e.preventDefault();
+ window.location = "#{admin_contact_versions_path}"
diff --git a/app/views/admin/contact_versions/show.haml b/app/views/admin/contact_versions/show.haml
new file mode 100644
index 000000000..d6762a8c6
--- /dev/null
+++ b/app/views/admin/contact_versions/show.haml
@@ -0,0 +1,107 @@
+- contact = Contact.new(@version.object.to_h)
+- @version.object_changes.to_h.each{|k,v| contact[k]=v.last}
+= render 'shared/title', name: contact.name
+
+.row
+ .col-md-8
+ .panel.panel-default{:style => "min-height:420px;"}
+ .panel-heading
+ %h3.panel-title
+ = l(@version.created_at, format: :short)
+ = @version.event
+ = plain_username(@version.terminator)
+ .panel-body
+ %dl.dl-horizontal
+ %dt= t(:id)
+ %dd{class: changing_css_class(@version,"code")}
+ = contact.code
+
+ %dt= t(:statuses)
+ %dd{class: changing_css_class(@version,"statuses")}
+ = contact.statuses.join(", ")
+
+ %dt= t(:ident)
+ %dd{class: changing_css_class(@version,"ident_country_code", "ident_type", "ident")}
+ = ident_for(contact)
+
+ %dt= t(:email)
+ %dd{class: changing_css_class(@version,"email")}
+ = contact.email
+
+ %dt= t(:phone)
+ %dd{class: changing_css_class(@version,"phone")}
+ = contact.phone
+
+ - if contact.fax
+ %dt= t(:fax)
+ %dd{class: changing_css_class(@version,"fax")}
+ = contact.fax
+
+ %br
+
+ %dt= t(:created)
+ %dd{class: changing_css_class(@version,"created_at")}
+ = l(contact.created_at, format: :short)
+
+ %dt= t(:updated)
+ %dd{class: changing_css_class(@version,"updated_at")}
+ = l(contact.updated_at, format: :short)
+
+ %dt= t(:registrar)
+ %dd{class: changing_css_class(@version,"registrar_id")}
+ - if contact.registrar.present?
+ = link_to(contact.registrar, admin_registrar_path(contact.registrar))
+
+ %dl.dl-horizontal
+ - if contact.org_name.present?
+ %dt= t(:org_name)
+ %dd{class: changing_css_class(@version,"org_name")}= contact.org_name
+
+ %dt= t(:street)
+ %dd{class: changing_css_class(@version,"street")}= contact.street.to_s.gsub("\n", '
').html_safe
+
+ %dt= t(:city)
+ %dd{class: changing_css_class(@version,"city")}= contact.city
+
+ %dt= t(:zip)
+ %dd{class: changing_css_class(@version,"zip")}= contact.zip
+
+ %dt= t(:state)
+ %dd{class: changing_css_class(@version,"state")}= contact.state
+
+ %dt= t(:country)
+ %dd{class: changing_css_class(@version,"country_code")}= contact.country
+
+
+ %span{:style => "padding-right:10px; float: right;"}
+ - if @version.previous
+ = link_to(t(:previous),
+ admin_contact_version_path(@version.previous.id),
+ class: 'btn btn-primary')
+ - else
+ %a.btn.btn-primary.disabled{:href => "#"}
+ %span= t(:previous)
+ - if @version.next
+ = link_to(t(:next),
+ admin_contact_version_path(@version.next.id),
+ class: 'btn btn-default')
+ - else
+ %a.btn.btn-default.disabled{:href => "#"}
+ %span= t(:next)
+
+ .col-md-4
+ .panel.panel-default{:style => "min-height:420px;"}
+ %ul.nav.nav-pills.nav-stacked
+ - @versions.each do |vs|
+ - if vs.id == @version.id
+ %li.active
+ = link_to admin_contact_version_path(vs.id) do
+ = l(vs.created_at, format: :short)
+ = vs.event
+ - else
+ %li
+ = link_to admin_contact_version_path(vs.id) do
+ = l(vs.created_at, format: :short)
+ = vs.event
+ %span{:style => "padding-left:10px; position: absolute; bottom: 10px;"}
+ = paginate @versions
\ No newline at end of file
diff --git a/app/views/admin/domain_versions/_version.haml b/app/views/admin/domain_versions/_version.haml
deleted file mode 100644
index 321e9abda..000000000
--- a/app/views/admin/domain_versions/_version.haml
+++ /dev/null
@@ -1,109 +0,0 @@
-- statuses_link ||= false
-- version ||= false
-- domain ||= false
-- pending_user ||= false
-
-- if domain.present?
- - if version # normal history
- - children = HashWithIndifferentAccess.new(version.children)
- - nameservers = Nameserver.where(id: children[:nameservers])
- - tech_contacts = Contact.where(id: children[:tech_contacts])
- - admin_contacts = Contact.where(id: children[:admin_contacts])
- - registrant = Contact.where(id: children[:registrant])
- - event = version.event
- - creator = plain_username(version.terminator)
- - else # pending history
- - nameservers = domain.nameservers
- - tech_contacts = domain.tech_contacts
- - admin_contacts = domain.admin_contacts
- - registrant = [domain.registrant]
- - creator = pending_user.try(:username)
- - event = 'pending'
-
- %td
- %p.nowrap
- = l(domain.updated_at, format: :shorts)
- - if statuses_link
- %br= link_to t(:edit_statuses), edit_admin_domain_path(params[:domain_id])
-
- %p.text-right
- = event
- %br
- = creator
-
- %td
- %p
- - if domain.statuses.present?
- - domain.statuses.each do |s|
- = s
- - if domain.status_notes.present?
- - notes = domain.status_notes[s]
- - if notes
- %br
- %i= notes
- %br
- - if domain.pending_json.present?
- %p
- = link_to t(:pending_epp), '#', class: 'js-pending-toggle'
-
- %td
- %p
- = "#{domain.period}#{domain.period_unit}"
- %br
- = "#{l(domain.valid_from, format: :date)}"
- %br
- = "#{l(domain.valid_to, format: :date)}"
-
- %td
- - if registrant
- - registrant.each do |r|
- %p
- = r[:name]
- = r[:phone]
- = r[:email]
- %p
- = r[:code]
-
- %td
- - if admin_contacts
- - admin_contacts.each do |ac|
- %p
- = ac[:name]
- = ac[:phone]
- = ac[:email]
- %p
- = ac[:code]
-
- %td
- - if tech_contacts
- - tech_contacts.each do |tc|
- %p
- = tc[:name]
- = tc[:phone]
- = tc[:email]
- %p
- = tc[:code]
-
- %td
- %p
- - if nameservers
- - nameservers.each do |ns|
- = ns[:hostname]
- %br
- = ns[:ipv4]
- = ns[:ipv6]
-
- %td
- %p
- = domain.registrar.name if domain.registrar
-
- - if domain.pending_json.present?
- %tr.js-pending{ style: 'display: none;' }
- %td{colspan: 8}
- = preserve do
- %pre
- - formatted_req = Nokogiri::XML(domain.pending_json['frame'])
- - if formatted_req.errors.none?
- = formatted_req
- - else
- = domain.pending_json['frame']
diff --git a/app/views/admin/domain_versions/archive.haml b/app/views/admin/domain_versions/archive.haml
new file mode 100644
index 000000000..f50f18d06
--- /dev/null
+++ b/app/views/admin/domain_versions/archive.haml
@@ -0,0 +1,80 @@
+= render 'shared/title', name: t(:domains)
+
+.row
+ .col-md-12
+ = search_form_for [:admin, @q], html: { style: 'margin-bottom: 0;', class: 'js-form', autocomplete: 'off' } do |f|
+ .row
+ .col-md-3
+ .form-group
+ = label_tag :name
+ = f.search_field :name, value: params[:q][:name], class: 'form-control', placeholder: t(:name)
+ .col-md-3
+ .form-group
+ = label_tag :registrant
+ = f.search_field :registrant, value: params[:q][:registrant], class: 'form-control', placeholder: t(:registrant)
+ .col-md-3
+ .form-group
+ = label_tag t(:registrar)
+ = f.search_field :registrar, value: params[:q][:registrar], class: 'form-control', placeholder: t(:registrant)
+ .col-md-3
+ .form-group
+ = label_tag :action
+ = select_tag '[q][event]', options_for_select([['Update', 'update'], ['Destroy', 'destroy']], params[:q][:event]), { include_blank:true, multiple: false, placeholder: t(:choose), class: 'form-control js-combobox' }
+ .row
+ .col-md-3
+ .col-md-3
+ .col-md-3
+ .form-group
+ = label_tag t(:results_per_page)
+ = text_field_tag :results_per_page, params[:results_per_page], class: 'form-control', placeholder: t(:results_per_page)
+ .col-md-3{style: 'padding-top: 25px;'}
+ %button.btn.btn-primary
+
+ %span.glyphicon.glyphicon-search
+
+ %button.btn.btn-default.js-reset-form
+ = t(:clear_fields)
+%hr
+
+
+.row
+ .col-md-12
+ .table-responsive
+ %table.table.table-hover.table-bordered.table-condensed
+ %thead
+ %tr
+ %th{class: 'col-xs-2'}
+ = t(:name)
+ %th{class: 'col-xs-2'}
+ = t(:registrant)
+ %th{class: 'col-xs-2'}
+ = t(:registrar)
+ %th{class: 'col-xs-2'}
+ = t(:action_date)
+ %th{class: 'col-xs-2'}
+ = t(:action)
+ %tbody
+ - @versions.each do |version|
+ - if version.reify
+ %tr
+ %td= link_to(version.reify.name, admin_domain_version_path(version.id))
+ %td
+ - if version.reify.registrant
+ = link_to(version.reify.registrant, admin_registrant_path(version.reify.registrant))
+ %td
+ - if version.reify.registrar
+ = link_to(version.reify.registrar, admin_registrar_path(version.reify.registrar))
+ %td= l(version.created_at, format: :short)
+ %td= version.event
+
+
+.row
+ .col-md-6
+ = paginate @versions
+ .col-md-6.text-right
+
+
+:coffee
+ $(".js-reset-form").on "click", (e) ->
+ e.preventDefault();
+ window.location = "#{admin_domain_versions_path}"
diff --git a/app/views/admin/domain_versions/show.haml b/app/views/admin/domain_versions/show.haml
new file mode 100644
index 000000000..e413679be
--- /dev/null
+++ b/app/views/admin/domain_versions/show.haml
@@ -0,0 +1,123 @@
+- domain = Domain.new(@version.object.to_h)
+- @version.object_changes.to_h.each{|k,v| domain[k]=v.last}
+
+= render 'shared/title', name: @version.reify.name
+
+- if @version
+ - children = HashWithIndifferentAccess.new(@version.children)
+ - nameservers = Nameserver.all_versions_for(children[:nameservers], @version.created_at)
+ - dnskeys = Dnskey.all_versions_for(children[:dnskeys], @version.created_at)
+ - tech_contacts = Contact.all_versions_for(children[:tech_contacts], @version.created_at)
+ - admin_contacts = Contact.all_versions_for(children[:admin_contacts], @version.created_at)
+ - registrant = Contact.all_versions_for(children[:registrant], @version.created_at)
+ - event = @version.event
+ - creator = plain_username(@version.terminator)
+
+
+.row
+ .col-md-8
+ .panel.panel-default{:style => "min-height:400px;"}
+ .panel-heading
+ %h3.panel-title
+ = l(@version.created_at, format: :short)
+ = event
+ = creator
+ .panel-body
+ %dl.dl-horizontal
+ %dt= t(:name)
+ %dd= link_to(domain.name, admin_domain_path(@version.item_id))
+
+ %dt= t(:statuses)
+ %dd{class: changing_css_class(@version,"statuses")}
+ - if domain.statuses.present?
+ - domain.statuses.each do |s|
+ = s
+
+ %dt= t(:registrant)
+ %dd{class: changing_css_class(@version,"registrant_id")}
+ - registrant.each do |r|
+ - link = r.version_loader ? admin_contact_version_path(r.version_loader.try(:id)) : admin_contact_path(r.id)
+ = link_to link, target: "contact_#{r.id}" do
+ = r[:name]
+ = r[:phone]
+ = r[:email]
+ = r[:code]
+ %br
+
+ %dt= t(:admin_contacts)
+ %dd
+ - admin_contacts.each do |r|
+ - link = r.version_loader ? admin_contact_version_path(r.version_loader.try(:id)) : admin_contact_path(r.id)
+ = link_to link, target: "contact_#{r.id}" do
+ = r[:name]
+ = r[:phone]
+ = r[:email]
+ = r[:code]
+ %br
+
+ %dt= t(:tech_contacts)
+ %dd
+ - tech_contacts.each do |r|
+ - link = r.version_loader ? admin_contact_version_path(r.version_loader.try(:id)) : admin_contact_path(r.id)
+ = link_to link, target: "contact_#{r.id}" do
+ = r[:name]
+ = r[:phone]
+ = r[:email]
+ = r[:code]
+ %br
+
+ %dt= t(:nameservers)
+ %dd
+ - nameservers.each do |ns|
+ = ns[:hostname]
+ = ns[:ipv4]
+ = ns[:ipv6]
+ %br
+
+ %dt= t(:dnskeys)
+ %dd
+ - dnskeys.each do |ns|
+ = ns[:flags]
+ = ns[:protocol]
+ = ns[:alg]
+ - if ns[:public_key].present?
+ \...#{ns[:public_key].to_s[-20,20]}
+ %br
+
+ %dt= t(:registrar)
+ %dd{class: changing_css_class(@version,"registrar_id")}
+ = link_to admin_registrar_path(domain.registrar), target: "registrar_#{domain.registrar.id}" do
+ = domain.registrar.name
+
+ %span{:style => "padding-right:10px; padding-top:40px; float: right; bottom: 10px;"}
+ - if @version.previous
+ = link_to(t(:previous),
+ admin_domain_version_path(@version.previous.id),
+ class: 'btn btn-primary')
+ - else
+ %a.btn.btn-primary.disabled{:href => "#"}
+ %span= t(:previous)
+ - if @version.next
+ = link_to(t(:next),
+ admin_domain_version_path(@version.next.id),
+ class: 'btn btn-default')
+ - else
+ %a.btn.btn-default.disabled{:href => "#"}
+ %span= t(:next)
+
+ .col-md-4
+ .panel.panel-default{:style => "min-height:400px;"}
+ %ul.nav.nav-pills.nav-stacked
+ - @versions.each do |vs|
+ - if vs.id == @version.id and vs.reify
+ %li.active
+ = link_to admin_domain_version_path(vs.id) do
+ = l(vs.created_at, format: :short)
+ = vs.event
+ - else
+ %li
+ = link_to admin_domain_version_path(vs.id) do
+ = l(vs.created_at, format: :short)
+ = vs.event
+ %span{:style => "padding-left:10px; position: absolute; bottom: 10px;"}
+ = paginate @versions
diff --git a/app/views/admin/domains/partials/_version.haml b/app/views/admin/domains/partials/_version.haml
new file mode 100644
index 000000000..7cd9a497e
--- /dev/null
+++ b/app/views/admin/domains/partials/_version.haml
@@ -0,0 +1,127 @@
+- statuses_link ||= false
+- version ||= false
+- domain ||= false
+- pending_user ||= false
+
+- if domain.present?
+ - if version # normal history
+ - children = HashWithIndifferentAccess.new(version.children)
+ - nameservers = Nameserver.all_versions_for(children[:nameservers], version.created_at)
+ - dnskeys = Dnskey.all_versions_for(children[:dnskeys], version.created_at)
+ - tech_contacts = Contact.all_versions_for(children[:tech_contacts], version.created_at)
+ - admin_contacts = Contact.all_versions_for(children[:admin_contacts], version.created_at)
+ - registrant = Contact.all_versions_for(children[:registrant], version.created_at)
+ - event = version.event
+ - creator = plain_username(version.terminator)
+ - else # pending history
+ - nameservers = domain.nameservers
+ - dnskeys = domain.dnskeys
+ - tech_contacts = domain.tech_contacts
+ - admin_contacts = domain.admin_contacts
+ - registrant = [domain.registrant]
+ - creator = pending_user.try(:username)
+ - event = 'pending'
+
+ %td
+ %p.nowrap
+ = l(domain.updated_at, format: :shorts)
+ - if statuses_link
+ %br= link_to t(:edit_statuses), edit_admin_domain_path(params[:domain_id])
+
+ %p.text-right
+ = event
+ %br
+ = creator
+ - if version
+ %p.text-right
+ = link_to "Pure history", admin_domain_version_path(version)
+
+ %td{class: changing_css_class(version,"statuses")}
+ %p
+ - if domain.statuses.present?
+ - domain.statuses.each do |s|
+ = s
+ - if domain.status_notes.present?
+ - notes = domain.status_notes[s]
+ - if notes
+ %br
+ %i= notes
+ %br
+ - if domain.pending_json.present?
+ %p
+ = link_to t(:pending_epp), '#', class: 'js-pending-toggle'
+
+ %td{class: changing_css_class(version, "period", "period_unit", "valid_from", "valid_to")}
+ %p
+ = "#{domain.period}#{domain.period_unit}"
+ %br
+ = "#{l(domain.valid_from, format: :date)}"
+ %br
+ = "#{l(domain.valid_to, format: :date)}"
+
+ %td
+ - Array(registrant).each do |r|
+ - link = r.version_loader ? admin_contact_version_path(r.version_loader.try(:id)) : admin_contact_path(r.id)
+ = link_to link, target: "contact_#{r.id}" do
+ %p
+ = r[:name]
+ = r[:phone]
+ = r[:email]
+ %p
+ = r[:code]
+
+ %td
+ - Array(admin_contacts).each do |ac|
+ - link = ac.version_loader ? admin_contact_version_path(ac.version_loader.try(:id)) : admin_contact_path(ac.id)
+ = link_to link, target: "contact_#{ac.id}" do
+ %p
+ = ac[:name]
+ = ac[:phone]
+ = ac[:email]
+ %p
+ = ac[:code]
+
+ %td
+ - Array(tech_contacts).each do |tc|
+ - link = tc.version_loader ? admin_contact_version_path(tc.version_loader.try(:id)) : admin_contact_path(tc.id)
+ = link_to link, target: "contact_#{tc.id}" do
+ %p
+ = tc[:name]
+ = tc[:phone]
+ = tc[:email]
+ %p
+ = tc[:code]
+
+ %td
+ %p
+ - Array(nameservers).each do |ns|
+ = ns[:hostname]
+ %br
+ = ns[:ipv4].presence
+ = ns[:ipv6].presence
+ %br
+ %td
+ - Array(dnskeys).each do |ns|
+ %p
+ = ns.flags
+ = ns.protocol
+ = ns.alg
+ - if ns.public_key.present?
+ \...#{ns.public_key.to_s[-20,20]}
+
+ %td{class: changing_css_class(version,"registrar_id")}
+ - if domain.registrar
+ %p
+ = link_to admin_registrar_path(domain.registrar), target: "registrar_#{domain.registrar.id}" do
+ = domain.registrar.name
+
+ - if domain.pending_json.present?
+ %tr.js-pending{ style: 'display: none;' }
+ %td{colspan: 8}
+ = preserve do
+ %pre
+ - formatted_req = Nokogiri::XML(domain.pending_json['frame'])
+ - if formatted_req.errors.none?
+ = formatted_req
+ - else
+ = domain.pending_json['frame']
diff --git a/app/views/admin/domain_versions/index.haml b/app/views/admin/domains/versions.haml
similarity index 80%
rename from app/views/admin/domain_versions/index.haml
rename to app/views/admin/domains/versions.haml
index d948d53df..b803808aa 100644
--- a/app/views/admin/domain_versions/index.haml
+++ b/app/views/admin/domains/versions.haml
@@ -4,8 +4,8 @@
.row
.col-md-12
- .table-responsive
- %table.table-hover.table-bordered.table-condensed
+ .table-responsive{data: {doublescroll: true}}
+ %table.table.table-bordered.table-condensed
%thead
%tr
%th{class: 'col-xs-1'}= t(:timestap)
@@ -15,24 +15,25 @@
%th{class: 'col-xs-2'}= t(:admin)
%th{class: 'col-xs-2'}= t(:tech)
%th{class: 'col-xs-2'}= t(:nameservers)
+ %th{class: 'col-xs-2'}= t(:dnskeys)
%th{class: 'col-xs-2'}= t(:registrar)
%tbody
- if @pending_domain.present?
%tr.small
- = render 'admin/domain_versions/version',
+ = render 'admin/domains/partials/version',
domain: @pending_domain, pending_user: @pending_user, statuses_link: true
-# current version
- if @domain.versions.present?
%tr.small
- = render 'admin/domain_versions/version',
+ = render 'admin/domains/partials/version',
domain: @domain, version: @domain.versions.last
-# all other older versions
- @domain.versions.not_creates.reverse.each do |version|
%tr.small
- = render 'admin/domain_versions/version',
+ = render 'admin/domains/partials/version',
domain: version.reify, version: version.previous
:coffee
diff --git a/app/views/layouts/admin/application.haml b/app/views/layouts/admin/application.haml
index 53a73520c..9937937e6 100644
--- a/app/views/layouts/admin/application.haml
+++ b/app/views/layouts/admin/application.haml
@@ -57,6 +57,10 @@
%li= link_to t(:invoices), admin_invoices_path
%li= link_to t(:account_activities), admin_account_activities_path(created_after: 'today')
%li.divider
+ %li.dropdown-header= t(:archive)
+ %li= link_to t(:domains_history), admin_domain_versions_path
+ %li= link_to t(:contacts_history), admin_contact_versions_path
+ %li.divider
%li.dropdown-header= t(:system)
%li= link_to t(:settings), admin_settings_path
%li= link_to t(:zonefile), admin_zonefile_settings_path
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 2a7f82a20..a28390ae9 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -942,6 +942,8 @@ en:
if_auth_info_is_left_empty_it_will_be_auto_generated: 'If auth info is left empty, it will be auto generated.'
each_domain_name_must_end_with_colon_sign: 'Each domain name must end with colon (:) sign.'
expiration_remind_subject: 'The %{name} domain has expired'
+ next: 'Next'
+ previous: 'Previous'
personal_domain_verification_url: 'Personal domain verification url'
available_verification_url_not_found: 'Available verification url not found, for domain.'
contact_already_associated_with_the_domain: 'Object association prohibits operation, contact already associated with the domain'
diff --git a/config/routes.rb b/config/routes.rb
index ea5b55a7e..a041e41b7 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -198,7 +198,7 @@ Rails.application.routes.draw do
end
resources :domains do
- resources :domain_versions
+ resources :domain_versions, controller: 'domains', action: 'versions'
resources :pending_updates
resources :pending_deletes
member do
@@ -207,6 +207,18 @@ Rails.application.routes.draw do
end
end
+ resources :domain_versions do
+ collection do
+ get 'search'
+ end
+ end
+
+ resources :contact_versions do
+ collection do
+ get 'search'
+ end
+ end
+
resources :settings
resources :blocked_domains do
diff --git a/vendor/assets/javascripts/jquery.doubleScroll.js b/vendor/assets/javascripts/jquery.doubleScroll.js
new file mode 100644
index 000000000..45ff0642f
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.doubleScroll.js
@@ -0,0 +1,126 @@
+/*
+ * @name DoubleScroll
+ * @desc displays scroll bar on top and on the bottom of the div
+ * @requires jQuery
+ *
+ * @author Pawel Suwala - http://suwala.eu/
+ * @author Antoine Vianey - http://www.astek.fr/
+ * @version 0.5 (11-11-2015)
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Usage:
+ * https://github.com/avianey/jqDoubleScroll
+ */
+(function( $ ) {
+
+ jQuery.fn.doubleScroll = function(userOptions) {
+
+ // Default options
+ var options = {
+ contentElement: undefined, // Widest element, if not specified first child element will be used
+ scrollCss: {
+ 'overflow-x': 'auto',
+ 'overflow-y': 'hidden'
+ },
+ contentCss: {
+ 'overflow-x': 'auto',
+ 'overflow-y': 'hidden'
+ },
+ onlyIfScroll: true, // top scrollbar is not shown if the bottom one is not present
+ resetOnWindowResize: false, // recompute the top ScrollBar requirements when the window is resized
+ timeToWaitForResize: 30 // wait for the last update event (usefull when browser fire resize event constantly during ressing)
+ };
+
+ $.extend(true, options, userOptions);
+
+ // do not modify
+ // internal stuff
+ $.extend(options, {
+ topScrollBarMarkup: '