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: '
', + topScrollBarWrapperSelector: '.doubleScroll-scroll-wrapper', + topScrollBarInnerSelector: '.doubleScroll-scroll' + }); + + var _showScrollBar = function($self, options) { + + if (options.onlyIfScroll && $self.get(0).scrollWidth <= $self.width()) { + // content doesn't scroll + // remove any existing occurrence... + $self.prev(options.topScrollBarWrapperSelector).remove(); + return; + } + + // add div that will act as an upper scroll only if not already added to the DOM + var $topScrollBar = $self.prev(options.topScrollBarWrapperSelector); + + if ($topScrollBar.length == 0) { + + // creating the scrollbar + // added before in the DOM + $topScrollBar = $(options.topScrollBarMarkup); + $self.before($topScrollBar); + + // apply the css + $topScrollBar.css(options.scrollCss); + $self.css(options.contentCss); + + // bind upper scroll to bottom scroll + $topScrollBar.bind('scroll.doubleScroll', function() { + $self.scrollLeft($topScrollBar.scrollLeft()); + }); + + // bind bottom scroll to upper scroll + var selfScrollHandler = function() { + $topScrollBar.scrollLeft($self.scrollLeft()); + }; + $self.bind('scroll.doubleScroll', selfScrollHandler); + } + + // find the content element (should be the widest one) + var $contentElement; + + if (options.contentElement !== undefined && $self.find(options.contentElement).length !== 0) { + $contentElement = $self.find(options.contentElement); + } else { + $contentElement = $self.find('>:first-child'); + } + + // set the width of the wrappers + $(options.topScrollBarInnerSelector, $topScrollBar).width($contentElement.outerWidth()); + $topScrollBar.width($self.width()); + $topScrollBar.scrollLeft($self.scrollLeft()); + + } + + return this.each(function() { + + var $self = $(this); + + _showScrollBar($self, options); + + // bind the resize handler + // do it once + if (options.resetOnWindowResize) { + + var id; + var handler = function(e) { + _showScrollBar($self, options); + }; + + $(window).bind('resize.doubleScroll', function() { + // adding/removing/replacing the scrollbar might resize the window + // so the resizing flag will avoid the infinite loop here... + clearTimeout(id); + id = setTimeout(handler, options.timeToWaitForResize); + }); + + } + + }); + + } + +}( jQuery ));