Merge branch 'develop' of http://repository.trustie.net/xianbo/trustie2
Conflicts: app/helpers/bids_helper.rb app/views/bids/_homework_list.html.erb app/views/homework_attach/show.html.erb
This commit is contained in:
commit
c9e4fdee9d
16
Gemfile
16
Gemfile
|
@ -6,6 +6,7 @@ unless RUBY_PLATFORM =~ /w32/
|
|||
gem 'rubyzip'
|
||||
gem 'zip-zip'
|
||||
end
|
||||
|
||||
gem 'seems_rateable', path: 'lib/seems_rateable'
|
||||
gem "rails", "3.2.13"
|
||||
gem "jquery-rails", "~> 2.0.2"
|
||||
|
@ -15,6 +16,11 @@ gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
|
|||
gem "builder", "3.0.0"
|
||||
gem 'acts-as-taggable-on'
|
||||
|
||||
group :development do
|
||||
gem 'better_errors', path: 'lib/better_errors'
|
||||
gem 'rack-mini-profiler', path: 'lib/rack-mini-profiler'
|
||||
end
|
||||
|
||||
# Gems used only for assets and not required
|
||||
# in production environments by default.
|
||||
group :assets do
|
||||
|
@ -82,16 +88,6 @@ else
|
|||
warn("Please configure your config/database.yml first")
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem "rdoc", ">= 2.4.2"
|
||||
if nil
|
||||
gem 'thin'
|
||||
gem 'rack-mini-profiler'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
local_gemfile = File.join(File.dirname(__FILE__), "Gemfile.local")
|
||||
if File.exists?(local_gemfile)
|
||||
puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v`
|
||||
|
|
16
Gemfile.lock
16
Gemfile.lock
|
@ -1,3 +1,16 @@
|
|||
PATH
|
||||
remote: lib/better_errors
|
||||
specs:
|
||||
better_errors (1.1.0)
|
||||
coderay (>= 1.0.0)
|
||||
erubis (>= 2.6.6)
|
||||
|
||||
PATH
|
||||
remote: lib/rack-mini-profiler
|
||||
specs:
|
||||
rack-mini-profiler (0.9.1)
|
||||
rack (>= 1.1.3)
|
||||
|
||||
PATH
|
||||
remote: lib/seems_rateable
|
||||
specs:
|
||||
|
@ -122,6 +135,7 @@ DEPENDENCIES
|
|||
activerecord-jdbc-adapter (= 1.2.5)
|
||||
activerecord-jdbcmysql-adapter
|
||||
acts-as-taggable-on
|
||||
better_errors!
|
||||
builder (= 3.0.0)
|
||||
coderay (~> 1.0.6)
|
||||
coffee-rails (~> 3.2.1)
|
||||
|
@ -130,9 +144,9 @@ DEPENDENCIES
|
|||
jquery-rails (~> 2.0.2)
|
||||
mysql2 (~> 0.3.11)
|
||||
net-ldap (~> 0.3.1)
|
||||
rack-mini-profiler!
|
||||
rack-openid
|
||||
rails (= 3.2.13)
|
||||
rdoc (>= 2.4.2)
|
||||
ruby-openid (~> 2.1.4)
|
||||
sass-rails (~> 3.2.3)
|
||||
seems_rateable!
|
||||
|
|
|
@ -149,7 +149,6 @@ class AdminController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.html {
|
||||
@groups = Group.all.sort
|
||||
render :layout => @user_base_tag
|
||||
}
|
||||
format.api
|
||||
end
|
||||
|
@ -196,4 +195,43 @@ class AdminController < ApplicationController
|
|||
[:text_rmagick_available, Object.const_defined?(:Magick)]
|
||||
]
|
||||
end
|
||||
#管理功能用户列表的搜索
|
||||
def search
|
||||
sort_init 'login', 'asc'
|
||||
sort_update %w(login firstname lastname mail admin created_on last_login_on)
|
||||
|
||||
case params[:format]
|
||||
when 'xml', 'json'
|
||||
@offset, @limit = api_offset_and_limit({:limit => 15})
|
||||
else
|
||||
@limit = 15#per_page_option
|
||||
end
|
||||
|
||||
@status = params[:status] || 1
|
||||
has = {
|
||||
"show_changesets" => true
|
||||
}
|
||||
scope = User.logged.status(@status)
|
||||
scope = scope.like(params[:name]) if params[:name].present?
|
||||
@user_count = scope.count
|
||||
@user_pages = Paginator.new @user_count, @limit, params['page']
|
||||
@user_base_tag = params[:id] ? 'base_users':'base'
|
||||
@offset ||= @user_pages.reverse_offset
|
||||
unless @offset == 0
|
||||
@users = scope.offset(@offset).limit(@limit).all.reverse
|
||||
else
|
||||
limit = @user_count % @limit
|
||||
if limit == 0
|
||||
limit = @limit
|
||||
end
|
||||
@users = scope.offset(@offset).limit(limit).all.reverse
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@groups = Group.all.sort
|
||||
}
|
||||
format.api
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,9 +52,14 @@ class FilesController < ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
if params[:tag_name]
|
||||
if params[:add_tag]
|
||||
@addTag=true
|
||||
#render :back
|
||||
tag_saveEx
|
||||
render :text =>"success"
|
||||
#render :text =>"success"
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
else
|
||||
@addTag=false
|
||||
container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
|
||||
|
@ -64,7 +69,27 @@ class FilesController < ApplicationController
|
|||
if !attachments.empty? && !attachments[:files].blank? && Setting.notified_events.include?('file_added')
|
||||
Mailer.attachments_added(attachments[:files]).deliver
|
||||
end
|
||||
redirect_to project_files_path(@project)
|
||||
|
||||
# TODO: 临时用 nyan
|
||||
sort_init 'created_on', 'desc'
|
||||
sort_update 'created_on' => "#{Attachment.table_name}.created_on",
|
||||
'filename' => "#{Attachment.table_name}.filename",
|
||||
'size' => "#{Attachment.table_name}.filesize",
|
||||
'downloads' => "#{Attachment.table_name}.downloads"
|
||||
|
||||
@containers = [ Project.includes(:attachments).reorder("#{Attachment.table_name}.created_on DESC").find(@project.id)] #modify by Long Jun
|
||||
@containers += @project.versions.includes(:attachments).reorder("#{Attachment.table_name}.created_on DESC").all.sort
|
||||
|
||||
@attachtype = 0
|
||||
@contenttype = 0
|
||||
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.html {
|
||||
redirect_to project_files_path(@project)
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -102,11 +102,12 @@ class HomeworkAttachController < ApplicationController
|
|||
percent_m.to_s + "%"
|
||||
end
|
||||
@limit = 10
|
||||
@jours = @homework.journals_for_messages.order("created_on DESC")
|
||||
@jours = @homework.journals_for_messages.where("is_comprehensive_evaluation is null").order("created_on DESC")
|
||||
@feedback_count = @jours.count
|
||||
@feedback_pages = Paginator.new @feedback_count, @limit, params['page']
|
||||
@offset ||= @feedback_pages.offset
|
||||
@jour = @jours[@offset, @limit]
|
||||
@comprehensive_evaluation = @homework.journals_for_messages.where("is_comprehensive_evaluation is not null").order("created_on DESC")
|
||||
end
|
||||
|
||||
#删除留言
|
||||
|
@ -127,13 +128,14 @@ class HomeworkAttachController < ApplicationController
|
|||
#添加留言
|
||||
def addjours
|
||||
@homework = HomeworkAttach.find(params[:jour_id])
|
||||
@homework.addjours User.current.id, params[:new_form][:user_message],0
|
||||
@jours = @homework.journals_for_messages.order("created_on DESC")
|
||||
@add_jour = @homework.addjours User.current.id, params[:new_form][:user_message],0,params[:is_comprehensive_evaluation]
|
||||
@jours = @homework.journals_for_messages.where("is_comprehensive_evaluation is null").order("created_on DESC")
|
||||
@limit = 10
|
||||
@feedback_count = @jours.count
|
||||
@feedback_pages = Paginator.new @feedback_count, @limit, params['page']
|
||||
@offset ||= @feedback_pages.offset
|
||||
@jour = @jours[@offset, @limit]
|
||||
@comprehensive_evaluation = @homework.journals_for_messages.where("is_comprehensive_evaluation is not null").order("created_on DESC")
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
|
|
|
@ -76,8 +76,7 @@ class MembersController < ApplicationController
|
|||
# ProjectInfo.create(:name => "test", :user_id => 123)
|
||||
end
|
||||
## end
|
||||
AppliedProject.deleteappiled(user_id, @project.id)
|
||||
end
|
||||
end
|
||||
else
|
||||
members << Member.new(:role_ids => params[:membership][:role_ids], :user_id => params[:membership][:user_id])
|
||||
user_grades << UserGrade.new(:user_id => params[:membership][:user_id], :project_id => @project.id)
|
||||
|
@ -95,6 +94,13 @@ class MembersController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
if members.present? && members.all? {|m| m.valid? }
|
||||
members.each do |member|
|
||||
AppliedProject.deleteappiled(member.user_id, @project.id)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to_settings_in_projects }
|
||||
format.js { @members = members;@applied_members = applied_members; }
|
||||
|
|
|
@ -54,6 +54,7 @@ class MyController < ApplicationController
|
|||
@user.safe_attributes = params[:user]
|
||||
@user.pref.attributes = params[:pref]
|
||||
@user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
|
||||
@user.login = params[:login]
|
||||
unless @user.user_extensions.nil?
|
||||
if @user.user_extensions.identity == 2
|
||||
@user.firstname = params[:enterprise_name]
|
||||
|
|
|
@ -37,7 +37,7 @@ class ProjectsController < ApplicationController
|
|||
# before_filter :authorize, :except => [:new_join, :new_homework, :homework, :statistics, :search, :watcherlist, :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy, :member, :focus, :file,
|
||||
# :statistics, :feedback, :course, :enterprise_course, :course_enterprise, :project_respond, :share,
|
||||
# :show_projects_score, :issue_score_index, :news_score_index, :file_score_index, :code_submit_score_index, :projects_topic_score_index]
|
||||
before_filter :authorize, :only => [:show, :settings, :edit, :sort_project_members, :update, :modules, :close, :reopen,:view_homework_attaches]
|
||||
before_filter :authorize, :only => [:show, :settings, :edit, :sort_project_members, :update, :modules, :close, :reopen,:view_homework_attaches,:course]
|
||||
before_filter :authorize_global, :only => [:new, :create,:view_homework_attaches]
|
||||
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy, :calendar]
|
||||
before_filter :file, :statistics, :watcherlist
|
||||
|
@ -716,6 +716,11 @@ class ProjectsController < ApplicationController
|
|||
@document = @project.documents.build
|
||||
#
|
||||
@base_courses_tag = @project.project_type
|
||||
#判断能否显示真名(当前用户为课程的教师时显示真名)
|
||||
if @project.project_type == Project::ProjectType_course
|
||||
@teachers= searchTeacherAndAssistant(@project)
|
||||
@canShowRealName = isCourseTeacher(User.current.id)
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html{render :layout => 'base_courses' if @base_courses_tag==1}
|
||||
format.api
|
||||
|
|
|
@ -65,13 +65,13 @@ class UsersController < ApplicationController
|
|||
def user_projects
|
||||
|
||||
if User.current.admin?
|
||||
@memberships = @user.memberships.all
|
||||
@memberships = @user.memberships.all(conditions: "projects.project_type = #{Project::ProjectType_project}")
|
||||
else
|
||||
cond = Project.visible_condition(User.current) + " AND projects.project_type <> 1"
|
||||
@memberships = @user.memberships.all(:conditions => cond)
|
||||
end
|
||||
events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 20)
|
||||
@events_by_day = events.group_by(&:event_date)
|
||||
#events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 20)
|
||||
#@events_by_day = events.group_by(&:event_date)
|
||||
@state = 0
|
||||
|
||||
|
||||
|
@ -289,7 +289,7 @@ class UsersController < ApplicationController
|
|||
when '0'
|
||||
@offset ||= @user_pages.reverse_offset
|
||||
unless @offset == 0
|
||||
@users_statuses = scope.offset(@offset).limit(@limit).all.reverse
|
||||
@users_statuses = scope.offset(@offset).limit(@limit).all.reverse
|
||||
else
|
||||
limit = @user_count % @limit
|
||||
if limit == 0
|
||||
|
@ -313,7 +313,7 @@ class UsersController < ApplicationController
|
|||
end
|
||||
@s_type = 1
|
||||
#sort {|x,y| y.user_status.changesets_count <=> x.user_status.changesets_count}
|
||||
#@users = @users[@offset, @limit]
|
||||
#@users = @users[@offset, @limit]
|
||||
when '2'
|
||||
@offset ||= @user_pages.reverse_offset
|
||||
unless @offset == 0
|
||||
|
@ -326,9 +326,9 @@ class UsersController < ApplicationController
|
|||
@users_statuses = scope.reorder('watchers_count').offset(@offset).limit(limit).all.reverse
|
||||
end
|
||||
@s_type = 2
|
||||
#@users = @users[@offset, @limit]
|
||||
#@users = @users[@offset, @limit]
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
@offset ||= @user_pages.reverse_offset
|
||||
unless @offset == 0
|
||||
|
@ -565,7 +565,7 @@ class UsersController < ApplicationController
|
|||
end
|
||||
|
||||
def watch_projects
|
||||
@watch_projects = Project.joins(:watchers).where("project_type <>? and watchable_type = ? and watchers.user_id = ?", '1','Project', @user.id)
|
||||
@watch_projects = Project.joins(:watchers).where("project_type <>? and watchable_type = ? and `watchers`.user_id = ?", '1','Project', @user.id)
|
||||
@state = 1
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
|
@ -689,7 +689,7 @@ class UsersController < ApplicationController
|
|||
end
|
||||
|
||||
def tag_saveEx
|
||||
@tags = params[:tag_name][:name]
|
||||
@tags = params[:tag_name]
|
||||
@obj_id = params[:obj_id]
|
||||
@obj_flag = params[:obj_flag]
|
||||
|
||||
|
|
|
@ -58,9 +58,14 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
# Displays a link to user's account page if active
|
||||
def link_to_user(user, options={})
|
||||
def link_to_user(user, canShowRealName = false, options={})
|
||||
if user.is_a?(User)
|
||||
name = h(user.name(options[:format]))
|
||||
if canShowRealName
|
||||
name = h(user.realname(options[:format]))
|
||||
else
|
||||
name = h(user.name(options[:format]))
|
||||
end
|
||||
|
||||
if user.active? || (User.current.admin? && user.logged?)
|
||||
link_to name, {:controller=> 'users', :action => 'show', id: user.id, host: Setting.user_domain}, :class => user.css_classes
|
||||
else
|
||||
|
|
|
@ -155,6 +155,16 @@ module BidsHelper
|
|||
end
|
||||
people.include?(User.current)
|
||||
end
|
||||
#当前用户是不是指定课程的学生
|
||||
def is_cur_course_student? course
|
||||
people = []
|
||||
course.members.each do |member|
|
||||
if [5,10].include? member.roles.first.id
|
||||
people << member.user
|
||||
end
|
||||
end
|
||||
people.include?(User.current)
|
||||
end
|
||||
|
||||
# def select_option_helper option
|
||||
# tmp = Hash.new
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
#enconding:utf-8
|
||||
# fq
|
||||
module BidsHelper
|
||||
|
||||
def render_notes(bid, journal, options={})
|
||||
content = ''
|
||||
removable = User.current == journal.user || User.current == bid.author
|
||||
links = []
|
||||
if !journal.notes.blank?
|
||||
links << link_to(image_tag('comment.png'),
|
||||
{:controller => 'bids', :action => 'new', :id => bid, :journal_id => journal},
|
||||
:remote => true,
|
||||
:method => 'post',
|
||||
:title => l(:button_quote)) if options[:reply_links]
|
||||
if removable
|
||||
url = {:controller => 'bids',
|
||||
:action => 'destroy',
|
||||
:object_id => journal,
|
||||
:id => bid}
|
||||
links << ' '
|
||||
links << link_to(image_tag('delete.png'), url,
|
||||
:remote => true, :method => 'delete', :class => "delete", :title => l(:button_delete))
|
||||
end
|
||||
end
|
||||
content << content_tag('div', links.join(' ').html_safe, :class => 'contextual') unless links.empty?
|
||||
content << textilizable(journal.notes)
|
||||
css_classes = "wiki"
|
||||
content_tag('div', content.html_safe, :id => "journal-#{journal.id}-notes", :class => css_classes)
|
||||
end
|
||||
|
||||
def link_to_in_place_notes_editor(text, field_id, url, options={})
|
||||
onclick = "$.ajax({url: '#{url_for(url)}', type: 'get'}); return false;"
|
||||
link_to text, '#', options.merge(:onclick => onclick)
|
||||
end
|
||||
|
||||
# this method is used to get all projects that tagged one tag
|
||||
# added by william
|
||||
def get_bids_by_tag(tag_name)
|
||||
Bid.tagged_with(tag_name).order('updated_on desc')
|
||||
end
|
||||
|
||||
#added by huang
|
||||
def sort_bid_enterprise(state, project_type)
|
||||
content = ''.html_safe
|
||||
case state
|
||||
when 0
|
||||
content << content_tag('li', link_to(l(:label_sort_by_active), calls_path(:bid_sort_type => '1', :project_type => project_type)))
|
||||
content << content_tag('li', link_to(l(:label_sort_by_time), calls_path(:bid_sort_type => '0', :project_type => project_type), :class=>"selected"), :class=>"selected")
|
||||
|
||||
when 1
|
||||
content << content_tag('li', link_to(l(:label_sort_by_active), calls_path(:bid_sort_type => '1', :project_type => project_type), :class=>"selected"), :class=>"selected")
|
||||
content << content_tag('li', link_to(l(:label_sort_by_time), calls_path(:bid_sort_type => '0', :project_type => project_type)))
|
||||
end
|
||||
content = content_tag('ul', content)
|
||||
content_tag('div', content, :class => "tabs_enterprise")
|
||||
end
|
||||
#end
|
||||
|
||||
def sort_bid(state, project_type)
|
||||
content = ''.html_safe
|
||||
case state
|
||||
when 0
|
||||
content << content_tag('li', link_to(l(:label_sort_by_active), calls_path(:bid_sort_type => '1', :project_type => project_type)))
|
||||
content << content_tag('li', link_to(l(:label_sort_by_time), calls_path(:bid_sort_type => '0', :project_type => project_type), :class=>"selected"), :class=>"selected")
|
||||
|
||||
when 1
|
||||
content << content_tag('li', link_to(l(:label_sort_by_active), calls_path(:bid_sort_type => '1', :project_type => project_type), :class=>"selected"), :class=>"selected")
|
||||
content << content_tag('li', link_to(l(:label_sort_by_time), calls_path(:bid_sort_type => '0', :project_type => project_type)))
|
||||
end
|
||||
content = content_tag('ul', content)
|
||||
content_tag('div', content, :class => "tabs")
|
||||
end
|
||||
|
||||
#huang
|
||||
def sort_contest(state)
|
||||
content = ''.html_safe
|
||||
case state
|
||||
when 0
|
||||
content << content_tag('li', link_to(l(:label_sort_by_active), contest_path(:contest_sort_type => '1')))
|
||||
content << content_tag('li', link_to(l(:label_sort_by_time), contest_path(:contest_sort_type => '0'), :class=>"selected"), :class=>"selected")
|
||||
|
||||
when 1
|
||||
content << content_tag('li', link_to(l(:label_sort_by_active), contest_path(:contest_sort_type => '1'), :class=>"selected"), :class=>"selected")
|
||||
content << content_tag('li', link_to(l(:label_sort_by_time), contest_path(:contest_sort_type => '0')))
|
||||
end
|
||||
content = content_tag('ul', content)
|
||||
content_tag('div', content, :class => "tabs")
|
||||
end
|
||||
#end
|
||||
|
||||
def course_options_for_select(courses)
|
||||
# <option value = '0'>#{l(:label_choose_reward)}</option>
|
||||
html = ''
|
||||
courses.each do |course|
|
||||
html << "<option value = #{course.id}>"
|
||||
html << course.name
|
||||
html << "</option>"
|
||||
end
|
||||
html.html_safe
|
||||
end
|
||||
|
||||
|
||||
# used to get the reward and handle the value which can be used to display in views
|
||||
# added by william
|
||||
def get_prize(b_project)
|
||||
b_project.get_reward
|
||||
end
|
||||
|
||||
def count_bid_project
|
||||
bids = Bid.where('parent_id = ?', @bid.id)
|
||||
@projects = []
|
||||
for bid in bids
|
||||
@projects += bid.biding_projects
|
||||
end
|
||||
@projects.count
|
||||
end
|
||||
|
||||
def count_bid_user
|
||||
bids = Bid.where('parent_id = ?', @bid.id)
|
||||
@users = []
|
||||
for bid in bids
|
||||
for project in bid.projects
|
||||
@users += project.users
|
||||
end
|
||||
end
|
||||
@users.count
|
||||
end
|
||||
|
||||
# 查看学号
|
||||
def im_watching_student_id? bid
|
||||
people = []
|
||||
people << bid.author
|
||||
case bid.reward_type # 天煞的bid分了三用途,里面各种hasmany还不定能用!
|
||||
when Bid::Enterprise
|
||||
when Bid::Contest
|
||||
bid.join_in_contests.each do |jic|
|
||||
people << jic.user
|
||||
end
|
||||
when Bid::Homework
|
||||
# people += bid.courses.first.users.to_a
|
||||
people += searchTeacherAndAssistant(bid.courses.first).to_a
|
||||
else
|
||||
raise 'bids_helper: unknow bid type' # 出了错看这里!不知道的抛异常,省的找不到出错的地方!
|
||||
end
|
||||
people.include?(User.current)
|
||||
end
|
||||
|
||||
# 当前用户是否加入了此课程(包括教师)
|
||||
def is_cur_course_user? bid
|
||||
people = []
|
||||
#people << bid.author
|
||||
course = bid.courses.first
|
||||
course.members.each do |member|
|
||||
people << member.user
|
||||
end
|
||||
people.include?(User.current)
|
||||
end
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
#当前用户是不是指定课程的学生
|
||||
def is_cur_course_student? course
|
||||
people = []
|
||||
course.members.each do |member|
|
||||
if [5,10].include? member.roles.first.id
|
||||
people << member.user
|
||||
end
|
||||
end
|
||||
people.include?(User.current)
|
||||
end
|
||||
>>>>>>> 8c33cf0afb89556112710759615cbd88bb0326f2
|
||||
|
||||
# def select_option_helper option
|
||||
# tmp = Hash.new
|
||||
# option.each do |project|
|
||||
# tmp[project.name] = project.identifier
|
||||
# end
|
||||
# tmp
|
||||
# end
|
||||
|
||||
def can_delete_project_homework bind_project,current_user
|
||||
current_user.id == bind_project.user.id || current_user.admin
|
||||
end
|
||||
|
||||
end
|
|
@ -137,14 +137,24 @@ module CoursesHelper
|
|||
Course.find_by_extra(try(extra))
|
||||
end
|
||||
#判断制定用户是不是当前课程的老师
|
||||
def is_course_teacher user,project
|
||||
is_teacher = false
|
||||
searchTeacherAndAssistant(project).each do |teacher|
|
||||
if user == teacher
|
||||
is_teacher = true
|
||||
break
|
||||
def is_course_teacher user,course
|
||||
people = []
|
||||
course.members.each do |member|
|
||||
role_id = member.roles.first.id
|
||||
if TeacherRoles.include? role_id
|
||||
people << member.user
|
||||
end
|
||||
end
|
||||
is_teacher
|
||||
people.include?(user)
|
||||
end
|
||||
#当前用户是不是指定课程的学生
|
||||
def is_cur_course_student? course
|
||||
people = []
|
||||
course.members.each do |member|
|
||||
if StudentRoles.include? member.roles.first.id
|
||||
people << member.user
|
||||
end
|
||||
end
|
||||
people.include?(User.current)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,5 +49,9 @@ module TagsHelper
|
|||
end
|
||||
return @result
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
def tagname_val
|
||||
("#tag_name_name").value
|
||||
end
|
|
@ -69,28 +69,30 @@ module UsersHelper
|
|||
# <li><%= link_to("所有反馈", {:controller => 'users', :action => 'show', :type => 2}) %></li>
|
||||
# </ul></div>
|
||||
|
||||
def show_activity(state)
|
||||
content = ''.html_safe
|
||||
case state
|
||||
when 0
|
||||
s = content_tag('span', l(:label_user_all_activity), :class => "current-page")
|
||||
content << content_tag('li', s)
|
||||
content << content_tag('li', link_to(l(:label_user_activity_myself), {:controller => 'users', :action => 'show', :type => 1}))
|
||||
content << content_tag('li', link_to(l(:label_user_all_respond), {:controller => 'users', :action => 'show', :type => 2}))
|
||||
when 1
|
||||
s = content_tag('span', l(:label_user_activity_myself), :class => "current-page")
|
||||
content << content_tag('li', link_to(l(:label_user_all_activity), {:controller => 'users', :action => 'show'}))
|
||||
content << content_tag('li', s, :class => "current-page")
|
||||
content << content_tag('li', link_to(l(:label_user_all_respond), {:controller => 'users', :action => 'show', :type => 2}))
|
||||
when 2
|
||||
s = content_tag('span', l(:label_user_all_respond), :class => "current-page")
|
||||
content << content_tag('li', link_to(l(:label_user_all_activity), {:controller => 'users', :action => 'show'}))
|
||||
content << content_tag('li', link_to(l(:label_user_activity_myself), {:controller => 'users', :action => 'show', :type => 1}))
|
||||
content << content_tag('li', s, :class => "current-page")
|
||||
end
|
||||
content_tag('div', content, :class => "pagination")
|
||||
end
|
||||
# TODO: 待删
|
||||
# def show_activity(state)
|
||||
# content = ''.html_safe
|
||||
# case state
|
||||
# when 0
|
||||
# s = content_tag('span', l(:label_user_all_activity), :class => "current-page")
|
||||
# content << content_tag('li', s)
|
||||
# content << content_tag('li', link_to(l(:label_user_activity_myself), {:controller => 'users', :action => 'show', :type => 1}))
|
||||
# content << content_tag('li', link_to(l(:label_user_all_respond), {:controller => 'users', :action => 'show', :type => 2}))
|
||||
# when 1
|
||||
# s = content_tag('span', l(:label_user_activity_myself), :class => "current-page")
|
||||
# content << content_tag('li', link_to(l(:label_user_all_activity), {:controller => 'users', :action => 'show'}))
|
||||
# content << content_tag('li', s, :class => "current-page")
|
||||
# content << content_tag('li', link_to(l(:label_user_all_respond), {:controller => 'users', :action => 'show', :type => 2}))
|
||||
# when 2
|
||||
# s = content_tag('span', l(:label_user_all_respond), :class => "current-page")
|
||||
# content << content_tag('li', link_to(l(:label_user_all_activity), {:controller => 'users', :action => 'show'}))
|
||||
# content << content_tag('li', link_to(l(:label_user_activity_myself), {:controller => 'users', :action => 'show', :type => 1}))
|
||||
# content << content_tag('li', s, :class => "current-page")
|
||||
# end
|
||||
# content_tag('div', content, :class => "pagination")
|
||||
# end
|
||||
|
||||
#TODO: 待删
|
||||
def watch_projects(state)
|
||||
content = ''.html_safe
|
||||
case state
|
||||
|
|
|
@ -13,8 +13,8 @@ class HomeworkAttach < ActiveRecord::Base
|
|||
"user_id"
|
||||
acts_as_attachable
|
||||
|
||||
def addjours user_id,message,status = 0
|
||||
jfm = self.journals_for_messages.build(:user_id => user_id,:notes =>message,:status => status)
|
||||
def addjours user_id,message,status = 0,is_comprehensive_evaluation = 0
|
||||
jfm = self.journals_for_messages.build(:user_id => user_id,:notes =>message,:status => status,:is_comprehensive_evaluation => is_comprehensive_evaluation)
|
||||
jfm.save
|
||||
jfm
|
||||
end
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# Added by young
|
||||
class JournalsForMessageObserver < ActiveRecord::Observer
|
||||
def after_create(journals_for_message)
|
||||
Thread.new do
|
||||
Mailer.journals_for_message_add(User.current, journals_for_message).deliver
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class Mailer < ActionMailer::Base
|
|||
helper :custom_fields
|
||||
|
||||
include Redmine::I18n
|
||||
|
||||
include CoursesHelper
|
||||
def self.default_url_options
|
||||
{ :host => Setting.host_name, :protocol => Setting.protocol }
|
||||
end
|
||||
|
@ -44,6 +44,7 @@ class Mailer < ActionMailer::Base
|
|||
when :Bid
|
||||
respond_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}")
|
||||
when :Project
|
||||
return -1 if journals_for_message.jour.project_type == Project::ProjectType_project
|
||||
project_feedback_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}")
|
||||
when :Contest
|
||||
show_contest_contest_url(journals_for_message.jour, anchor: "word_li_#{journals_for_message.id}")
|
||||
|
@ -53,7 +54,23 @@ class Mailer < ActionMailer::Base
|
|||
logger.error "[Builds a Mail::Message ERROR] journalsForMessage's jour is unkown type, journalsForMessage.id = #{journals_for_message.id}"
|
||||
return -1
|
||||
end
|
||||
mail :to => @mail.mail, :subject => @title
|
||||
#如果是直接留言并且留言对象是Project并且Project类型是课程
|
||||
if !journals_for_message.at_user && journals_for_message.jour.class.to_s.to_sym == :Project && journals_for_message.jour.project_type == 1
|
||||
project = journals_for_message.jour
|
||||
#课程的教师
|
||||
@teachers = searchTeacherAndAssistant journals_for_message.jour
|
||||
#收件人邮箱
|
||||
@recipients ||= []
|
||||
@teachers.each do |teacher|
|
||||
@recipients << teacher.user.mail
|
||||
end
|
||||
mail :to => @recipients,
|
||||
:subject => "#{l(:label_your_course)}#{journals_for_message.jour.name}#{l(:label_have_message)} "
|
||||
else
|
||||
mail :to => @mail.mail, :subject => @title
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
# Builds a Mail::Message object used to email recipients of the added issue.
|
||||
|
@ -423,7 +440,10 @@ class Mailer < ActionMailer::Base
|
|||
headers[:to].delete(@author.mail) if headers[:to].is_a?(Array)
|
||||
headers[:cc].delete(@author.mail) if headers[:cc].is_a?(Array)
|
||||
end
|
||||
|
||||
if !User.current.nil?
|
||||
#不给本人发邮件
|
||||
headers[:to].delete(User.current.mail) if headers[:to].is_a?(Array)
|
||||
end
|
||||
if @author && @author.logged?
|
||||
redmine_headers 'Sender' => @author.login
|
||||
end
|
||||
|
|
|
@ -686,7 +686,12 @@ class User < Principal
|
|||
(block_given? ? yield(role, self) : true)
|
||||
}
|
||||
else
|
||||
false
|
||||
if admin?
|
||||
return true
|
||||
end
|
||||
#无项目时 查看Non member(id为1)角色是否有权限执行action
|
||||
Role.find('1').allowed_to?(action)
|
||||
# false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,11 +1,33 @@
|
|||
<%= call_hook :view_account_login_top %>
|
||||
|
||||
<script type="text/javascript" language="javascript">
|
||||
function clearInfo(id, content) {
|
||||
var text = $('#' + id);
|
||||
if (text.val() == content) {
|
||||
$('#' + id).val('');
|
||||
}
|
||||
}
|
||||
|
||||
function showInfo(id, content) {
|
||||
var text = $('#' + id);
|
||||
if (text.val() == '') {
|
||||
$('#' + id).val(content);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="login-form">
|
||||
<%= form_tag(signin_path) do %>
|
||||
<%= back_url_hidden_field_tag %>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="right"><label for="username"><%=l(:field_login)%>:</label></td>
|
||||
<td align="left"><%= text_field_tag 'username', params[:username], :tabindex => '1' %></td>
|
||||
<td align="right"><label for="username"><%=l(:lable_user_name)%>:</label></td>
|
||||
<td align="left">
|
||||
<%= text_field_tag 'username', params[:username], :tabindex => '1' , :value => "#{l(:label_login_prompt)}",
|
||||
:onfocus => "clearInfo('username','#{l(:label_login_prompt)}')",
|
||||
:onblur => "showInfo('username','#{l(:label_login_prompt)}')",
|
||||
:style => "resize: none;font-size: 12px;color: #818283;"%>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right"><label for="password"><%=l(:field_password)%>:</label></td>
|
||||
|
|
|
@ -279,7 +279,7 @@
|
|||
|
||||
<% if @user.auth_source_id.nil? %>
|
||||
<p><%= f.text_field :login, :size => 25, :required => true %><span id="valid_user_login"></span>
|
||||
<em class="info"><%= l(:label_max_number) %></em></p>
|
||||
<em class="info"><%= l(:label_max_number) %></em></p>
|
||||
<p><%= f.password_field :password, :size => 25, :required => true %>
|
||||
<em class="info"><%= l(:text_caracters_minimum, :count => Setting.password_min_length) %></em></p>
|
||||
|
||||
|
@ -302,10 +302,16 @@
|
|||
<!-- added by bai 增加了身份、性别和地区-->
|
||||
|
||||
<span id = 'gender' style = 'display:none'>
|
||||
<p><table><tr><td class="info" align="right" style="width: 90px"><strong><%= l(:label_gender) %><span class="required"> </span></strong></td>
|
||||
<td class="info" style="width: 10px">
|
||||
<%= select_tag 'gender', "<option value = '0'>#{l(:label_gender_male)}</option>
|
||||
<option value = '1'>#{l(:label_gender_female)}</option>".html_safe %></td></tr></table></p></span>
|
||||
<p>
|
||||
<table><tr>
|
||||
<td class="info" align="right" style="width: 90px"><strong><%= l(:label_gender) %><span class="required"> </span></strong></td>
|
||||
<td class="info" style="width: 10px">
|
||||
<%= select_tag 'gender', "<option value = '0'>#{l(:label_gender_male)}</option>
|
||||
<option value = '1'>#{l(:label_gender_female)}</option>".html_safe %>
|
||||
</td>
|
||||
</tr></table>
|
||||
</p>
|
||||
</span>
|
||||
|
||||
|
||||
<p><table><tr><td class="info" align="right" style="width: 90px"><strong><%= l(:label_location) %><span class="required"> *</span></strong></td>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<% if User.current.admin? %>
|
||||
<div class="contextual">
|
||||
<%= link_to l(:label_user_new), new_user_path, :class => 'icon icon-add' %>
|
||||
</div>
|
||||
|
||||
<h3><%= l(:label_user_plural)%></h3>
|
||||
|
||||
<%= form_tag(:controller => 'admin', :action => 'search', :method => :get) do %>
|
||||
<fieldset>
|
||||
<legend>
|
||||
<%= l(:label_filter_plural) %>
|
||||
</legend>
|
||||
<label for='status'><%= l(:field_status) %>:</label>
|
||||
<%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %>
|
||||
|
||||
<% if @groups.present? %>
|
||||
<label for='group_id'><%= l(:label_group) %>:</label>
|
||||
<%= select_tag 'group_id', content_tag('option') + options_from_collection_for_select(@groups, :id, :name, params[:group_id].to_i), :onchange => "this.form.submit(); return false;" %>
|
||||
<% end %>
|
||||
|
||||
<label for='name'><%= l(:label_user) %>:</label>
|
||||
<%= text_field_tag 'name', params[:name], :size => 30 %>
|
||||
<%= submit_tag l(:label_search), :class => "small", :name => nil %>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
|
||||
|
||||
<div class="autoscroll">
|
||||
<table class="list">
|
||||
<thead>
|
||||
<tr>
|
||||
<%= sort_header_tag('login', :caption => l(:field_login)) %>
|
||||
<%= sort_header_tag('firstname', :caption => l(:field_firstname)) %>
|
||||
<%= sort_header_tag('lastname', :caption => l(:field_lastname)) %>
|
||||
<%= sort_header_tag('mail', :caption => l(:field_mail)) %>
|
||||
|
||||
<%= sort_header_tag('admin', :caption => l(:field_admin), :default_order => 'desc') %>
|
||||
<%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %>
|
||||
<%= sort_header_tag('last_login_on', :caption => l(:field_last_login_on), :default_order => 'desc') %>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for user in @users -%>
|
||||
<tr class="<%= user.css_classes %> <%= cycle("odd", "even") %>">
|
||||
<td class="username"><%= avatar(user, :size => "14") %><%= link_to h(user.login), edit_user_path(user) %></td>
|
||||
<td class="firstname"><%= h(user.firstname) %></td>
|
||||
<td class="lastname"><%= h(user.lastname) %></td>
|
||||
<td class="email"><%= mail_to(h(user.mail)) %></td>
|
||||
<td align="center"><%= checked_image user.admin? %></td>
|
||||
<td class="created_on" align="center"><%= format_time(user.created_on) %></td>
|
||||
<td class="last_login_on" align="center"><%= format_time(user.last_login_on) unless user.last_login_on.nil? %></td>
|
||||
<td class="buttons"> <%= change_status_link(user) %>
|
||||
<%= delete_link user_path(user, :back_url => users_path(params)) unless User.current == user %> </td>
|
||||
</tr>
|
||||
<% end -%>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<ul>
|
||||
<%= pagination_links_full @user_pages, @user_count %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<% html_title(l(:label_user_plural)) -%>
|
||||
|
||||
<% end %>
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
<h3><%= l(:label_user_plural)%></h3>
|
||||
|
||||
<%= form_tag(:controller => 'users', :action => 'search', :method => :get) do %>
|
||||
<%= form_tag(:controller => 'admin', :action => 'search', :method => :get) do %>
|
||||
<fieldset>
|
||||
<legend>
|
||||
<%= l(:label_filter_plural) %>
|
||||
|
|
|
@ -12,6 +12,5 @@ fileSpan.find('a.remove-upload')
|
|||
})
|
||||
.off('click');
|
||||
var divattach = fileSpan.find('div.div_attachments');
|
||||
divattach.html('<%#= j(render :partial => 'tags/tagEx', :locals => {:obj => @attachment, :object_flag => "6"})%>');
|
||||
|
||||
divattach.html('<%= j(render :partial => 'tags/tagEx', :locals => {:obj => @attachment, :object_flag => "6"})%>');
|
||||
<% end %>
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
<!-- fq -->
|
||||
<% is_teacher = is_course_teacher User.current,@bid.courses.first.project %>
|
||||
<% is_student = is_cur_course_student? @bid.courses.first %>
|
||||
<% is_teacher = is_course_teacher User.current,@bid.courses.first %>
|
||||
|
||||
<%= form_tag(:controller => 'bids', :action => "show_project", :method => :get) do %>
|
||||
<div class="project-search-block">
|
||||
<table width="100%" valign="center">
|
||||
<tr>
|
||||
<td ><span style="margin-left:0px"><%= l(:label_task_plural)%>(<%= @homework_list.count%>)</span>
|
||||
<%= link_to "作业打包下载", zipdown_assort_path(obj_class: @bid.class, obj_id: @bid), remote: false, class: "button_submit button_submit_font_white", style: "margin: 5px 10px;line-height: 20px;height: 20px;display: inline-block;" if(
|
||||
User.current.admin? ||
|
||||
!(User.current.roles_for_project(@bid.courses.first).map(&:id) & ([7,9])).empty? ) ||
|
||||
User.current.admin? ||
|
||||
!(User.current.roles_for_project(@bid.courses.first).map(&:id) & ([7,9])).empty? ) ||
|
||||
(Rails.env.development?) %>
|
||||
</td>
|
||||
<td align="right">
|
||||
|
@ -25,8 +27,8 @@
|
|||
<% @homework_list.each do |homework|%>
|
||||
<% if homework.attachments.any?%>
|
||||
<table width="660px" border="0" align="center">
|
||||
<tr>
|
||||
<td colspan="2" valign="top" width="50" ><%= image_tag(url_to_avatar(homework.user), :class => "avatar")%></td>
|
||||
<tr>
|
||||
<td colspan="2" valign="top" width="50" ><%= image_tag(url_to_avatar(homework.user), :class => "avatar")%></td>
|
||||
<td>
|
||||
<table width="580px" border="0">
|
||||
<tr>
|
||||
|
@ -44,14 +46,18 @@
|
|||
<% end %>
|
||||
</td>
|
||||
<td style="vertical-align: top">
|
||||
<% if is_cur_course_user? @bid %>
|
||||
<% if is_student %>
|
||||
<%= link_to "互评>>" , homework_attach_path(homework)%>
|
||||
<% else %>
|
||||
<% if is_teacher %>
|
||||
<%= link_to "综评>>" , homework_attach_path(homework)%>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1" valign="top" style="width: 300px">
|
||||
<strong>发布人: <%= link_to (display_id ? homework.user.realname : homework.user ), user_path(homework.user)%></strong>
|
||||
<strong>发布人: <%= link_to ( is_teacher ? homework.user.realname : homework.user ), user_path(homework.user)%></strong>
|
||||
</td>
|
||||
<td>
|
||||
<strong>作业评分:</strong>
|
||||
|
@ -71,7 +77,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" valign="top">
|
||||
<% if display_id %>
|
||||
<% if is_cur_course_user? @bid %>
|
||||
<strong><%= l(:label_bidding_user_studentcode) %> : <%= homework.user.user_extensions.student_id%></strong>
|
||||
<% end %>
|
||||
</td>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<% versions = project.versions.sort %>
|
||||
<% attachmenttypes = project.attachmenttypes %>
|
||||
<%= error_messages_for 'attachment' %>
|
||||
<%= form_tag(project_files_path(project), :multipart => true,:name=>"upload_form", :class => "tabular") do %>
|
||||
<%= form_tag(project_files_path(project), :multipart => true,:remote => false,:method => :post,:name=>"upload_form", :class => "tabular") do %>
|
||||
<div class="box">
|
||||
<p>
|
||||
<table>
|
||||
|
|
|
@ -25,9 +25,9 @@
|
|||
:onchange => "attachment_contenttypes_searchex(this.value)" %>
|
||||
<% end %>
|
||||
|
||||
<div id="upload_file_div" class="relation_file_div hidden">
|
||||
<%= render :partial => 'new', locals: {project: @project} %>
|
||||
</div>
|
||||
<div id="upload_file_div" class="relation_file_div hidden">
|
||||
<%= render :partial => 'new', locals: {project: @project} %>
|
||||
</div>
|
||||
|
||||
<div id="relation_file_div" class="relation_file_div hidden">
|
||||
<fieldset>
|
||||
|
@ -114,7 +114,7 @@
|
|||
;
|
||||
});
|
||||
|
||||
function eval_ajax (xhr, textStatus) {
|
||||
function eval_ajax(xhr, textStatus) {
|
||||
if (textStatus == 'success') {
|
||||
eval(xhr.responseText);
|
||||
} else if (textStatus == 'error') {
|
||||
|
@ -128,7 +128,7 @@
|
|||
type: "POST",
|
||||
data: {
|
||||
type: encodeURIComponent(value),
|
||||
contentType:$('#attach_sufix_browse').val()
|
||||
contentType: $('#attach_sufix_browse').val()
|
||||
}
|
||||
|
||||
}).complete(eval_ajax);
|
||||
|
@ -179,8 +179,8 @@
|
|||
contentType: $('#attach_sufix_browse').val()
|
||||
}
|
||||
}).error(function () {
|
||||
alert('error');
|
||||
});
|
||||
alert('error');
|
||||
});
|
||||
} else if (textStatus == 'error') {
|
||||
alert('An error has occurred');
|
||||
}
|
||||
|
@ -190,22 +190,21 @@
|
|||
|
||||
|
||||
<script type='text/javascript'>
|
||||
function tagAddClick(divid,objId,objTag)
|
||||
{
|
||||
function tagAddClick(divid, objId, objTag) {
|
||||
alert("OK");
|
||||
$.ajax({
|
||||
type :"POST",
|
||||
url :'/users/tag_saveEx',
|
||||
type: "POST",
|
||||
url: '/users/tag_saveEx',
|
||||
data: {
|
||||
tagname: $('tag_name').value,
|
||||
obj_id: encodeURIComponent(objId),
|
||||
obj_flag:encodeURIComponent(objTag)
|
||||
obj_flag: encodeURIComponent(objTag)
|
||||
},
|
||||
success: function(data, textStatus){
|
||||
success: function (data, textStatus) {
|
||||
alert("OK");
|
||||
$(divid).empty();
|
||||
$(divid).html('123');
|
||||
$("#" + divid + " #name").val("");
|
||||
$("#" + divid + " #name").val("");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
:url => {:controller => 'homework_attach',
|
||||
:action => 'addjours',
|
||||
:jour_id => homework_attach.id,
|
||||
:is_comprehensive_evaluation => is_comprehensive_evaluation,
|
||||
:sta => sta}) do |f|%>
|
||||
|
||||
<div id = 'pre_show'>
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<% is_teacher = is_course_teacher User.current,homework.bid.courses.first %>
|
||||
<% if comprehensive_evaluation != nil && comprehensive_evaluation.count > 0 %>
|
||||
<% stars = homework.rates(:quality).where("rater_id = #{comprehensive_evaluation.first.user.id}").select("stars").first %>
|
||||
<div style="height: 100px; padding-bottom: 10px">
|
||||
<div style="font-size: 15px">
|
||||
<strong>作业综评:</strong>
|
||||
<span class="user" style="font-size: 15px">
|
||||
<div data-kls="HomeworkAttach" data-id="2" data-dimension="quality" data-average="3.25" class="rateable div_inline jDisabled"
|
||||
style="height: 15px; width: 100px; overflow: hidden; z-index: 1; position: relative;">
|
||||
<% if stars != nil %>
|
||||
<div class="jRatingColor" style="width: <%=stars.stars * 2 * 10 %>%;"></div>
|
||||
<% else %>
|
||||
<div class="jRatingColor" style="width: 0px;"></div>
|
||||
<% end %>
|
||||
<div class="jRatingAverage" style="width: 0px; top: -20px;"></div>
|
||||
<div class="jStar" style="width: 115px; height: 20px; top: -40px;
|
||||
background: url('/images/seems_rateable/stars.png') repeat-x scroll 0% 0% transparent;">
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div style="font-size: 14px;">
|
||||
<div style="margin-left: 20px;margin-bottom: 10px;margin-top: 10px;"> <%= comprehensive_evaluation.first.notes%> </div>
|
||||
</div>
|
||||
<% if is_teacher %>
|
||||
<div style="text-align: center;">评分:
|
||||
<%= rating_for homework, dimension: :quality, class: 'rateable div_inline' %>
|
||||
<span style="font-size: 11px">(您可以重新打分,打分结果以最后一次打分为主!)</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<% if is_teacher %>
|
||||
<div style="height: 200px; padding-bottom: 10px">
|
||||
<div style="font-size: 15px">
|
||||
<strong>作业综评:</strong>
|
||||
</div>
|
||||
<div style="text-align: center;">评分:
|
||||
<%= rating_for homework, dimension: :quality, class: 'rateable div_inline' %>
|
||||
<span style="font-size: 11px">(您可以重新打分,打分结果以最后一次打分为主!)</span>
|
||||
</div>
|
||||
<div>
|
||||
<%= render :partial => 'addjour', :locals => {:homework_attach => homework, :sta => 0,:is_comprehensive_evaluation => 1} %>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div style="height: 80px; padding-bottom: 10px">
|
||||
<div style="font-size: 15px">
|
||||
<strong>作业综评:</strong>
|
||||
</div>
|
||||
<div style="text-align: center;font-size: 15px;color: #15BCCC;vertical-align:middle;padding-top: 15px; "><strong>老师还未进行评价!</strong></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
|
@ -38,10 +38,25 @@
|
|||
<% if jour.size > 0 %>
|
||||
<ul class="message-for-user">
|
||||
<% for journal in jour%>
|
||||
<% seems = homework.rates(:quality).where("rater_id = #{journal.user.id}").select("stars").first %>
|
||||
<li id='word_li_<%= journal.id.to_s %>' class="outer-message-for-user">
|
||||
<span class="portrait"><%= image_tag(url_to_avatar(journal.user), :class => "avatar") %></span>
|
||||
<span class="body">
|
||||
<span class="user"><%= link_to journal.user, user_path(journal.user)%></span>
|
||||
<span class="user" style="font-size: 15px">
|
||||
<%= link_to journal.user, user_path(journal.user)%>
|
||||
<!--
|
||||
<% if seems != nil %>
|
||||
<div data-kls="HomeworkAttach" data-id="2" data-dimension="quality" data-average="3.25" class="rateable div_inline jDisabled"
|
||||
style="height: 15px; width: 100px; overflow: hidden; z-index: 1; position: relative;">
|
||||
<div class="jRatingColor" style="width: <%=seems.stars * 2 * 10 %>%;"></div>
|
||||
<div class="jRatingAverage" style="width: 0px; top: -20px;"></div>
|
||||
<div class="jStar" style="width: 115px; height: 20px; top: -40px;
|
||||
background: url('/images/seems_rateable/stars.png') repeat-x scroll 0% 0% transparent;">
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
-->
|
||||
</span>
|
||||
<span class="font_lighter"><% label = l(:label_contest_requirement) %></span>
|
||||
<div> <%= textilizable journal.notes%> </div>
|
||||
<span class="font_lighter"><%= l(:label_bids_published) %>
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
$('#message').html('<%= escape_javascript(render(:partial => 'showjour', :locals => {:jour =>@jour, :state => false} )) %>');
|
||||
$('#pre_show').html('<%= escape_javascript(render(:partial => 'pre_show', :locals => {:content => nil})) %>');
|
||||
$('#new_form_user_message').val("");
|
||||
$('#new_form_reference_user_id').val("");
|
||||
<% if @add_jour.is_comprehensive_evaluation == 1 %>
|
||||
$('#comprehensive_evaluation').html('<%= escape_javascript(render(:partial => 'comprehensive_evaluation',
|
||||
:locals => {:comprehensive_evaluation => @comprehensive_evaluation,:homework => @homework} )) %>');
|
||||
<% else %>
|
||||
$('#message').html('<%= escape_javascript(render(:partial => 'showjour', :locals => {:jour =>@jour, :state => false,:homework => @homework} )) %>');
|
||||
$('#pre_show').html('<%= escape_javascript(render(:partial => 'pre_show', :locals => {:content => nil})) %>');
|
||||
$('#new_form_user_message').val("");
|
||||
$('#new_form_reference_user_id').val("");
|
||||
<% end %>
|
|
@ -6,6 +6,9 @@
|
|||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
<% is_student = is_cur_course_student? @homework.bid.courses.first %>
|
||||
<% is_teacher = is_course_teacher User.current,@homework.bid.courses.first %>
|
||||
|
||||
<p id="notice"><%= notice %></p>
|
||||
<!-- <%= image_tag(url_to_avatar(@user), :class => "avatar2") %> -->
|
||||
<div style="height: auto; padding-bottom: 10px">
|
||||
|
@ -93,10 +96,14 @@
|
|||
</div>
|
||||
</div>
|
||||
<div style="float: left; padding-left: 100px; padding-top:35px ">
|
||||
<% score = @homework.average(:quality).try(:avg).try(:round, 2).to_s %>
|
||||
<div style="text-align: center">最终得分</div>
|
||||
<div style="padding-top: 1px; font-size: 15px; color: blue;text-align: center">
|
||||
<%= @homework.average(:quality).try(:avg).try(:round, 2).to_s %>
|
||||
分
|
||||
<% if score == "" %>
|
||||
0分
|
||||
<% else %>
|
||||
<%= score %>分
|
||||
<% end %>
|
||||
</div>
|
||||
<div style="padding-top: 3px">
|
||||
<%= rating_for @homework, :static => true, dimension: :quality, class: 'rateable div_inline' %>
|
||||
|
@ -114,35 +121,32 @@
|
|||
</div>
|
||||
<div class="underline-contests_one"></div>
|
||||
|
||||
<!--
|
||||
<div style="height: auto; padding-bottom: 10px">
|
||||
<div style="font-size: 15px">
|
||||
<strong>作业综评:</strong>
|
||||
</div>
|
||||
<div style="text-align: center;">评分:
|
||||
<%= rating_for @homework, :static => true, dimension: :quality, class: 'rateable div_inline' %>
|
||||
</div>
|
||||
<!-- 作业综评 -->
|
||||
<div id="comprehensive_evaluation">
|
||||
<%= render :partial => 'comprehensive_evaluation', :locals => {:comprehensive_evaluation => @comprehensive_evaluation,:homework => @homework} %>
|
||||
</div>
|
||||
<div class="underline-contests_one"></div>
|
||||
-->
|
||||
|
||||
<div class="underline-contests_one"></div>
|
||||
<div style="height: 50px">
|
||||
<div style="font-size: 15px"><strong>作业评论:</strong></div>
|
||||
<div style="text-align: center;">评分:
|
||||
<%= rating_for @homework, dimension: :quality, class: 'rateable div_inline' %>
|
||||
<span style="font-size: 11px">(您可以重新打分,打分结果以最后一次打分为主!)</span>
|
||||
</div>
|
||||
|
||||
<% if is_student %>
|
||||
<div style="text-align: center;">评分:
|
||||
<%= rating_for @homework, dimension: :quality, class: 'rateable div_inline' %>
|
||||
<span style="font-size: 11px">(您可以重新打分,打分结果以最后一次打分为主!)</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if !is_teacher %>
|
||||
<!--提示登录后对应用进行评价-->
|
||||
<div id='leave-message'>
|
||||
<%= render :partial => 'addjour', :locals => {:homework_attach => @homework, :sta => 0} %>
|
||||
<div id="leave-message">
|
||||
<%= render :partial => 'addjour', :locals => {:homework_attach => @homework, :sta => 0, :is_comprehensive_evaluation => nil} %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- 留言列表区 -->
|
||||
<div id="message" style="font-size: 14px;">
|
||||
<%= render :partial => 'showjour', :locals => {:jour => @jour} %>
|
||||
<%= render :partial => 'showjour', :locals => {:jour => @jour,:homework => @homework} %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
|
||||
<style>
|
||||
.softapplication-img .soft-application {
|
||||
float: left;
|
||||
width: 25%;
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
<% is_student = is_cur_course_student? @homework.bid.courses.first %>
|
||||
<% is_teacher = is_course_teacher User.current,@homework.bid.courses.first %>
|
||||
|
||||
<p id="notice"><%= notice %></p>
|
||||
<!-- <%= image_tag(url_to_avatar(@user), :class => "avatar2") %> -->
|
||||
<div style="height: auto; padding-bottom: 10px">
|
||||
<tr>
|
||||
<td colspan="2" valign="top" width="320" >
|
||||
</td>
|
||||
<td>
|
||||
<table width="100%" border="0">
|
||||
<tr style="font-size: 18px">
|
||||
<td colspan="2" valign="top"><strong>作业基础信息<%=@count %></strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 570px; padding-left:40px; word-wrap: break-word; word-break: break-all">
|
||||
发布人员:<%= link_to @homework.user, user_path(@homework.user)%>
|
||||
</td>
|
||||
<td>发布时间:<%=format_time @homework.created_at %></td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-left: 40px">
|
||||
<span>作业下载:</span>
|
||||
<% options = {:author => true } %>
|
||||
<%= render :partial => 'app_link', :locals => {:attachments => @homework.attachments, :options => options} %>
|
||||
</td>
|
||||
<td style="width: 240px; word-wrap: break-word; word-break: break-all">
|
||||
所属任务:<%= link_to(@homework.bid.name, project_for_bid_path(@homework.bid))%>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-left: 40px" colspan="2">平均评分:
|
||||
<%= rating_for @homework, :static => true, dimension: :quality, class: 'rateable div_inline' %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
<div class="underline-contests_one"></div>
|
||||
|
||||
<div style="height: auto; padding-bottom: 10px">
|
||||
<tr>
|
||||
<td colspan="2" valign="top" width="320" >
|
||||
</td>
|
||||
<td>
|
||||
<table width="100%" border="0">
|
||||
<tr>
|
||||
<td colspan="2" valign="top"><div style="font-size: 15px;"><strong>作业描述:</strong></div></td>
|
||||
</tr>
|
||||
<% @homework.attachments.map do |attachment| %>
|
||||
<% if attachment.description != nil && attachment.description != "" %>
|
||||
<tr>
|
||||
<td style="width: 570px; padding-left:40px; word-wrap: break-word; word-break: break-all">
|
||||
<div style="padding-top: 5px"> <%= attachment.description %></div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
<div class="underline-contests_one"></div>
|
||||
|
||||
<div style="height: auto; padding-bottom: 10px">
|
||||
<div style="font-size: 15px;">
|
||||
<strong>作业得分:</strong>
|
||||
</div>
|
||||
<div style="overflow: hidden">
|
||||
<div style="margin-left: 15%; float: left">
|
||||
<div style="padding-left: 45px; padding-bottom: 5px">得分比例</div>
|
||||
<div>
|
||||
<% 100.step(20,-20) do |star| %>
|
||||
<div data-kls="HomeworkAttach" data-id="2" data-dimension="quality" data-average="3.25" class="rateable div_inline jDisabled"
|
||||
style="height: 20px; width: 115px; overflow: hidden; z-index: 1; position: relative;">
|
||||
<div class="jRatingColor" style="width: <%=star%>%;"></div>
|
||||
<div class="jRatingAverage" style="width: 0px; top: -20px;"></div>
|
||||
<div class="jStar" style="width: 115px; height: 20px; top: -40px;
|
||||
background: url('/images/seems_rateable/stars.png') repeat-x scroll 0% 0% transparent;">
|
||||
</div>
|
||||
</div>
|
||||
<%= @stars_status_map["star#{(star/20).to_s}".to_sym] %>
|
||||
<br>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div style="float: left; padding-left: 100px; padding-top:35px ">
|
||||
<% score = @homework.average(:quality).try(:avg).try(:round, 2).to_s %>
|
||||
<div style="text-align: center">最终得分</div>
|
||||
<div style="padding-top: 1px; font-size: 15px; color: blue;text-align: center">
|
||||
<% if score == "" %>
|
||||
0分
|
||||
<% else %>
|
||||
<%= score %>分
|
||||
<% end %>
|
||||
</div>
|
||||
<div style="padding-top: 3px">
|
||||
<%= rating_for @homework, :static => true, dimension: :quality, class: 'rateable div_inline' %>
|
||||
</div>
|
||||
</div>
|
||||
<div style="float: left; padding-left: 100px; padding-top:35px ">
|
||||
<div>打分总人数</div>
|
||||
<div style="padding-left: 28px; padding-top: 1px; font-size: 25px; color: blue">
|
||||
<strong>
|
||||
<%= @homework.raters(:quality).count%>
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="underline-contests_one"></div>
|
||||
|
||||
<!-- 作业综评 -->
|
||||
<div id="comprehensive_evaluation">
|
||||
<%= render :partial => 'comprehensive_evaluation', :locals => {:comprehensive_evaluation => @comprehensive_evaluation,:homework => @homework} %>
|
||||
</div>
|
||||
|
||||
<div class="underline-contests_one"></div>
|
||||
<div style="height: 50px">
|
||||
<div style="font-size: 15px"><strong>作业评论:</strong></div>
|
||||
<% if is_student %>
|
||||
<div style="text-align: center;">评分:
|
||||
<%= rating_for @homework, dimension: :quality, class: 'rateable div_inline' %>
|
||||
<span style="font-size: 11px">(您可以重新打分,打分结果以最后一次打分为主!)</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if !is_teacher %>
|
||||
<!--提示登录后对应用进行评价-->
|
||||
<div id="leave-message">
|
||||
<%= render :partial => 'addjour', :locals => {:homework_attach => @homework, :sta => 0, :is_comprehensive_evaluation => nil} %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- 留言列表区 -->
|
||||
<div id="message" style="font-size: 14px;">
|
||||
<<<<<<< HEAD
|
||||
<%= render :partial => 'showjour', :locals => {:jour => @jour} %>
|
||||
=======
|
||||
<%= render :partial => 'showjour', :locals => {:jour => @jour,:homework => @homework} %>
|
||||
>>>>>>> 8c33cf0afb89556112710759615cbd88bb0326f2
|
||||
</div>
|
||||
|
||||
<div>
|
||||
</div>
|
|
@ -26,7 +26,7 @@
|
|||
<% end %>
|
||||
|
||||
<!-- <%= avatar(@issue.author, :size => "50") %> -->
|
||||
|
||||
|
||||
<div class="subject">
|
||||
<%= render_issue_subject_with_tree(@issue) %>
|
||||
</div>
|
||||
|
@ -35,7 +35,7 @@
|
|||
<span id="praise_tread" style="float: right">
|
||||
<%= render :partial => "/praise_tread/praise_tread",:locals => {:obj => @issue,:show_flag => true,:user_id =>User.current.id}%>
|
||||
</span>
|
||||
|
||||
|
||||
<p class="author">
|
||||
<%= authoring @issue.created_on, @issue.author %>.
|
||||
<% if @issue.created_on != @issue.updated_on %>
|
||||
|
@ -107,10 +107,12 @@ end %>
|
|||
|
||||
<% if false # !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
|
||||
<hr />
|
||||
|
||||
<div id="issue_tree">
|
||||
<div class="contextual">
|
||||
<%= link_to_new_subtask(@issue) if User.current.allowed_to?(:manage_subtasks, @project) %>
|
||||
</div>
|
||||
|
||||
<p><strong><%=l(:label_subtask_plural)%></strong></p>
|
||||
<%= render_descendants_tree(@issue) unless @issue.leaf? %>
|
||||
</div>
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
<%= yield :header_tags -%>
|
||||
</head>
|
||||
<body class="<%= h body_css_classes %>">
|
||||
<% project = @bid.courses.first %>
|
||||
<% course = Course.find_by_extra(project.identifier) %>
|
||||
<div id="wrapper">
|
||||
<div id="wrapper2">
|
||||
<div id="wrapper3">
|
||||
|
@ -45,20 +47,30 @@
|
|||
<div class="main_context">
|
||||
<div class="spaceleft">
|
||||
<div>
|
||||
<table style="width: 232px ; padding-left: 8px; font-size:16px; line-height: 1.5em">
|
||||
<tr valign="top">
|
||||
<td style="width: 70px" >课程名:</td>
|
||||
<td>
|
||||
<span style=" word-wrap: break-word; word-break: break-all"><strong> <%= link_to @bid.courses.first.name.to_s, homework_project_path(@bid.courses.first) if @bid.courses.first %></strong></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="">
|
||||
<td width="70px">作业名:</td>
|
||||
<td>
|
||||
<span style="color:#ed8924"><strong><%= h @bid.name %></strong></span>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- <tr>
|
||||
<table style="width: 250px ; line-height: 1.5em;font-size: 12px">
|
||||
<tr>
|
||||
<td style="width: 40px">
|
||||
<% if get_avatar?(project) %>
|
||||
<%= image_tag(url_to_avatar(project),:style => "width:60px;height:60px" ) %>
|
||||
<% else %>
|
||||
<%= image_tag('../images/avatars/Project/course.jpg', :style => "width:60px;height:60px") %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td style="width: 50px;vertical-align: top;" ><strong>课程名:</strong></td>
|
||||
<td style="text-align: left">
|
||||
<span style=" word-wrap: break-word; word-break: break-all"><strong> <%= link_to @bid.courses.first.name.to_s, homework_project_path(@bid.courses.first) if @bid.courses.first %></strong></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 50px;vertical-align: top;"><strong>作业名:</strong></td>
|
||||
<td style="text-align: left">
|
||||
<span style="color:#ed8924"><strong><%= h @bid.name %></strong></span>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- <tr>
|
||||
<td></td>
|
||||
<td align="center">
|
||||
<span class="bid_course" style="padding-left: 8px"> <% if @user != User.current %>
|
||||
|
@ -66,18 +78,61 @@
|
|||
<% end %> </span>
|
||||
</td>
|
||||
</tr> -->
|
||||
</table>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="user_underline"></div>
|
||||
<!--count-->
|
||||
|
||||
|
||||
<div class="inf_user_image">
|
||||
<table>
|
||||
<tr>
|
||||
<td valign="top" colspan="2" style="font-size: 16px;padding-left: 8px;"><strong><%= l(:label_new_course_description) %> :</strong></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="font_lighter_sidebar" colspan="2" style="padding-left: 8px;"><%= project.description %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td valign="top" style="padding-left: 8px;"><%= l(:label_create_time) %> :</td>
|
||||
<td class="font_lighter_sidebar"><%= format_time course.created_at %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td valign="top" style="padding-left: 8px; width:60px">主讲老师 :</td>
|
||||
<td class="font_lighter_sidebar"><%= link_to(@user.lastname+@user.firstname, user_path(@user)) %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td valign="top" style="padding-left: 8px;"><%= l(:label_class_period) %> :</td>
|
||||
<td class="font_lighter_sidebar"><%= course.class_period.to_s %> <%= l(:label_class_hour) %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td valign="top" style="padding-left: 8px;"><%= l(:label_main_term) %> :</td>
|
||||
<td class="font_lighter_sidebar"><%= course.time.to_s %> <%= course.term %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td valign="top" style="padding-left: 8px;"><%= l(:label_teacher_work_unit) %> :</td>
|
||||
<td class="font_lighter_sidebar"><%= @user.user_extensions.occupation %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="user_underline"></div>
|
||||
|
||||
<!-- info -->
|
||||
<div class="inf_user_image">
|
||||
<table>
|
||||
|
||||
<!--
|
||||
<tr>
|
||||
<td style="text-align: justify ; text-justify: inter-ideograph; width: 180px"><%= l(:label_teacher_homework) %> : <%= link_to(@user.lastname+@user.firstname, user_path(@user)) %></td>
|
||||
</tr>
|
||||
-->
|
||||
|
||||
<tr>
|
||||
<td><%= l(:label_limit_time) %>: <%= @bid.deadline %></td>
|
||||
|
|
|
@ -55,16 +55,25 @@
|
|||
<legend onclick="toggleFieldset(this);">
|
||||
<%= l(:label_information_plural) %>
|
||||
</legend>
|
||||
|
||||
<!-- 昵称 -->
|
||||
<p style="width:630px;padding-left: 26px;">
|
||||
<%= f.text_field :login, :required => true, :name => "login"%>
|
||||
<span class='font_lighter'><%= l(:label_max_number) %></span>
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<span id='name' style='display:none'>
|
||||
<p style="width:530px;padding-left: 26px;">
|
||||
<%= f.text_field :lastname, :required => true %>
|
||||
<span class='font_lighter'><%= l(:field_lastname_eg) %></span>
|
||||
</p>
|
||||
<p style="width:530px;padding-left: 26px;">
|
||||
<%= f.text_field :firstname, :required => true %>
|
||||
<span class='font_lighter'><%= l(:field_firstname_eg) %></span>
|
||||
</p>
|
||||
|
||||
<p style="width:530px;padding-left: 26px;">
|
||||
<%= f.text_field :lastname, :required => true %>
|
||||
<span class='font_lighter'><%= l(:field_lastname_eg) %></span>
|
||||
</p>
|
||||
<p style="width:530px;padding-left: 26px;">
|
||||
<%= f.text_field :firstname, :required => true %>
|
||||
<span class='font_lighter'><%= l(:field_firstname_eg) %></span>
|
||||
</p>
|
||||
</span>
|
||||
|
||||
<span id='enterprise' style='display:none'>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<td colspan="2" valign="top">
|
||||
<strong> <%= h(e.project) if @project.nil? || @project.id != e.project.id %></strong>
|
||||
<span class="font_lighter">
|
||||
<%= link_to_user(e.event_author) if e.respond_to?(:event_author) %>
|
||||
<%= link_to_user(e.event_author) if e.respond_to?(:event_author) %>(<%= link_to_user(e.event_author,@canShowRealName) if e.respond_to?(:event_author) %>)
|
||||
<%= l(:label_new_activity) %> </span>
|
||||
|
||||
<%= link_to "#{eventToLanguageCourse(e.event_type, @project)}: "<< format_activity_title(e.event_title), (e.event_type.eql?("attachment")&&e.container.kind_of?(Project)) ? project_files_path(e.container) : e.event_url %>
|
||||
|
@ -46,6 +46,7 @@
|
|||
|
||||
<!-- Added by Longjun 在最后一页显示创建信息 -->
|
||||
<% if format_date(day) == format_date(@date_to - @days) %>
|
||||
<h1>Test</h1>
|
||||
<div >
|
||||
<table width="660">
|
||||
<tr>
|
||||
|
@ -80,7 +81,16 @@
|
|||
<td colspan="2">
|
||||
<table width="580">
|
||||
<tr>
|
||||
<td > <%= link_to (h @user.try(:name)), user_path(@user) if @user %> <%= l(:label_user_create_project) %> <%= link_to @project.name %><strong> !</strong></td>
|
||||
<td >
|
||||
<%
|
||||
#判断是否显示真名
|
||||
if @canShowRealName
|
||||
%>
|
||||
<%= link_to (h @user.try(:name)), user_path(@user) if @user %>(<%= link_to (h @user.try(:realname)), user_path(@user) if @user %>)
|
||||
<% else %>
|
||||
<%= link_to (h @user.try(:name)), user_path(@user) if @user %>
|
||||
<% end %>
|
||||
<%= l(:label_user_create_project) %> <%= link_to @project.name %><strong> !</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font_lighter" style="float: right"><%= l :label_update_time %>: <%= format_time(@project.created_on) %>
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
<hr />
|
||||
<% users_results.each do |user| %>
|
||||
<p class="font_description2">
|
||||
<strong><%= l(:label_tags_user_name) %><%= link_to ("#{user.firstname+user.lastname}"),
|
||||
<strong><%= l(:label_tags_user_name) %><%= link_to ("#{user.name}"),
|
||||
:controller => "users",:action => "show",:id => user.id%></strong>
|
||||
<br />
|
||||
<strong><%= l(:label_tags_user_mail) %></strong><%= mail_to(h(user.mail)) %>
|
||||
</p>
|
||||
<div class="line_under"></div>
|
||||
<% end %>
|
||||
|
|
|
@ -1,13 +1,3 @@
|
|||
<script>
|
||||
window.onload=function (){
|
||||
$('#upload_form').submit(function() {
|
||||
$(this).ajaxSubmit( {
|
||||
target : '#tags_show'
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="tags">
|
||||
<%#begin
|
||||
|
@ -60,11 +50,7 @@
|
|||
<%= text_field "tag_name" ,"name"%>
|
||||
<input id="object_id" type="text" size="20" name="object_id" value='<%=obj.id%>' class="hidden">
|
||||
<input id="object_flag" type="text" size="20" name="object_flag" value='<%=object_flag%>' class="hidden">
|
||||
<%= button_tag "增加", :type=>"button", :onclick=>"tagAddClick(tags_show-"+obj.class.to_s + "-" +obj.id.to_s + ","+ obj.id.to_s + "," + object_flag.to_s + ")" %>
|
||||
<%#= f.text_field :object_id,:value=> obj.id,:style=>"display:none"%>
|
||||
<%#= f.text_field :object_flag,:value=> object_flag,:style=>"display:none"%>
|
||||
<input type="image" name="button" onclick="submitForm('<%=obj.id%>,<%=object_flag%>')" src="/commit.png"/>
|
||||
<%= submit_tag "增加", :name=>"add_tag" %>
|
||||
<%= submit_tag l(:button_add), :name=>"add_tag",:remote=>"false", :format => 'js' %>
|
||||
<div class='hidden'>
|
||||
<% preTags = @preTags.nil? ? [] : @preTags %>
|
||||
<% preTags.each do |tag|%>
|
||||
|
@ -113,13 +99,15 @@
|
|||
|
||||
|
||||
<script language="JavaScript">
|
||||
function submitForm(objId,objTag){
|
||||
function submitForm123(objId,objTag){
|
||||
alert("OK");
|
||||
$.ajax({
|
||||
type :"POST",
|
||||
type :"Get",
|
||||
url :'<%= users_tag_saveEx_path%>',
|
||||
remote:"true",
|
||||
format:"js",
|
||||
data: {
|
||||
tagname: $('tag_name').value,
|
||||
tagname: $('tag_name_name').val(),
|
||||
obj_id: encodeURIComponent(objId),
|
||||
obj_flag:encodeURIComponent(objTag)
|
||||
},
|
||||
|
@ -150,7 +138,7 @@
|
|||
type :"POST",
|
||||
url :'<%= users_tag_saveEx_path%>',
|
||||
data: {
|
||||
tagname: $('tag_name').value,
|
||||
tagname: $('tag_name_name').val(),
|
||||
obj_id: encodeURIComponent(objId),
|
||||
obj_flag:encodeURIComponent(objTag)
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="user_course_list">
|
||||
<div class="user_course_list menu-div">
|
||||
<div class="menu">
|
||||
<%= link_to "#{l(:label_course_new)}", new_project_path(course: 1, project_type: 1), class: 'icon icon-add' if @user == User.current %>
|
||||
<ul>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</p>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="user_course_list">
|
||||
<div class="user_course_list menu-div">
|
||||
<div class="menu">
|
||||
<%= link_to"#{l(:label_course_view_student)}",course_path(course: 1), :class => 'icon icon-add' %>
|
||||
<ul>
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
<div class="pagination">
|
||||
<ul>
|
||||
<%= pagination_links_full @user_pages, @user_count %>
|
||||
<ul>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- </p> -->
|
||||
<% html_title(l(:label_user_plural)) -%>
|
||||
|
|
|
@ -1,71 +1,4 @@
|
|||
<% if User.current.admin? %>
|
||||
<div class="contextual">
|
||||
<%= link_to l(:label_user_new), new_user_path, :class => 'icon icon-add' %>
|
||||
</div>
|
||||
|
||||
<h3><%= l(:label_user_plural)%></h3>
|
||||
|
||||
<%= form_tag(:controller => 'users', :action => 'search', :method => :get) do %>
|
||||
<fieldset>
|
||||
<legend>
|
||||
<%= l(:label_filter_plural) %>
|
||||
</legend>
|
||||
<label for='status'><%= l(:field_status) %>:</label>
|
||||
<%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %>
|
||||
|
||||
<% if @groups.present? %>
|
||||
<label for='group_id'><%= l(:label_group) %>:</label>
|
||||
<%= select_tag 'group_id', content_tag('option') + options_from_collection_for_select(@groups, :id, :name, params[:group_id].to_i), :onchange => "this.form.submit(); return false;" %>
|
||||
<% end %>
|
||||
|
||||
<label for='name'><%= l(:label_user) %>:</label>
|
||||
<%= text_field_tag 'name', params[:name], :size => 30 %>
|
||||
<%= submit_tag l(:label_search), :class => "small", :name => nil %>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
|
||||
|
||||
<div class="autoscroll">
|
||||
<table class="list">
|
||||
<thead>
|
||||
<tr>
|
||||
<%= sort_header_tag('login', :caption => l(:field_login)) %>
|
||||
<%= sort_header_tag('firstname', :caption => l(:field_firstname)) %>
|
||||
<%= sort_header_tag('lastname', :caption => l(:field_lastname)) %>
|
||||
<%= sort_header_tag('mail', :caption => l(:field_mail)) %>
|
||||
|
||||
<%= sort_header_tag('admin', :caption => l(:field_admin), :default_order => 'desc') %>
|
||||
<%= sort_header_tag('created_on', :caption => l(:field_created_on), :default_order => 'desc') %>
|
||||
<%= sort_header_tag('last_login_on', :caption => l(:field_last_login_on), :default_order => 'desc') %>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for user in @users -%>
|
||||
<tr class="<%= user.css_classes %> <%= cycle("odd", "even") %>">
|
||||
<td class="username"><%= avatar(user, :size => "14") %><%= link_to h(user.login), edit_user_path(user) %></td>
|
||||
<td class="firstname"><%= h(user.firstname) %></td>
|
||||
<td class="lastname"><%= h(user.lastname) %></td>
|
||||
<td class="email"><%= mail_to(h(user.mail)) %></td>
|
||||
<td align="center"><%= checked_image user.admin? %></td>
|
||||
<td class="created_on" align="center"><%= format_time(user.created_on) %></td>
|
||||
<td class="last_login_on" align="center"><%= format_time(user.last_login_on) unless user.last_login_on.nil? %></td>
|
||||
<td class="buttons"> <%= change_status_link(user) %>
|
||||
<%= delete_link user_path(user, :back_url => users_path(params)) unless User.current == user %> </td>
|
||||
</tr>
|
||||
<% end -%>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<ul>
|
||||
<%= pagination_links_full @user_pages, @user_count %>
|
||||
<ul>
|
||||
</div>
|
||||
|
||||
<% html_title(l(:label_user_plural)) -%>
|
||||
|
||||
<% else %>
|
||||
|
||||
<div class="top-content">
|
||||
<%= form_tag(:controller => 'users', :action => 'search', :method => :get) do %>
|
||||
|
@ -87,8 +20,9 @@
|
|||
<td ><%=link_to l(:field_homepage), home_path %> > <%=link_to l(:label_software_user), :controller => 'users', :action => 'index' %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
<div class="autoscroll">
|
||||
<% if @users.size > 0 %>
|
||||
|
@ -125,7 +59,7 @@
|
|||
<div class="pagination">
|
||||
<ul>
|
||||
<%= pagination_links_full @user_pages, @user_count %>
|
||||
<ul>
|
||||
</ul>
|
||||
</div>
|
||||
<% html_title(l(:label_user_plural)) -%>
|
||||
<% end -%>
|
||||
|
||||
|
|
|
@ -1,5 +1,24 @@
|
|||
<% if User.current.id == @user.id%>
|
||||
<%= show_activity @state%>
|
||||
<div class="menu-div">
|
||||
<div class="menu">
|
||||
<span style="color: #000; font-weight: ;"><%= "#{@user.name}的动态" %></span>
|
||||
<ul><%#链接绑定在页面最下方的jQuery%>
|
||||
<li mode='all' class="<%= "on" if @state.eql?(0)%>"><%=l :label_user_all_activity%></li>
|
||||
<li mode='myself' class="<%= "on" if @state.eql?(1)%>"><%=l :label_user_activity_myself%></li>
|
||||
<li mode='respond' class="<%= "on" if @state.eql?(2)%>"><%=l :label_user_all_respond%></li>
|
||||
</ul>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
||||
</script>
|
||||
</div>
|
||||
<%#= show_activity @state%>
|
||||
<div style="height:20px"></div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<%= form_tag(:controller => 'users', :action => "show") do %>
|
||||
<div class="user-search-block hidden" style="float:right;margin-top:-55px">
|
||||
<table width="100%" valign="center">
|
||||
|
@ -281,6 +300,9 @@
|
|||
<% end %>
|
||||
<script type="text/javascript" language="javascript">
|
||||
$(document).ready(function($) {
|
||||
$("#content .pagination:first~ div").first().find("a").attr("target", "_blank");
|
||||
$("#content .menu-div:first~ div").first().find("a").attr("target", "_blank");
|
||||
$('[mode=all]').click(function(event) {window.location.href='<%=user_activities_url%>'; });
|
||||
$('[mode=myself]').click(function(event) {window.location.href='<%=user_activities_url(type: 1)%>'; });
|
||||
$('[mode=respond]').click(function(event) {window.location.href='<%=user_activities_url(type: 2)%>'; });
|
||||
});
|
||||
</script>
|
|
@ -1,7 +1,18 @@
|
|||
<!--add by huang-->
|
||||
<%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new', :course => 0, :project_type => 0}, :class => 'icon icon-add') if(User.current.allowed_to?(:add_project, nil, :global => true) && @user == User.current)%>
|
||||
|
||||
<!--span class="font_lighter" >温馨提示:项目可以是一次作业,也可以是别人或者自己创建的一项小工程~</span-->
|
||||
<%= watch_projects @state %>
|
||||
<div class="menu-div">
|
||||
<div class="menu">
|
||||
<%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new', :course => 0, :project_type => 0}, :class => 'icon icon-add') if(User.current.allowed_to?(:add_project, nil, :global => true) && @user == User.current)%>
|
||||
<ul><%#链接绑定在页面最下方的jQuery%>
|
||||
<li mode='take' class="<%= "on" if @state.eql?(0)%>"><%=l :label_project_take%></li>
|
||||
<li mode='watched' class="<%= "on" if @state.eql?(1)%>"><%=l :label_has_watched_project%></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div style="height:20px"></div>
|
||||
|
||||
<%#= watch_projects @state %>
|
||||
<% unless @memberships.empty? %>
|
||||
<div class="content_frame">
|
||||
<ul class="user_project_sort">
|
||||
|
@ -48,5 +59,7 @@
|
|||
$(document).ready(function($) {
|
||||
$("#content .tabs_new~").find("a").attr("target", "_blank");
|
||||
$("#content .tabs_new~ .pagination").find("a").removeAttr("target");
|
||||
$('[mode=take]').click(function(event) {window.location.href='<%=user_projects_user_url%>'; });
|
||||
$('[mode=watched]').click(function(event) {window.location.href='<%=watch_projects_user_url(type: 1)%>'; });
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
<%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new', :course => 0, :project_type => 0}, :class => 'icon icon-add') if(User.current.allowed_to?(:add_project, nil, :global => true) && @user == User.current)%>
|
||||
|
||||
<!--span class="font_lighter" >温馨提示:项目可以是一次作业,也可以是别人或者自己创建的一项小工程~</span-->
|
||||
<%= watch_projects @state%>
|
||||
|
||||
<div class="menu-div">
|
||||
<div class="menu">
|
||||
<%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new', :course => 0, :project_type => 0}, :class => 'icon icon-add') if(User.current.allowed_to?(:add_project, nil, :global => true) && @user == User.current)%>
|
||||
<ul><%#链接绑定在页面最下方的jQuery%>
|
||||
<li mode='take' class="<%= "on" if @state.eql?(0)%>"><%=l :label_project_take%></li>
|
||||
<li mode='watched' class="<%= "on" if @state.eql?(1)%>"><%=l :label_has_watched_project%></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%#= watch_projects @state%>
|
||||
<% if @watch_projects.count > 0 %>
|
||||
<% for watch_project in @watch_projects %>
|
||||
<table width="660" border="0" align="center" style="border-bottom: 1px dashed rgb(204, 204, 204); margin-bottom: 10px;font-size:14px;">
|
||||
|
@ -30,3 +41,10 @@
|
|||
<% else %>
|
||||
<%= l(:label_watch_no_projects)%>
|
||||
<% end %>
|
||||
<script type="text/javascript" language="javascript">
|
||||
$(document).ready(function($) {
|
||||
|
||||
$('[mode=take]').click(function(event) {window.location.href='<%=user_projects_user_url%>'; });
|
||||
$('[mode=watched]').click(function(event) {window.location.href='<%=watch_projects_user_url(type: 1)%>'; });
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -266,7 +266,7 @@ en:
|
|||
field_is_public: Public
|
||||
field_parent: Subproject of
|
||||
field_is_in_roadmap: Issues displayed in roadmap
|
||||
field_login: Login
|
||||
field_login: Account/Email
|
||||
field_mail_notification: Email notifications
|
||||
field_admin: Administrator
|
||||
field_last_login_on: Last connection
|
||||
|
@ -1332,7 +1332,7 @@ en:
|
|||
label_tags_user_mail: User E-mail:
|
||||
label_tags_user_name: User Name:
|
||||
label_tags_numbers: Tag numbers:
|
||||
label_max_number: Must be at most 25 characters long.
|
||||
label_max_number: Open label nickname is displayed on the web site of your,Must be at most 25 characters long.
|
||||
label_all_revisions: All revisions:
|
||||
label_repository_name: Repository name
|
||||
label_upassword_info: The password can be shared in the group
|
||||
|
@ -1617,6 +1617,9 @@ en:
|
|||
label_class_hour: period
|
||||
label_activity_time: publish date
|
||||
|
||||
label_your_course: your course
|
||||
label_have_message : have a new message
|
||||
label_login_prompt: Email/NickName
|
||||
|
||||
# ajax异步验证
|
||||
modal_valid_passing: can be used.
|
||||
|
|
|
@ -284,7 +284,7 @@ zh:
|
|||
field_is_public: 公开
|
||||
field_parent: 上级项目
|
||||
field_is_in_roadmap: 在路线图中显示
|
||||
field_login: 登录名
|
||||
field_login: 昵称
|
||||
field_mail_notification: 邮件通知
|
||||
field_admin: 管理员
|
||||
field_last_login_on: 最后登录
|
||||
|
@ -453,6 +453,7 @@ zh:
|
|||
permission_export_wiki_pages: 导出 wiki 页面
|
||||
permission_manage_subtasks: 管理子任务
|
||||
permission_view_journals_for_messages: 查看留言
|
||||
permission_view_courses: 查看课程列表
|
||||
|
||||
project_module_issue_tracking: 问题跟踪
|
||||
project_module_time_tracking: 时间跟踪
|
||||
|
@ -464,6 +465,7 @@ zh:
|
|||
project_module_boards: 讨论区
|
||||
project_module_calendar: 日历
|
||||
project_module_gantt: 甘特图
|
||||
project_module_course: 课程
|
||||
label_module_share: DTS测试工具
|
||||
|
||||
label_user: 用户
|
||||
|
@ -598,6 +600,8 @@ zh:
|
|||
label_my_page_block: 我的工作台模块
|
||||
label_administration: 管理
|
||||
label_login: 登录
|
||||
label_login_prompt: 邮箱/账号
|
||||
lable_user_name: 登录名
|
||||
label_logout: 退出
|
||||
label_help: 帮助
|
||||
label_reported_issues: 已报告的问题
|
||||
|
@ -1495,8 +1499,10 @@ zh:
|
|||
label_tags_user_mail: 用户邮箱:
|
||||
label_tags_user_name: 用户名:
|
||||
label_tags_numbers: Tag统计:
|
||||
label_max_number: 至多25个字符。
|
||||
label_max_number: 昵称是在网站中显示的您的公开标识,至多25个字符。
|
||||
label_mail_attention: qq邮箱可能收不到此邮件,其他邮箱如果没有收到可能在垃圾邮件中,其中gmail与教育网邮箱的激活邮件有时比较慢,请耐心等待。
|
||||
label_your_course: 您的课程《
|
||||
label_have_message : 》有新的留言
|
||||
label_all_revisions: 所有版本:
|
||||
label_upassword_info: 该密码在项目组内可共享
|
||||
label_how_commit_code: 查看如何提交代码:
|
||||
|
|
|
@ -186,6 +186,9 @@ RedmineApp::Application.routes.draw do
|
|||
|
||||
#added by young
|
||||
resources :users do
|
||||
collection do
|
||||
match "tag_saveEx" , via: [:get, :post]
|
||||
end
|
||||
member do
|
||||
match 'user_projects', :to => 'users#user_projects', :via => :get
|
||||
match 'user_activities', :to => 'users#show', :via => :get, :as => "user_activities"
|
||||
|
@ -237,7 +240,7 @@ RedmineApp::Application.routes.draw do
|
|||
match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships'
|
||||
################# added by william
|
||||
match 'users/tag_save', :to => 'users#tag_save', :via => :post, :as => 'tag'
|
||||
match 'users/tag_saveEx', :to => 'users#tag_saveEx', :via => :post
|
||||
match 'users/tag_saveEx', :to => 'users#tag_saveEx', :via => [:get, :post]
|
||||
|
||||
post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
|
||||
delete 'watchers/watch', :to => 'watchers#unwatch'
|
||||
|
@ -523,6 +526,7 @@ RedmineApp::Application.routes.draw do
|
|||
match 'admin', :controller => 'admin', :action => 'index', :via => :get
|
||||
match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get
|
||||
match 'admin/users', :controller => 'admin', :action => 'users', :via => :get
|
||||
match 'admin/search', :controller => 'admin', :action => 'search', :via => [:get, :post]
|
||||
match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get
|
||||
match 'admin/info', :controller => 'admin', :action => 'info', :via => :get
|
||||
match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddColoumToHomeworkJournalsForMessage < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :journals_for_messages, :is_comprehensive_evaluation, :integer
|
||||
end
|
||||
end
|
17
db/schema.rb
17
db/schema.rb
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20140526104509) do
|
||||
ActiveRecord::Schema.define(:version => 20140522025721) do
|
||||
|
||||
create_table "activities", :force => true do |t|
|
||||
t.integer "act_id", :null => false
|
||||
|
@ -23,11 +23,6 @@ ActiveRecord::Schema.define(:version => 20140526104509) do
|
|||
add_index "activities", ["user_id", "act_type"], :name => "index_activities_on_user_id_and_act_type"
|
||||
add_index "activities", ["user_id"], :name => "index_activities_on_user_id"
|
||||
|
||||
create_table "andoidcontests", :force => true do |t|
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
end
|
||||
|
||||
create_table "applied_projects", :force => true do |t|
|
||||
t.integer "project_id", :null => false
|
||||
t.integer "user_id", :null => false
|
||||
|
@ -62,11 +57,14 @@ ActiveRecord::Schema.define(:version => 20140526104509) do
|
|||
add_index "attachments", ["container_id", "container_type"], :name => "index_attachments_on_container_id_and_container_type"
|
||||
add_index "attachments", ["created_on"], :name => "index_attachments_on_created_on"
|
||||
|
||||
create_table "attachmentstypes", :force => true do |t|
|
||||
t.integer "typeId", :null => false
|
||||
create_table "attachmentstypes", :id => false, :force => true do |t|
|
||||
t.integer "id", :null => false
|
||||
t.integer "typeId"
|
||||
t.string "typeName", :limit => 50
|
||||
end
|
||||
|
||||
add_index "attachmentstypes", ["id"], :name => "id"
|
||||
|
||||
create_table "auth_sources", :force => true do |t|
|
||||
t.string "type", :limit => 30, :default => "", :null => false
|
||||
t.string "name", :limit => 60, :default => "", :null => false
|
||||
|
@ -656,8 +654,8 @@ ActiveRecord::Schema.define(:version => 20140526104509) do
|
|||
t.boolean "inherit_members", :default => false, :null => false
|
||||
t.integer "project_type"
|
||||
t.boolean "hidden_repo", :default => false, :null => false
|
||||
t.integer "user_id"
|
||||
t.integer "attachmenttype", :default => 1
|
||||
t.integer "user_id"
|
||||
end
|
||||
|
||||
add_index "projects", ["lft"], :name => "index_projects_on_lft"
|
||||
|
@ -803,7 +801,6 @@ ActiveRecord::Schema.define(:version => 20140526104509) do
|
|||
t.string "application_developers"
|
||||
t.string "deposit_project_url"
|
||||
t.string "deposit_project"
|
||||
t.integer "project_id"
|
||||
end
|
||||
|
||||
create_table "students_for_courses", :force => true do |t|
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
language: ruby
|
||||
rvm:
|
||||
- 2.1.0
|
||||
- 2.0.0
|
|
@ -0,0 +1 @@
|
|||
--markup markdown --no-private
|
|
@ -0,0 +1,3 @@
|
|||
# Changelog
|
||||
|
||||
See https://github.com/charliesome/better_errors/releases
|
|
@ -0,0 +1,10 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gemspec
|
||||
|
||||
gem "rake"
|
||||
gem "rspec", "2.14.1"
|
||||
gem "binding_of_caller", platforms: :ruby
|
||||
gem "pry", "0.9.12"
|
||||
gem "yard"
|
||||
gem "kramdown"
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2014 Charlie Somerville
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,103 @@
|
|||
# Better Errors [![Gem Version](http://img.shields.io/gem/v/better_errors.svg)](https://rubygems.org/gems/better_errors) [![Build Status](https://travis-ci.org/charliesome/better_errors.svg)](https://travis-ci.org/charliesome/better_errors) [![Code Climate](http://img.shields.io/codeclimate/github/charliesome/better_errors.svg)](https://codeclimate.com/github/charliesome/better_errors)
|
||||
|
||||
Better Errors replaces the standard Rails error page with a much better and more useful error page. It is also usable outside of Rails in any Rack app as Rack middleware.
|
||||
|
||||
![image](http://i.imgur.com/6zBGAAb.png)
|
||||
|
||||
## Features
|
||||
|
||||
* Full stack trace
|
||||
* Source code inspection for all stack frames (with highlighting)
|
||||
* Local and instance variable inspection
|
||||
* Live REPL on every stack frame
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your Gemfile:
|
||||
|
||||
```ruby
|
||||
group :development do
|
||||
gem "better_errors"
|
||||
end
|
||||
```
|
||||
|
||||
If you would like to use Better Errors' **advanced features** (REPL, local/instance variable inspection, pretty stack frame names), you need to add the [`binding_of_caller`](https://github.com/banister/binding_of_caller) gem by [@banisterfiend](http://twitter.com/banisterfiend) to your Gemfile:
|
||||
|
||||
```ruby
|
||||
gem "binding_of_caller"
|
||||
```
|
||||
|
||||
This is an optional dependency however, and Better Errors will work without it.
|
||||
|
||||
_Note: If you discover that Better Errors isn't working - particularly after upgrading from version 0.5.0 or less - be sure to set `config.consider_all_requests_local = true` in `config/environments/development.rb`._
|
||||
|
||||
## Security
|
||||
|
||||
**NOTE:** It is *critical* you put better\_errors in the **development** section. **Do NOT run better_errors in production, or on Internet facing hosts.**
|
||||
|
||||
You will notice that the only machine that gets the Better Errors page is localhost, which means you get the default error page if you are developing on a remote host (or a virtually remote host, such as a Vagrant box). Obviously, the REPL is not something you want to expose to the public, but there may also be other pieces of sensitive information available in the backtrace.
|
||||
|
||||
To poke selective holes in this security mechanism, you can add a line like this to your startup (for example, on Rails it would be `config/environments/development.rb`)
|
||||
|
||||
```ruby
|
||||
BetterErrors::Middleware.allow_ip! ENV['TRUSTED_IP'] if ENV['TRUSTED_IP']
|
||||
```
|
||||
|
||||
Then run Rails like this:
|
||||
|
||||
```shell
|
||||
TRUSTED_IP=66.68.96.220 rails s
|
||||
```
|
||||
|
||||
Note that the `allow_ip!` is actually backed by a `Set`, so you can add more than one IP address or subnet.
|
||||
|
||||
**Tip:** You can find your apparent IP by hitting the old error page's "Show env dump" and looking at "REMOTE_ADDR".
|
||||
|
||||
**VirtualBox:** If you are using VirtualBox and are accessing the guest from your host's browser, you will need to use `allow_ip!` to see the error page.
|
||||
|
||||
## Usage
|
||||
|
||||
If you're using Rails, there's nothing else you need to do.
|
||||
|
||||
If you're not using Rails, you need to insert `BetterErrors::Middleware` into your middleware stack, and optionally set `BetterErrors.application_root` if you'd like Better Errors to abbreviate filenames within your application.
|
||||
|
||||
Here's an example using Sinatra:
|
||||
|
||||
```ruby
|
||||
require "sinatra"
|
||||
require "better_errors"
|
||||
|
||||
configure :development do
|
||||
use BetterErrors::Middleware
|
||||
BetterErrors.application_root = __dir__
|
||||
end
|
||||
|
||||
get "/" do
|
||||
raise "oops"
|
||||
end
|
||||
```
|
||||
|
||||
### Unicorn, Puma, and other multi-worker servers
|
||||
|
||||
Better Errors works by leaving a lot of context in server process memory. If
|
||||
you're using a web server that runs multiple "workers" it's likely that a second
|
||||
request (as happens when you click on a stack frame) will hit a different
|
||||
worker. That worker won't have the necessary context in memory, and you'll see
|
||||
a `Session Expired` message.
|
||||
|
||||
If this is the case for you, consider turning the number of workers to one (1)
|
||||
in `development`. Another option would be to use Webrick, Mongrel, Thin,
|
||||
or another single-process server as your `rails server`, when you are trying
|
||||
to troubleshoot an issue in development.
|
||||
|
||||
## Get in touch!
|
||||
|
||||
If you're using better_errors, I'd love to hear from you. Drop me a line and tell me what you think!
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
4. Push to the branch (`git push origin my-new-feature`)
|
||||
5. Create new Pull Request
|
|
@ -0,0 +1,13 @@
|
|||
require "bundler/gem_tasks"
|
||||
require "rspec/core/rake_task"
|
||||
|
||||
namespace :test do
|
||||
RSpec::Core::RakeTask.new(:with_binding_of_caller)
|
||||
|
||||
without_task = RSpec::Core::RakeTask.new(:without_binding_of_caller)
|
||||
without_task.ruby_opts = "-I spec -r without_binding_of_caller"
|
||||
|
||||
task :all => [:with_binding_of_caller, :without_binding_of_caller]
|
||||
end
|
||||
|
||||
task :default => "test:all"
|
|
@ -0,0 +1,27 @@
|
|||
lib = File.expand_path('../lib', __FILE__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require 'better_errors/version'
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "better_errors"
|
||||
s.version = BetterErrors::VERSION
|
||||
s.authors = ["Charlie Somerville"]
|
||||
s.email = ["charlie@charliesomerville.com"]
|
||||
s.description = %q{Provides a better error page for Rails and other Rack apps. Includes source code inspection, a live REPL and local/instance variable inspection for all stack frames.}
|
||||
s.summary = %q{Better error page for Rails and other Rack apps}
|
||||
s.homepage = "https://github.com/charliesome/better_errors"
|
||||
s.license = "MIT"
|
||||
|
||||
s.files = `git ls-files`.split($/)
|
||||
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
||||
s.require_paths = ["lib"]
|
||||
|
||||
s.required_ruby_version = ">= 2.0.0"
|
||||
|
||||
s.add_dependency "erubis", ">= 2.6.6"
|
||||
s.add_dependency "coderay", ">= 1.0.0"
|
||||
|
||||
# optional dependencies:
|
||||
# s.add_dependency "binding_of_caller"
|
||||
# s.add_dependency "pry"
|
||||
end
|
|
@ -0,0 +1,146 @@
|
|||
require "pp"
|
||||
require "erubis"
|
||||
require "coderay"
|
||||
require "uri"
|
||||
|
||||
require "better_errors/code_formatter"
|
||||
require "better_errors/error_page"
|
||||
require "better_errors/middleware"
|
||||
require "better_errors/raised_exception"
|
||||
require "better_errors/repl"
|
||||
require "better_errors/stack_frame"
|
||||
require "better_errors/version"
|
||||
|
||||
module BetterErrors
|
||||
POSSIBLE_EDITOR_PRESETS = [
|
||||
{ symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
|
||||
{ symbols: [:macvim, :mvim], sniff: /vim/i, url: proc { |file, line| "mvim://open?url=file://#{file}&line=#{line}" } },
|
||||
{ symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
|
||||
{ symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
|
||||
]
|
||||
|
||||
class << self
|
||||
# The path to the root of the application. Better Errors uses this property
|
||||
# to determine if a file in a backtrace should be considered an application
|
||||
# frame. If you are using Better Errors with Rails, you do not need to set
|
||||
# this attribute manually.
|
||||
#
|
||||
# @return [String]
|
||||
attr_accessor :application_root
|
||||
|
||||
# The logger to use when logging exception details and backtraces. If you
|
||||
# are using Better Errors with Rails, you do not need to set this attribute
|
||||
# manually. If this attribute is `nil`, nothing will be logged.
|
||||
#
|
||||
# @return [Logger, nil]
|
||||
attr_accessor :logger
|
||||
|
||||
# @private
|
||||
attr_accessor :binding_of_caller_available
|
||||
|
||||
# @private
|
||||
alias_method :binding_of_caller_available?, :binding_of_caller_available
|
||||
|
||||
# The ignored instance variables.
|
||||
# @return [Array]
|
||||
attr_accessor :ignored_instance_variables
|
||||
end
|
||||
@ignored_instance_variables = []
|
||||
|
||||
# Returns a proc, which when called with a filename and line number argument,
|
||||
# returns a URL to open the filename and line in the selected editor.
|
||||
#
|
||||
# Generates TextMate URLs by default.
|
||||
#
|
||||
# BetterErrors.editor["/some/file", 123]
|
||||
# # => txmt://open?url=file:///some/file&line=123
|
||||
#
|
||||
# @return [Proc]
|
||||
def self.editor
|
||||
@editor
|
||||
end
|
||||
|
||||
# Configures how Better Errors generates open-in-editor URLs.
|
||||
#
|
||||
# @overload BetterErrors.editor=(sym)
|
||||
# Uses one of the preset editor configurations. Valid symbols are:
|
||||
#
|
||||
# * `:textmate`, `:txmt`, `:tm`
|
||||
# * `:sublime`, `:subl`, `:st`
|
||||
# * `:macvim`
|
||||
#
|
||||
# @param [Symbol] sym
|
||||
#
|
||||
# @overload BetterErrors.editor=(str)
|
||||
# Uses `str` as the format string for generating open-in-editor URLs.
|
||||
#
|
||||
# Use `%{file}` and `%{line}` as placeholders for the actual values.
|
||||
#
|
||||
# @example
|
||||
# BetterErrors.editor = "my-editor://open?url=%{file}&line=%{line}"
|
||||
#
|
||||
# @param [String] str
|
||||
#
|
||||
# @overload BetterErrors.editor=(proc)
|
||||
# Uses `proc` to generate open-in-editor URLs. The proc will be called
|
||||
# with `file` and `line` parameters when a URL needs to be generated.
|
||||
#
|
||||
# Your proc should take care to escape `file` appropriately with
|
||||
# `URI.encode_www_form_component` (please note that `URI.escape` is **not**
|
||||
# a suitable substitute.)
|
||||
#
|
||||
# @example
|
||||
# BetterErrors.editor = proc { |file, line|
|
||||
# "my-editor://open?url=#{URI.encode_www_form_component file}&line=#{line}"
|
||||
# }
|
||||
#
|
||||
# @param [Proc] proc
|
||||
#
|
||||
def self.editor=(editor)
|
||||
POSSIBLE_EDITOR_PRESETS.each do |config|
|
||||
if config[:symbols].include?(editor)
|
||||
return self.editor = config[:url]
|
||||
end
|
||||
end
|
||||
|
||||
if editor.is_a? String
|
||||
self.editor = proc { |file, line| editor % { file: URI.encode_www_form_component(file), line: line } }
|
||||
else
|
||||
if editor.respond_to? :call
|
||||
@editor = editor
|
||||
else
|
||||
raise TypeError, "Expected editor to be a valid editor key, a format string or a callable."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Enables experimental Pry support in the inline REPL
|
||||
#
|
||||
# If you encounter problems while using Pry, *please* file a bug report at
|
||||
# https://github.com/charliesome/better_errors/issues
|
||||
def self.use_pry!
|
||||
REPL::PROVIDERS.unshift const: :Pry, impl: "better_errors/repl/pry"
|
||||
end
|
||||
|
||||
# Automatically sniffs a default editor preset based on the EDITOR
|
||||
# environment variable.
|
||||
#
|
||||
# @return [Symbol]
|
||||
def self.default_editor
|
||||
POSSIBLE_EDITOR_PRESETS.detect(-> { {} }) { |config|
|
||||
ENV["EDITOR"] =~ config[:sniff]
|
||||
}[:url] || :textmate
|
||||
end
|
||||
|
||||
BetterErrors.editor = default_editor
|
||||
end
|
||||
|
||||
begin
|
||||
require "binding_of_caller"
|
||||
require "better_errors/exception_extension"
|
||||
BetterErrors.binding_of_caller_available = true
|
||||
rescue LoadError => e
|
||||
BetterErrors.binding_of_caller_available = false
|
||||
end
|
||||
|
||||
require "better_errors/rails" if defined? Rails::Railtie
|
|
@ -0,0 +1,63 @@
|
|||
module BetterErrors
|
||||
# @private
|
||||
class CodeFormatter
|
||||
require "better_errors/code_formatter/html"
|
||||
require "better_errors/code_formatter/text"
|
||||
|
||||
FILE_TYPES = {
|
||||
".rb" => :ruby,
|
||||
"" => :ruby,
|
||||
".html" => :html,
|
||||
".erb" => :erb,
|
||||
".haml" => :haml
|
||||
}
|
||||
|
||||
attr_reader :filename, :line, :context
|
||||
|
||||
def initialize(filename, line, context = 5)
|
||||
@filename = filename
|
||||
@line = line
|
||||
@context = context
|
||||
end
|
||||
|
||||
def output
|
||||
formatted_code
|
||||
rescue Errno::ENOENT, Errno::EINVAL
|
||||
source_unavailable
|
||||
end
|
||||
|
||||
def formatted_code
|
||||
formatted_lines.join
|
||||
end
|
||||
|
||||
def coderay_scanner
|
||||
ext = File.extname(filename)
|
||||
FILE_TYPES[ext] || :text
|
||||
end
|
||||
|
||||
def each_line_of(lines, &blk)
|
||||
line_range.zip(lines).map { |current_line, str|
|
||||
yield (current_line == line), current_line, str
|
||||
}
|
||||
end
|
||||
|
||||
def highlighted_lines
|
||||
CodeRay.scan(context_lines.join, coderay_scanner).div(wrap: nil).lines
|
||||
end
|
||||
|
||||
def context_lines
|
||||
range = line_range
|
||||
source_lines[(range.begin - 1)..(range.end - 1)] or raise Errno::EINVAL
|
||||
end
|
||||
|
||||
def source_lines
|
||||
@source_lines ||= File.readlines(filename)
|
||||
end
|
||||
|
||||
def line_range
|
||||
min = [line - context, 1].max
|
||||
max = [line + context, source_lines.count].min
|
||||
min..max
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
module BetterErrors
|
||||
# @private
|
||||
class CodeFormatter::HTML < CodeFormatter
|
||||
def source_unavailable
|
||||
"<p class='unavailable'>Source is not available</p>"
|
||||
end
|
||||
|
||||
def formatted_lines
|
||||
each_line_of(highlighted_lines) { |highlight, current_line, str|
|
||||
class_name = highlight ? "highlight" : ""
|
||||
sprintf '<pre class="%s">%s</pre>', class_name, str
|
||||
}
|
||||
end
|
||||
|
||||
def formatted_nums
|
||||
each_line_of(highlighted_lines) { |highlight, current_line, str|
|
||||
class_name = highlight ? "highlight" : ""
|
||||
sprintf '<span class="%s">%5d</span>', class_name, current_line
|
||||
}
|
||||
end
|
||||
|
||||
def formatted_code
|
||||
%{<div class="code_linenums">#{formatted_nums.join}</div><div class="code">#{super}</div>}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
module BetterErrors
|
||||
# @private
|
||||
class CodeFormatter::Text < CodeFormatter
|
||||
def source_unavailable
|
||||
"# Source is not available"
|
||||
end
|
||||
|
||||
def formatted_lines
|
||||
each_line_of(context_lines) { |highlight, current_line, str|
|
||||
sprintf '%s %3d %s', (highlight ? '>' : ' '), current_line, str
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,110 @@
|
|||
require "cgi"
|
||||
require "json"
|
||||
require "securerandom"
|
||||
|
||||
module BetterErrors
|
||||
# @private
|
||||
class ErrorPage
|
||||
def self.template_path(template_name)
|
||||
File.expand_path("../templates/#{template_name}.erb", __FILE__)
|
||||
end
|
||||
|
||||
def self.template(template_name)
|
||||
Erubis::EscapedEruby.new(File.read(template_path(template_name)))
|
||||
end
|
||||
|
||||
attr_reader :exception, :env, :repls
|
||||
|
||||
def initialize(exception, env)
|
||||
@exception = RaisedException.new(exception)
|
||||
@env = env
|
||||
@start_time = Time.now.to_f
|
||||
@repls = []
|
||||
end
|
||||
|
||||
def id
|
||||
@id ||= SecureRandom.hex(8)
|
||||
end
|
||||
|
||||
def render(template_name = "main")
|
||||
self.class.template(template_name).result binding
|
||||
end
|
||||
|
||||
def do_variables(opts)
|
||||
index = opts["index"].to_i
|
||||
@frame = backtrace_frames[index]
|
||||
@var_start_time = Time.now.to_f
|
||||
{ html: render("variable_info") }
|
||||
end
|
||||
|
||||
def do_eval(opts)
|
||||
index = opts["index"].to_i
|
||||
code = opts["source"]
|
||||
|
||||
unless binding = backtrace_frames[index].frame_binding
|
||||
return { error: "REPL unavailable in this stack frame" }
|
||||
end
|
||||
|
||||
result, prompt, prefilled_input =
|
||||
(@repls[index] ||= REPL.provider.new(binding)).send_input(code)
|
||||
|
||||
{ result: result,
|
||||
prompt: prompt,
|
||||
prefilled_input: prefilled_input,
|
||||
highlighted_input: CodeRay.scan(code, :ruby).div(wrap: nil) }
|
||||
end
|
||||
|
||||
def backtrace_frames
|
||||
exception.backtrace
|
||||
end
|
||||
|
||||
def application_frames
|
||||
backtrace_frames.select(&:application?)
|
||||
end
|
||||
|
||||
def first_frame
|
||||
application_frames.first || backtrace_frames.first
|
||||
end
|
||||
|
||||
private
|
||||
def editor_url(frame)
|
||||
BetterErrors.editor[frame.filename, frame.line]
|
||||
end
|
||||
|
||||
def rack_session
|
||||
env['rack.session']
|
||||
end
|
||||
|
||||
def rails_params
|
||||
env['action_dispatch.request.parameters']
|
||||
end
|
||||
|
||||
def uri_prefix
|
||||
env["SCRIPT_NAME"] || ""
|
||||
end
|
||||
|
||||
def request_path
|
||||
env["PATH_INFO"]
|
||||
end
|
||||
|
||||
def html_formatted_code_block(frame)
|
||||
CodeFormatter::HTML.new(frame.filename, frame.line).output
|
||||
end
|
||||
|
||||
def text_formatted_code_block(frame)
|
||||
CodeFormatter::Text.new(frame.filename, frame.line).output
|
||||
end
|
||||
|
||||
def text_heading(char, str)
|
||||
str + "\n" + char*str.size
|
||||
end
|
||||
|
||||
def inspect_value(obj)
|
||||
CGI.escapeHTML(obj.inspect)
|
||||
rescue NoMethodError
|
||||
"<span class='unsupported'>(object doesn't support inspect)</span>"
|
||||
rescue Exception => e
|
||||
"<span class='unsupported'>(exception was raised in inspect)</span>"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
module BetterErrors
|
||||
module ExceptionExtension
|
||||
prepend_features Exception
|
||||
|
||||
def set_backtrace(*)
|
||||
if caller_locations.none? { |loc| loc.path == __FILE__ }
|
||||
@__better_errors_bindings_stack = binding.callers.drop(1)
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def __better_errors_bindings_stack
|
||||
@__better_errors_bindings_stack || []
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,141 @@
|
|||
require "json"
|
||||
require "ipaddr"
|
||||
require "set"
|
||||
|
||||
module BetterErrors
|
||||
# Better Errors' error handling middleware. Including this in your middleware
|
||||
# stack will show a Better Errors error page for exceptions raised below this
|
||||
# middleware.
|
||||
#
|
||||
# If you are using Ruby on Rails, you do not need to manually insert this
|
||||
# middleware into your middleware stack.
|
||||
#
|
||||
# @example Sinatra
|
||||
# require "better_errors"
|
||||
#
|
||||
# if development?
|
||||
# use BetterErrors::Middleware
|
||||
# end
|
||||
#
|
||||
# @example Rack
|
||||
# require "better_errors"
|
||||
# if ENV["RACK_ENV"] == "development"
|
||||
# use BetterErrors::Middleware
|
||||
# end
|
||||
#
|
||||
class Middleware
|
||||
# The set of IP addresses that are allowed to access Better Errors.
|
||||
#
|
||||
# Set to `{ "127.0.0.1/8", "::1/128" }` by default.
|
||||
ALLOWED_IPS = Set.new
|
||||
|
||||
# Adds an address to the set of IP addresses allowed to access Better
|
||||
# Errors.
|
||||
def self.allow_ip!(addr)
|
||||
ALLOWED_IPS << IPAddr.new(addr)
|
||||
end
|
||||
|
||||
allow_ip! "127.0.0.0/8"
|
||||
allow_ip! "::1/128" rescue nil # windows ruby doesn't have ipv6 support
|
||||
|
||||
# A new instance of BetterErrors::Middleware
|
||||
#
|
||||
# @param app The Rack app/middleware to wrap with Better Errors
|
||||
# @param handler The error handler to use.
|
||||
def initialize(app, handler = ErrorPage)
|
||||
@app = app
|
||||
@handler = handler
|
||||
end
|
||||
|
||||
# Calls the Better Errors middleware
|
||||
#
|
||||
# @param [Hash] env
|
||||
# @return [Array]
|
||||
def call(env)
|
||||
if allow_ip? env
|
||||
better_errors_call env
|
||||
else
|
||||
@app.call env
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allow_ip?(env)
|
||||
# REMOTE_ADDR is not in the rack spec, so some application servers do
|
||||
# not provide it.
|
||||
return true unless env["REMOTE_ADDR"] and !env["REMOTE_ADDR"].strip.empty?
|
||||
ip = IPAddr.new env["REMOTE_ADDR"].split("%").first
|
||||
ALLOWED_IPS.any? { |subnet| subnet.include? ip }
|
||||
end
|
||||
|
||||
def better_errors_call(env)
|
||||
case env["PATH_INFO"]
|
||||
when %r{/__better_errors/(?<id>.+?)/(?<method>\w+)\z}
|
||||
internal_call env, $~
|
||||
when %r{/__better_errors/?\z}
|
||||
show_error_page env
|
||||
else
|
||||
protected_app_call env
|
||||
end
|
||||
end
|
||||
|
||||
def protected_app_call(env)
|
||||
@app.call env
|
||||
rescue Exception => ex
|
||||
@error_page = @handler.new ex, env
|
||||
log_exception
|
||||
show_error_page(env, ex)
|
||||
end
|
||||
|
||||
def show_error_page(env, exception=nil)
|
||||
type, content = if @error_page
|
||||
if text?(env)
|
||||
[ 'plain', @error_page.render('text') ]
|
||||
else
|
||||
[ 'html', @error_page.render ]
|
||||
end
|
||||
else
|
||||
[ 'html', no_errors_page ]
|
||||
end
|
||||
|
||||
status_code = 500
|
||||
if defined? ActionDispatch::ExceptionWrapper
|
||||
status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
|
||||
end
|
||||
|
||||
[status_code, { "Content-Type" => "text/#{type}; charset=utf-8" }, [content]]
|
||||
end
|
||||
|
||||
def text?(env)
|
||||
env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" ||
|
||||
!env["HTTP_ACCEPT"].to_s.include?('html')
|
||||
end
|
||||
|
||||
def log_exception
|
||||
return unless BetterErrors.logger
|
||||
|
||||
message = "\n#{@error_page.exception.type} - #{@error_page.exception.message}:\n"
|
||||
@error_page.backtrace_frames.each do |frame|
|
||||
message << " #{frame}\n"
|
||||
end
|
||||
|
||||
BetterErrors.logger.fatal message
|
||||
end
|
||||
|
||||
def internal_call(env, opts)
|
||||
if opts[:id] != @error_page.id
|
||||
return [200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(error: "Session expired")]]
|
||||
end
|
||||
|
||||
env["rack.input"].rewind
|
||||
response = @error_page.send("do_#{opts[:method]}", JSON.parse(env["rack.input"].read))
|
||||
[200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(response)]]
|
||||
end
|
||||
|
||||
def no_errors_page
|
||||
"<h1>No errors</h1><p>No errors have been recorded yet.</p><hr>" +
|
||||
"<code>Better Errors v#{BetterErrors::VERSION}</code>"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
module BetterErrors
|
||||
# @private
|
||||
class Railtie < Rails::Railtie
|
||||
initializer "better_errors.configure_rails_initialization" do
|
||||
if use_better_errors?
|
||||
insert_middleware
|
||||
BetterErrors.logger = Rails.logger
|
||||
BetterErrors.application_root = Rails.root.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def insert_middleware
|
||||
if defined? ActionDispatch::DebugExceptions
|
||||
app.middleware.insert_after ActionDispatch::DebugExceptions, BetterErrors::Middleware
|
||||
else
|
||||
app.middleware.use BetterErrors::Middleware
|
||||
end
|
||||
end
|
||||
|
||||
def use_better_errors?
|
||||
!Rails.env.production? and app.config.consider_all_requests_local
|
||||
end
|
||||
|
||||
def app
|
||||
Rails.application
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,66 @@
|
|||
# @private
|
||||
module BetterErrors
|
||||
class RaisedException
|
||||
attr_reader :exception, :message, :backtrace
|
||||
|
||||
def initialize(exception)
|
||||
if exception.respond_to?(:original_exception) && exception.original_exception
|
||||
exception = exception.original_exception
|
||||
end
|
||||
|
||||
@exception = exception
|
||||
@message = exception.message
|
||||
|
||||
setup_backtrace
|
||||
massage_syntax_error
|
||||
end
|
||||
|
||||
def type
|
||||
exception.class
|
||||
end
|
||||
|
||||
private
|
||||
def has_bindings?
|
||||
exception.respond_to?(:__better_errors_bindings_stack) && exception.__better_errors_bindings_stack.any?
|
||||
end
|
||||
|
||||
def setup_backtrace
|
||||
if has_bindings?
|
||||
setup_backtrace_from_bindings
|
||||
else
|
||||
setup_backtrace_from_backtrace
|
||||
end
|
||||
end
|
||||
|
||||
def setup_backtrace_from_bindings
|
||||
@backtrace = exception.__better_errors_bindings_stack.map { |binding|
|
||||
file = binding.eval "__FILE__"
|
||||
line = binding.eval "__LINE__"
|
||||
name = binding.frame_description
|
||||
StackFrame.new(file, line, name, binding)
|
||||
}
|
||||
end
|
||||
|
||||
def setup_backtrace_from_backtrace
|
||||
@backtrace = (exception.backtrace || []).map { |frame|
|
||||
if /\A(?<file>.*?):(?<line>\d+)(:in `(?<name>.*)')?/ =~ frame
|
||||
StackFrame.new(file, line.to_i, name)
|
||||
end
|
||||
}.compact
|
||||
end
|
||||
|
||||
def massage_syntax_error
|
||||
case exception.class.to_s
|
||||
when "Haml::SyntaxError"
|
||||
if /\A(.+?):(\d+)/ =~ exception.backtrace.first
|
||||
backtrace.unshift(StackFrame.new($1, $2.to_i, ""))
|
||||
end
|
||||
when "SyntaxError"
|
||||
if /\A(.+?):(\d+): (.*)/m =~ exception.message
|
||||
backtrace.unshift(StackFrame.new($1, $2.to_i, ""))
|
||||
@message = $3
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
module BetterErrors
|
||||
# @private
|
||||
module REPL
|
||||
PROVIDERS = [
|
||||
{ impl: "better_errors/repl/basic",
|
||||
const: :Basic },
|
||||
]
|
||||
|
||||
def self.provider
|
||||
@provider ||= const_get detect[:const]
|
||||
end
|
||||
|
||||
def self.provider=(prov)
|
||||
@provider = prov
|
||||
end
|
||||
|
||||
def self.detect
|
||||
PROVIDERS.find { |prov|
|
||||
test_provider prov
|
||||
}
|
||||
end
|
||||
|
||||
def self.test_provider(provider)
|
||||
require provider[:impl]
|
||||
true
|
||||
rescue LoadError
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
module BetterErrors
|
||||
module REPL
|
||||
class Basic
|
||||
def initialize(binding)
|
||||
@binding = binding
|
||||
end
|
||||
|
||||
def send_input(str)
|
||||
[execute(str), ">>", ""]
|
||||
end
|
||||
|
||||
private
|
||||
def execute(str)
|
||||
"=> #{@binding.eval(str).inspect}\n"
|
||||
rescue Exception => e
|
||||
"!! #{e.inspect rescue e.class.to_s rescue "Exception"}\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,78 @@
|
|||
require "fiber"
|
||||
require "pry"
|
||||
|
||||
module BetterErrors
|
||||
module REPL
|
||||
class Pry
|
||||
class Input
|
||||
def readline
|
||||
Fiber.yield
|
||||
end
|
||||
end
|
||||
|
||||
class Output
|
||||
def initialize
|
||||
@buffer = ""
|
||||
end
|
||||
|
||||
def puts(*args)
|
||||
args.each do |arg|
|
||||
@buffer << "#{arg.chomp}\n"
|
||||
end
|
||||
end
|
||||
|
||||
def tty?
|
||||
false
|
||||
end
|
||||
|
||||
def read_buffer
|
||||
@buffer
|
||||
ensure
|
||||
@buffer = ""
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(binding)
|
||||
@fiber = Fiber.new do
|
||||
@pry.repl binding
|
||||
end
|
||||
@input = Input.new
|
||||
@output = Output.new
|
||||
@pry = ::Pry.new input: @input, output: @output
|
||||
@pry.hooks.clear_all if defined?(@pry.hooks.clear_all)
|
||||
@fiber.resume
|
||||
end
|
||||
|
||||
def send_input(str)
|
||||
local ::Pry.config, color: false, pager: false do
|
||||
@fiber.resume "#{str}\n"
|
||||
[@output.read_buffer, *prompt]
|
||||
end
|
||||
end
|
||||
|
||||
def prompt
|
||||
if indent = @pry.instance_variable_get(:@indent) and !indent.indent_level.empty?
|
||||
["..", indent.indent_level]
|
||||
else
|
||||
[">>", ""]
|
||||
end
|
||||
rescue
|
||||
[">>", ""]
|
||||
end
|
||||
|
||||
private
|
||||
def local(obj, attrs)
|
||||
old_attrs = {}
|
||||
attrs.each do |k, v|
|
||||
old_attrs[k] = obj.send k
|
||||
obj.send "#{k}=", v
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
old_attrs.each do |k, v|
|
||||
obj.send "#{k}=", v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,111 @@
|
|||
require "set"
|
||||
|
||||
module BetterErrors
|
||||
# @private
|
||||
class StackFrame
|
||||
def self.from_exception(exception)
|
||||
RaisedException.new(exception).backtrace
|
||||
end
|
||||
|
||||
attr_reader :filename, :line, :name, :frame_binding
|
||||
|
||||
def initialize(filename, line, name, frame_binding = nil)
|
||||
@filename = filename
|
||||
@line = line
|
||||
@name = name
|
||||
@frame_binding = frame_binding
|
||||
|
||||
set_pretty_method_name if frame_binding
|
||||
end
|
||||
|
||||
def application?
|
||||
if root = BetterErrors.application_root
|
||||
filename.index(root) == 0 && filename.index("#{root}/vendor") != 0
|
||||
end
|
||||
end
|
||||
|
||||
def application_path
|
||||
filename[(BetterErrors.application_root.length+1)..-1]
|
||||
end
|
||||
|
||||
def gem?
|
||||
Gem.path.any? { |path| filename.index(path) == 0 }
|
||||
end
|
||||
|
||||
def gem_path
|
||||
if path = Gem.path.detect { |path| filename.index(path) == 0 }
|
||||
gem_name_and_version, path = filename.sub("#{path}/gems/", "").split("/", 2)
|
||||
/(?<gem_name>.+)-(?<gem_version>[\w.]+)/ =~ gem_name_and_version
|
||||
"#{gem_name} (#{gem_version}) #{path}"
|
||||
end
|
||||
end
|
||||
|
||||
def class_name
|
||||
@class_name
|
||||
end
|
||||
|
||||
def method_name
|
||||
@method_name || @name
|
||||
end
|
||||
|
||||
def context
|
||||
if gem?
|
||||
:gem
|
||||
elsif application?
|
||||
:application
|
||||
else
|
||||
:dunno
|
||||
end
|
||||
end
|
||||
|
||||
def pretty_path
|
||||
case context
|
||||
when :application; application_path
|
||||
when :gem; gem_path
|
||||
else filename
|
||||
end
|
||||
end
|
||||
|
||||
def local_variables
|
||||
return {} unless frame_binding
|
||||
frame_binding.eval("local_variables").each_with_object({}) do |name, hash|
|
||||
if defined?(frame_binding.local_variable_get)
|
||||
hash[name] = frame_binding.local_variable_get(name)
|
||||
else
|
||||
hash[name] = frame_binding.eval(name.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def instance_variables
|
||||
return {} unless frame_binding
|
||||
Hash[visible_instance_variables.map { |x|
|
||||
[x, frame_binding.eval(x.to_s)]
|
||||
}]
|
||||
end
|
||||
|
||||
def visible_instance_variables
|
||||
frame_binding.eval("instance_variables") - BetterErrors.ignored_instance_variables
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{pretty_path}:#{line}:in `#{name}'"
|
||||
end
|
||||
|
||||
private
|
||||
def set_pretty_method_name
|
||||
name =~ /\A(block (\([^)]+\) )?in )?/
|
||||
recv = frame_binding.eval("self")
|
||||
|
||||
return unless method_name = frame_binding.eval("::Kernel.__method__")
|
||||
|
||||
if Module === recv
|
||||
@class_name = "#{$1}#{recv}"
|
||||
@method_name = ".#{method_name}"
|
||||
else
|
||||
@class_name = "#{$1}#{Kernel.instance_method(:class).bind(recv).call}"
|
||||
@method_name = "##{method_name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
|||
<%== text_heading("=", "%s at %s" % [exception.type, request_path]) %>
|
||||
|
||||
> <%== exception.message %>
|
||||
<% if backtrace_frames.any? %>
|
||||
|
||||
<%== text_heading("-", "%s, line %i" % [first_frame.pretty_path, first_frame.line]) %>
|
||||
|
||||
``` ruby
|
||||
<%== text_formatted_code_block(first_frame) %>```
|
||||
|
||||
App backtrace
|
||||
-------------
|
||||
|
||||
<%== application_frames.map { |s| " - #{s}" }.join("\n") %>
|
||||
|
||||
Full backtrace
|
||||
--------------
|
||||
|
||||
<%== backtrace_frames.map { |s| " - #{s}" }.join("\n") %>
|
||||
|
||||
<% end %>
|
|
@ -0,0 +1,70 @@
|
|||
<header class="trace_info clearfix">
|
||||
<div class="title">
|
||||
<h2 class="name"><%= @frame.name %></h2>
|
||||
<div class="location"><span class="filename"><a href="<%= editor_url(@frame) %>"><%= @frame.pretty_path %></a></span></div>
|
||||
</div>
|
||||
<div class="code_block clearfix">
|
||||
<%== html_formatted_code_block @frame %>
|
||||
</div>
|
||||
|
||||
<% if BetterErrors.binding_of_caller_available? && @frame.frame_binding %>
|
||||
<div class="repl">
|
||||
<div class="console">
|
||||
<pre></pre>
|
||||
<div class="prompt"><span>>></span> <input/></div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<% if BetterErrors.binding_of_caller_available? && @frame.frame_binding %>
|
||||
<div class="hint">
|
||||
This is a live shell. Type in here.
|
||||
</div>
|
||||
|
||||
<div class="variable_info"></div>
|
||||
<% end %>
|
||||
|
||||
<% unless BetterErrors.binding_of_caller_available? %>
|
||||
<div class="hint">
|
||||
<strong>Tip:</strong> add <code>gem "binding_of_caller"</code> to your Gemfile to enable the REPL and local/instance variable inspection.
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="sub">
|
||||
<h3>Request info</h3>
|
||||
<div class='inset variables'>
|
||||
<table class="var_table">
|
||||
<% if rails_params %>
|
||||
<tr><td class="name">Request parameters</td><td><pre><%== inspect_value rails_params %></pre></td></tr>
|
||||
<% end %>
|
||||
<% if rack_session %>
|
||||
<tr><td class="name">Rack session</td><td><pre><%== inspect_value rack_session %></pre></td></tr>
|
||||
<% end %>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sub">
|
||||
<h3>Local Variables</h3>
|
||||
<div class='inset variables'>
|
||||
<table class="var_table">
|
||||
<% @frame.local_variables.each do |name, value| %>
|
||||
<tr><td class="name"><%= name %></td><td><pre><%== inspect_value value %></pre></td></tr>
|
||||
<% end %>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sub">
|
||||
<h3>Instance Variables</h3>
|
||||
<div class="inset variables">
|
||||
<table class="var_table">
|
||||
<% @frame.instance_variables.each do |name, value| %>
|
||||
<tr><td class="name"><%= name %></td><td><pre><%== inspect_value value %></pre></td></tr>
|
||||
<% end %>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <%= Time.now.to_f - @var_start_time %> seconds -->
|
|
@ -0,0 +1,3 @@
|
|||
module BetterErrors
|
||||
VERSION = "1.1.0"
|
||||
end
|
|
@ -0,0 +1,92 @@
|
|||
require "spec_helper"
|
||||
|
||||
module BetterErrors
|
||||
describe CodeFormatter do
|
||||
let(:filename) { File.expand_path("../support/my_source.rb", __FILE__) }
|
||||
|
||||
let(:formatter) { CodeFormatter.new(filename, 8) }
|
||||
|
||||
it "picks an appropriate scanner" do
|
||||
formatter.coderay_scanner.should == :ruby
|
||||
end
|
||||
|
||||
it "shows 5 lines of context" do
|
||||
formatter.line_range.should == (3..13)
|
||||
|
||||
formatter.context_lines.should == [
|
||||
"three\n",
|
||||
"four\n",
|
||||
"five\n",
|
||||
"six\n",
|
||||
"seven\n",
|
||||
"eight\n",
|
||||
"nine\n",
|
||||
"ten\n",
|
||||
"eleven\n",
|
||||
"twelve\n",
|
||||
"thirteen\n"
|
||||
]
|
||||
end
|
||||
|
||||
it "works when the line is right on the edge" do
|
||||
formatter = CodeFormatter.new(filename, 20)
|
||||
formatter.line_range.should == (15..20)
|
||||
end
|
||||
|
||||
describe CodeFormatter::HTML do
|
||||
it "highlights the erroring line" do
|
||||
formatter = CodeFormatter::HTML.new(filename, 8)
|
||||
formatter.output.should =~ /highlight.*eight/
|
||||
end
|
||||
|
||||
it "works when the line is right on the edge" do
|
||||
formatter = CodeFormatter::HTML.new(filename, 20)
|
||||
formatter.output.should_not == formatter.source_unavailable
|
||||
end
|
||||
|
||||
it "doesn't barf when the lines don't make any sense" do
|
||||
formatter = CodeFormatter::HTML.new(filename, 999)
|
||||
formatter.output.should == formatter.source_unavailable
|
||||
end
|
||||
|
||||
it "doesn't barf when the file doesn't exist" do
|
||||
formatter = CodeFormatter::HTML.new("fkdguhskd7e l", 1)
|
||||
formatter.output.should == formatter.source_unavailable
|
||||
end
|
||||
end
|
||||
|
||||
describe CodeFormatter::Text do
|
||||
it "highlights the erroring line" do
|
||||
formatter = CodeFormatter::Text.new(filename, 8)
|
||||
formatter.output.should == <<-TEXT.gsub(/^ /, "")
|
||||
3 three
|
||||
4 four
|
||||
5 five
|
||||
6 six
|
||||
7 seven
|
||||
> 8 eight
|
||||
9 nine
|
||||
10 ten
|
||||
11 eleven
|
||||
12 twelve
|
||||
13 thirteen
|
||||
TEXT
|
||||
end
|
||||
|
||||
it "works when the line is right on the edge" do
|
||||
formatter = CodeFormatter::Text.new(filename, 20)
|
||||
formatter.output.should_not == formatter.source_unavailable
|
||||
end
|
||||
|
||||
it "doesn't barf when the lines don't make any sense" do
|
||||
formatter = CodeFormatter::Text.new(filename, 999)
|
||||
formatter.output.should == formatter.source_unavailable
|
||||
end
|
||||
|
||||
it "doesn't barf when the file doesn't exist" do
|
||||
formatter = CodeFormatter::Text.new("fkdguhskd7e l", 1)
|
||||
formatter.output.should == formatter.source_unavailable
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,76 @@
|
|||
require "spec_helper"
|
||||
|
||||
module BetterErrors
|
||||
describe ErrorPage do
|
||||
let!(:exception) { raise ZeroDivisionError, "you divided by zero you silly goose!" rescue $! }
|
||||
|
||||
let(:error_page) { ErrorPage.new exception, { "PATH_INFO" => "/some/path" } }
|
||||
|
||||
let(:response) { error_page.render }
|
||||
|
||||
let(:empty_binding) {
|
||||
local_a = :value_for_local_a
|
||||
local_b = :value_for_local_b
|
||||
|
||||
@inst_c = :value_for_inst_c
|
||||
@inst_d = :value_for_inst_d
|
||||
|
||||
binding
|
||||
}
|
||||
|
||||
it "includes the error message" do
|
||||
response.should include("you divided by zero you silly goose!")
|
||||
end
|
||||
|
||||
it "includes the request path" do
|
||||
response.should include("/some/path")
|
||||
end
|
||||
|
||||
it "includes the exception class" do
|
||||
response.should include("ZeroDivisionError")
|
||||
end
|
||||
|
||||
context "variable inspection" do
|
||||
let(:exception) { empty_binding.eval("raise") rescue $! }
|
||||
|
||||
if BetterErrors.binding_of_caller_available?
|
||||
it "shows local variables" do
|
||||
html = error_page.do_variables("index" => 0)[:html]
|
||||
html.should include("local_a")
|
||||
html.should include(":value_for_local_a")
|
||||
html.should include("local_b")
|
||||
html.should include(":value_for_local_b")
|
||||
end
|
||||
else
|
||||
it "tells the user to add binding_of_caller to their gemfile to get fancy features" do
|
||||
html = error_page.do_variables("index" => 0)[:html]
|
||||
html.should include(%{gem "binding_of_caller"})
|
||||
end
|
||||
end
|
||||
|
||||
it "shows instance variables" do
|
||||
html = error_page.do_variables("index" => 0)[:html]
|
||||
html.should include("inst_c")
|
||||
html.should include(":value_for_inst_c")
|
||||
html.should include("inst_d")
|
||||
html.should include(":value_for_inst_d")
|
||||
end
|
||||
|
||||
it "shows filter instance variables" do
|
||||
BetterErrors.stub(:ignored_instance_variables).and_return([ :@inst_d ])
|
||||
html = error_page.do_variables("index" => 0)[:html]
|
||||
html.should include("inst_c")
|
||||
html.should include(":value_for_inst_c")
|
||||
html.should_not include('<td class="name">@inst_d</td>')
|
||||
html.should_not include("<pre>:value_for_inst_d</pre>")
|
||||
end
|
||||
end
|
||||
|
||||
it "doesn't die if the source file is not a real filename" do
|
||||
exception.stub(:backtrace).and_return([
|
||||
"<internal:prelude>:10:in `spawn_rack_application'"
|
||||
])
|
||||
response.should include("Source unavailable")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,146 @@
|
|||
require "spec_helper"
|
||||
|
||||
module BetterErrors
|
||||
describe Middleware do
|
||||
let(:app) { Middleware.new(->env { ":)" }) }
|
||||
let(:exception) { RuntimeError.new("oh no :(") }
|
||||
|
||||
it "passes non-error responses through" do
|
||||
app.call({}).should == ":)"
|
||||
end
|
||||
|
||||
it "calls the internal methods" do
|
||||
app.should_receive :internal_call
|
||||
app.call("PATH_INFO" => "/__better_errors/1/preform_awesomness")
|
||||
end
|
||||
|
||||
it "calls the internal methods on any subfolder path" do
|
||||
app.should_receive :internal_call
|
||||
app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors/1/preform_awesomness")
|
||||
end
|
||||
|
||||
it "shows the error page" do
|
||||
app.should_receive :show_error_page
|
||||
app.call("PATH_INFO" => "/__better_errors/")
|
||||
end
|
||||
|
||||
it "shows the error page on any subfolder path" do
|
||||
app.should_receive :show_error_page
|
||||
app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors/")
|
||||
end
|
||||
|
||||
it "doesn't show the error page to a non-local address" do
|
||||
app.should_not_receive :better_errors_call
|
||||
app.call("REMOTE_ADDR" => "1.2.3.4")
|
||||
end
|
||||
|
||||
it "shows to a whitelisted IP" do
|
||||
BetterErrors::Middleware.allow_ip! '77.55.33.11'
|
||||
app.should_receive :better_errors_call
|
||||
app.call("REMOTE_ADDR" => "77.55.33.11")
|
||||
end
|
||||
|
||||
it "doesn't blow up when given a blank REMOTE_ADDR" do
|
||||
expect { app.call("REMOTE_ADDR" => " ") }.to_not raise_error
|
||||
end
|
||||
|
||||
it "doesn't blow up when given an IP address with a zone index" do
|
||||
expect { app.call("REMOTE_ADDR" => "0:0:0:0:0:0:0:1%0" ) }.to_not raise_error
|
||||
end
|
||||
|
||||
context "when requesting the /__better_errors manually" do
|
||||
let(:app) { Middleware.new(->env { ":)" }) }
|
||||
|
||||
it "shows that no errors have been recorded" do
|
||||
status, headers, body = app.call("PATH_INFO" => "/__better_errors")
|
||||
body.join.should match /No errors have been recorded yet./
|
||||
end
|
||||
|
||||
it "shows that no errors have been recorded on any subfolder path" do
|
||||
status, headers, body = app.call("PATH_INFO" => "/any_sub/folder/path/__better_errors")
|
||||
body.join.should match /No errors have been recorded yet./
|
||||
end
|
||||
end
|
||||
|
||||
context "when handling an error" do
|
||||
let(:app) { Middleware.new(->env { raise exception }) }
|
||||
|
||||
it "returns status 500" do
|
||||
status, headers, body = app.call({})
|
||||
|
||||
status.should == 500
|
||||
end
|
||||
|
||||
context "original_exception" do
|
||||
class OriginalExceptionException < Exception
|
||||
attr_reader :original_exception
|
||||
|
||||
def initialize(message, original_exception = nil)
|
||||
super(message)
|
||||
@original_exception = original_exception
|
||||
end
|
||||
end
|
||||
|
||||
it "shows Original Exception if it responds_to and has an original_exception" do
|
||||
app = Middleware.new(->env {
|
||||
raise OriginalExceptionException.new("Other Exception", Exception.new("Original Exception"))
|
||||
})
|
||||
|
||||
status, _, body = app.call({})
|
||||
|
||||
status.should == 500
|
||||
body.join.should_not match(/Other Exception/)
|
||||
body.join.should match(/Original Exception/)
|
||||
end
|
||||
|
||||
it "won't crash if the exception responds_to but doesn't have an original_exception" do
|
||||
app = Middleware.new(->env {
|
||||
raise OriginalExceptionException.new("Other Exception")
|
||||
})
|
||||
|
||||
status, _, body = app.call({})
|
||||
|
||||
status.should == 500
|
||||
body.join.should match(/Other Exception/)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns ExceptionWrapper's status_code" do
|
||||
ad_ew = double("ActionDispatch::ExceptionWrapper")
|
||||
ad_ew.stub('new').with({}, exception ){ double("ExceptionWrapper", status_code: 404) }
|
||||
stub_const('ActionDispatch::ExceptionWrapper', ad_ew)
|
||||
|
||||
status, headers, body = app.call({})
|
||||
|
||||
status.should == 404
|
||||
end
|
||||
|
||||
it "returns UTF-8 error pages" do
|
||||
status, headers, body = app.call({})
|
||||
|
||||
headers["Content-Type"].should match /charset=utf-8/
|
||||
end
|
||||
|
||||
it "returns text pages by default" do
|
||||
status, headers, body = app.call({})
|
||||
|
||||
headers["Content-Type"].should match /text\/plain/
|
||||
end
|
||||
|
||||
it "returns HTML pages by default" do
|
||||
# Chrome's 'Accept' header looks similar this.
|
||||
status, headers, body = app.call("HTTP_ACCEPT" => "text/html,application/xhtml+xml;q=0.9,*/*")
|
||||
|
||||
headers["Content-Type"].should match /text\/html/
|
||||
end
|
||||
|
||||
it "logs the exception" do
|
||||
logger = Object.new
|
||||
logger.should_receive :fatal
|
||||
BetterErrors.stub(:logger).and_return(logger)
|
||||
|
||||
app.call({})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
require "spec_helper"
|
||||
|
||||
module BetterErrors
|
||||
describe RaisedException do
|
||||
let(:exception) { RuntimeError.new("whoops") }
|
||||
subject { RaisedException.new(exception) }
|
||||
|
||||
its(:exception) { should == exception }
|
||||
its(:message) { should == "whoops" }
|
||||
its(:type) { should == RuntimeError }
|
||||
|
||||
context "when the exception wraps another exception" do
|
||||
let(:original_exception) { RuntimeError.new("something went wrong!") }
|
||||
let(:exception) { double(:original_exception => original_exception) }
|
||||
|
||||
its(:exception) { should == original_exception }
|
||||
its(:message) { should == "something went wrong!" }
|
||||
end
|
||||
|
||||
context "when the exception is a syntax error" do
|
||||
let(:exception) { SyntaxError.new("foo.rb:123: you made a typo!") }
|
||||
|
||||
its(:message) { should == "you made a typo!" }
|
||||
its(:type) { should == SyntaxError }
|
||||
|
||||
it "has the right filename and line number in the backtrace" do
|
||||
subject.backtrace.first.filename.should == "foo.rb"
|
||||
subject.backtrace.first.line.should == 123
|
||||
end
|
||||
end
|
||||
|
||||
context "when the exception is a HAML syntax error" do
|
||||
before do
|
||||
stub_const("Haml::SyntaxError", Class.new(SyntaxError))
|
||||
end
|
||||
|
||||
let(:exception) {
|
||||
Haml::SyntaxError.new("you made a typo!").tap do |ex|
|
||||
ex.set_backtrace(["foo.rb:123", "haml/internals/blah.rb:123456"])
|
||||
end
|
||||
}
|
||||
|
||||
its(:message) { should == "you made a typo!" }
|
||||
its(:type) { should == Haml::SyntaxError }
|
||||
|
||||
it "has the right filename and line number in the backtrace" do
|
||||
subject.backtrace.first.filename.should == "foo.rb"
|
||||
subject.backtrace.first.line.should == 123
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
require "spec_helper"
|
||||
require "better_errors/repl/basic"
|
||||
require "better_errors/repl/shared_examples"
|
||||
|
||||
module BetterErrors
|
||||
module REPL
|
||||
describe Basic do
|
||||
let(:fresh_binding) {
|
||||
local_a = 123
|
||||
binding
|
||||
}
|
||||
|
||||
let(:repl) { Basic.new fresh_binding }
|
||||
|
||||
it_behaves_like "a REPL provider"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
require "spec_helper"
|
||||
require "pry"
|
||||
require "better_errors/repl/pry"
|
||||
require "better_errors/repl/shared_examples"
|
||||
|
||||
module BetterErrors
|
||||
module REPL
|
||||
describe Pry do
|
||||
let(:fresh_binding) {
|
||||
local_a = 123
|
||||
binding
|
||||
}
|
||||
|
||||
let(:repl) { Pry.new fresh_binding }
|
||||
|
||||
it "does line continuation" do
|
||||
output, prompt, filled = repl.send_input ""
|
||||
output.should == "=> nil\n"
|
||||
prompt.should == ">>"
|
||||
filled.should == ""
|
||||
|
||||
output, prompt, filled = repl.send_input "def f(x)"
|
||||
output.should == ""
|
||||
prompt.should == ".."
|
||||
filled.should == " "
|
||||
|
||||
output, prompt, filled = repl.send_input "end"
|
||||
if RUBY_VERSION >= "2.1.0"
|
||||
output.should == "=> :f\n"
|
||||
else
|
||||
output.should == "=> nil\n"
|
||||
end
|
||||
prompt.should == ">>"
|
||||
filled.should == ""
|
||||
end
|
||||
|
||||
it_behaves_like "a REPL provider"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
shared_examples_for "a REPL provider" do
|
||||
it "evaluates ruby code in a given context" do
|
||||
repl.send_input("local_a = 456")
|
||||
fresh_binding.eval("local_a").should == 456
|
||||
end
|
||||
|
||||
it "returns a tuple of output and the new prompt" do
|
||||
output, prompt = repl.send_input("1 + 2")
|
||||
output.should == "=> 3\n"
|
||||
prompt.should == ">>"
|
||||
end
|
||||
|
||||
it "doesn't barf if the code throws an exception" do
|
||||
output, prompt = repl.send_input("raise Exception")
|
||||
output.should include "Exception: Exception"
|
||||
prompt.should == ">>"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,157 @@
|
|||
require "spec_helper"
|
||||
|
||||
module BetterErrors
|
||||
describe StackFrame do
|
||||
context "#application?" do
|
||||
it "is true for application filenames" do
|
||||
BetterErrors.stub(:application_root).and_return("/abc/xyz")
|
||||
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
|
||||
|
||||
frame.application?.should be_true
|
||||
end
|
||||
|
||||
it "is false for everything else" do
|
||||
BetterErrors.stub(:application_root).and_return("/abc/xyz")
|
||||
frame = StackFrame.new("/abc/nope", 123, "foo")
|
||||
|
||||
frame.application?.should be_false
|
||||
end
|
||||
|
||||
it "doesn't care if no application_root is set" do
|
||||
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
|
||||
|
||||
frame.application?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
context "#gem?" do
|
||||
it "is true for gem filenames" do
|
||||
Gem.stub(:path).and_return(["/abc/xyz"])
|
||||
frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
|
||||
|
||||
frame.gem?.should be_true
|
||||
end
|
||||
|
||||
it "is false for everything else" do
|
||||
Gem.stub(:path).and_return(["/abc/xyz"])
|
||||
frame = StackFrame.new("/abc/nope", 123, "foo")
|
||||
|
||||
frame.gem?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
context "#application_path" do
|
||||
it "chops off the application root" do
|
||||
BetterErrors.stub(:application_root).and_return("/abc/xyz")
|
||||
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
|
||||
|
||||
frame.application_path.should == "app/controllers/crap_controller.rb"
|
||||
end
|
||||
end
|
||||
|
||||
context "#gem_path" do
|
||||
it "chops of the gem path and stick (gem) there" do
|
||||
Gem.stub(:path).and_return(["/abc/xyz"])
|
||||
frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
|
||||
|
||||
frame.gem_path.should == "whatever (1.2.3) lib/whatever.rb"
|
||||
end
|
||||
|
||||
it "prioritizes gem path over application path" do
|
||||
BetterErrors.stub(:application_root).and_return("/abc/xyz")
|
||||
Gem.stub(:path).and_return(["/abc/xyz/vendor"])
|
||||
frame = StackFrame.new("/abc/xyz/vendor/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
|
||||
|
||||
frame.gem_path.should == "whatever (1.2.3) lib/whatever.rb"
|
||||
end
|
||||
end
|
||||
|
||||
context "#pretty_path" do
|
||||
it "returns #application_path for application paths" do
|
||||
BetterErrors.stub(:application_root).and_return("/abc/xyz")
|
||||
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index")
|
||||
frame.pretty_path.should == frame.application_path
|
||||
end
|
||||
|
||||
it "returns #gem_path for gem paths" do
|
||||
Gem.stub(:path).and_return(["/abc/xyz"])
|
||||
frame = StackFrame.new("/abc/xyz/gems/whatever-1.2.3/lib/whatever.rb", 123, "foo")
|
||||
|
||||
frame.pretty_path.should == frame.gem_path
|
||||
end
|
||||
end
|
||||
|
||||
it "special cases SyntaxErrors" do
|
||||
begin
|
||||
eval(%{ raise SyntaxError, "you wrote bad ruby!" }, nil, "my_file.rb", 123)
|
||||
rescue SyntaxError => syntax_error
|
||||
end
|
||||
frames = StackFrame.from_exception(syntax_error)
|
||||
frames.first.filename.should == "my_file.rb"
|
||||
frames.first.line.should == 123
|
||||
end
|
||||
|
||||
it "doesn't blow up if no method name is given" do
|
||||
error = StandardError.allocate
|
||||
|
||||
error.stub(:backtrace).and_return(["foo.rb:123"])
|
||||
frames = StackFrame.from_exception(error)
|
||||
frames.first.filename.should == "foo.rb"
|
||||
frames.first.line.should == 123
|
||||
|
||||
error.stub(:backtrace).and_return(["foo.rb:123: this is an error message"])
|
||||
frames = StackFrame.from_exception(error)
|
||||
frames.first.filename.should == "foo.rb"
|
||||
frames.first.line.should == 123
|
||||
end
|
||||
|
||||
it "ignores a backtrace line if its format doesn't make any sense at all" do
|
||||
error = StandardError.allocate
|
||||
error.stub(:backtrace).and_return(["foo.rb:123:in `foo'", "C:in `find'", "bar.rb:123:in `bar'"])
|
||||
frames = StackFrame.from_exception(error)
|
||||
frames.count.should == 2
|
||||
end
|
||||
|
||||
it "doesn't blow up if a filename contains a colon" do
|
||||
error = StandardError.allocate
|
||||
error.stub(:backtrace).and_return(["crap:filename.rb:123"])
|
||||
frames = StackFrame.from_exception(error)
|
||||
frames.first.filename.should == "crap:filename.rb"
|
||||
end
|
||||
|
||||
it "doesn't blow up with a BasicObject as frame binding" do
|
||||
obj = BasicObject.new
|
||||
def obj.my_binding
|
||||
::Kernel.binding
|
||||
end
|
||||
frame = StackFrame.new("/abc/xyz/app/controllers/crap_controller.rb", 123, "index", obj.my_binding)
|
||||
frame.class_name.should == 'BasicObject'
|
||||
end
|
||||
|
||||
it "sets method names properly" do
|
||||
obj = "string"
|
||||
def obj.my_method
|
||||
begin
|
||||
raise "foo"
|
||||
rescue => err
|
||||
err
|
||||
end
|
||||
end
|
||||
|
||||
frame = StackFrame.from_exception(obj.my_method).first
|
||||
if BetterErrors.binding_of_caller_available?
|
||||
frame.method_name.should == "#my_method"
|
||||
frame.class_name.should == "String"
|
||||
else
|
||||
frame.method_name.should == "my_method"
|
||||
frame.class_name.should == nil
|
||||
end
|
||||
end
|
||||
|
||||
if RUBY_ENGINE == "java"
|
||||
it "doesn't blow up on a native Java exception" do
|
||||
expect { StackFrame.from_exception(java.lang.Exception.new) }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
seven
|
||||
eight
|
||||
nine
|
||||
ten
|
||||
eleven
|
||||
twelve
|
||||
thirteen
|
||||
fourteen
|
||||
fifteen
|
||||
sixteen
|
||||
seventeen
|
||||
eighteen
|
||||
nineteen
|
||||
twenty
|
|
@ -0,0 +1,73 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe BetterErrors do
|
||||
context ".editor" do
|
||||
it "defaults to textmate" do
|
||||
subject.editor["foo.rb", 123].should == "txmt://open?url=file://foo.rb&line=123"
|
||||
end
|
||||
|
||||
it "url escapes the filename" do
|
||||
subject.editor["&.rb", 0].should == "txmt://open?url=file://%26.rb&line=0"
|
||||
end
|
||||
|
||||
[:emacs, :emacsclient].each do |editor|
|
||||
it "uses emacs:// scheme when set to #{editor.inspect}" do
|
||||
subject.editor = editor
|
||||
subject.editor[].should start_with "emacs://"
|
||||
end
|
||||
end
|
||||
|
||||
[:macvim, :mvim].each do |editor|
|
||||
it "uses mvim:// scheme when set to #{editor.inspect}" do
|
||||
subject.editor = editor
|
||||
subject.editor[].should start_with "mvim://"
|
||||
end
|
||||
end
|
||||
|
||||
[:sublime, :subl, :st].each do |editor|
|
||||
it "uses subl:// scheme when set to #{editor.inspect}" do
|
||||
subject.editor = editor
|
||||
subject.editor[].should start_with "subl://"
|
||||
end
|
||||
end
|
||||
|
||||
[:textmate, :txmt, :tm].each do |editor|
|
||||
it "uses txmt:// scheme when set to #{editor.inspect}" do
|
||||
subject.editor = editor
|
||||
subject.editor[].should start_with "txmt://"
|
||||
end
|
||||
end
|
||||
|
||||
["emacsclient", "/usr/local/bin/emacsclient"].each do |editor|
|
||||
it "uses emacs:// scheme when EDITOR=#{editor}" do
|
||||
ENV["EDITOR"] = editor
|
||||
subject.editor = subject.default_editor
|
||||
subject.editor[].should start_with "emacs://"
|
||||
end
|
||||
end
|
||||
|
||||
["mvim -f", "/usr/local/bin/mvim -f"].each do |editor|
|
||||
it "uses mvim:// scheme when EDITOR=#{editor}" do
|
||||
ENV["EDITOR"] = editor
|
||||
subject.editor = subject.default_editor
|
||||
subject.editor[].should start_with "mvim://"
|
||||
end
|
||||
end
|
||||
|
||||
["subl -w", "/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl"].each do |editor|
|
||||
it "uses mvim:// scheme when EDITOR=#{editor}" do
|
||||
ENV["EDITOR"] = editor
|
||||
subject.editor = subject.default_editor
|
||||
subject.editor[].should start_with "subl://"
|
||||
end
|
||||
end
|
||||
|
||||
["mate -w", "/usr/bin/mate -w"].each do |editor|
|
||||
it "uses txmt:// scheme when EDITOR=#{editor}" do
|
||||
ENV["EDITOR"] = editor
|
||||
subject.editor = subject.default_editor
|
||||
subject.editor[].should start_with "txmt://"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
$: << File.expand_path("../../lib", __FILE__)
|
||||
|
||||
ENV["EDITOR"] = nil
|
||||
|
||||
require "better_errors"
|
|
@ -0,0 +1,9 @@
|
|||
module Kernel
|
||||
alias_method :require_with_binding_of_caller, :require
|
||||
|
||||
def require(feature)
|
||||
raise LoadError if feature == "binding_of_caller"
|
||||
|
||||
require_with_binding_of_caller(feature)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
language: ruby
|
||||
rvm:
|
||||
- 1.9.3
|
||||
- 2.0.0
|
||||
- 2.1.1
|
||||
bundler_args: ""
|
||||
services:
|
||||
- redis
|
||||
- memcached
|
|
@ -0,0 +1,181 @@
|
|||
28-June-2012 - Sam
|
||||
|
||||
* Started change log
|
||||
* Corrected profiler so it properly captures POST requests (was supressing non 200s)
|
||||
* Amended Rack.MiniProfiler.config[:user_provider] to use ip addres for identity
|
||||
* Fixed bug where unviewed missing ids never got cleared
|
||||
* Supress all '/assets/' in the rails tie (makes debugging easier)
|
||||
* record_sql was mega buggy
|
||||
* added MemcacheStore
|
||||
|
||||
9-July-2012 - Sam
|
||||
|
||||
* Cleaned up mechanism for profiling in production, all you need to do now
|
||||
is call Rack::MiniProfiler.authorize_request to get profiling working in
|
||||
production
|
||||
* Added option to display full backtraces pp=full-backtrace
|
||||
* Cleaned up railties, got rid of the post authorize callback
|
||||
* Version 0.1.3
|
||||
|
||||
12-July-2012 - Sam
|
||||
|
||||
* Fixed incorrect profiling steps (was not indenting or measuring start time right
|
||||
* Implemented native PG and MySql2 interceptors, this gives way more accurate times
|
||||
* Refactored context so its a proper class and not a hash
|
||||
* Added some more client probing built in to rails
|
||||
* More tests
|
||||
|
||||
18-July-2012 - Sam
|
||||
|
||||
* Added First Paint time for chrome
|
||||
* Bug fix to ensure non Rails installs have mini profiler
|
||||
* Version 0.1.7
|
||||
|
||||
30-July-2012 - Sam
|
||||
|
||||
* Made compliant with ancient versions of Rack (including Rack used by Rails2)
|
||||
* Fixed broken share link
|
||||
* Fixed crashes on startup (in MemoryStore and FileStore)
|
||||
* Version 0.1.8
|
||||
* Unicode fix
|
||||
* Version 0.1.9
|
||||
|
||||
7-August-2012 - Sam
|
||||
|
||||
* Added option to disable profiler for the current session (pp=disable / pp=enable)
|
||||
* yajl compatability contributed by Sven Riedel
|
||||
|
||||
10-August-2012 - Sam
|
||||
|
||||
* Added basic prepared statement profiling for postgres
|
||||
|
||||
20-August-2012 - Sam
|
||||
|
||||
* 1.12.pre
|
||||
* Cap X-MiniProfiler-Ids at 10, otherwise the header can get killed
|
||||
|
||||
3-September-2012 - Sam
|
||||
|
||||
* 1.13.pre
|
||||
* pg gem prepared statements were not being logged correctly
|
||||
* added setting config.backtrace_ignores = [] - an array of regexes that match on caller lines that get ignored
|
||||
* added setting config.backtrace_includes = [] - an array of regexes that get included in the trace by default
|
||||
* cleaned up the way client settings are stored
|
||||
* made pp=full-backtrace "sticky"
|
||||
* added pp=normal-backtrace to clear the "sticky" state
|
||||
* change "pp=sample" to work with "caller" no need for stack trace gem
|
||||
|
||||
4-September-2012 - Sam
|
||||
|
||||
* 1.15.pre
|
||||
* fixed annoying bug where client settings were not sticking
|
||||
* fixed long standing issue with Rack::ConditionalGet stopping MiniProfiler from working properly
|
||||
|
||||
5-September-2012 - Sam
|
||||
|
||||
* 1.16
|
||||
* fixed long standing problem specs (issue with memory store)
|
||||
* fixed issue where profiler would be dumped when you got a 404 in production (and any time rails is bypassed)
|
||||
* implemented stacktrace properly
|
||||
|
||||
9-September-2012 - Sam
|
||||
|
||||
* 1.17
|
||||
* pp=sample was bust unless stacktrace was installed
|
||||
|
||||
10-September-2012 - Sam
|
||||
|
||||
* 1.19
|
||||
* fix compat issue with 1.8.7
|
||||
|
||||
12-September-2012 - Sam
|
||||
|
||||
* 1.20
|
||||
* Added pp=profile-gc , it allows you to profile the GC in Ruby 1.9.3
|
||||
|
||||
17-September-2012
|
||||
* 1.21
|
||||
* New MemchacedStore
|
||||
* Rails 4 support
|
||||
|
||||
17-September-2012
|
||||
* Allow rack-mini-profiler to be sourced from github
|
||||
* Extracted the pp=profile-gc-time out, the object space profiler needs to disable gc
|
||||
|
||||
20-September-2012
|
||||
* 1.22
|
||||
* Fix permission issue in the gem
|
||||
|
||||
8-April-2013
|
||||
* 1.24
|
||||
* Flame Graph Support see: http://samsaffron.com/archive/2013/03/19/flame-graphs-in-ruby-miniprofiler
|
||||
* Fix file retention leak in file_store
|
||||
* New toggle_shortcut and start_hidden options
|
||||
* Fix for AngularJS support and MooTools
|
||||
* More robust gc profiling
|
||||
* Mongoid support
|
||||
* Fix for html5 implicit body tags
|
||||
* script tag initialized via data-attributes
|
||||
* new - Rack::MiniProfiler.counter counter_name {}
|
||||
* Allow usage of existing jQuery if its already loaded
|
||||
* Fix pp=enable
|
||||
* 1.8.7 support ... grrr
|
||||
* Net:HTTP profiling
|
||||
* pre authorize to run in all non development? and production? modes
|
||||
|
||||
8-April-2013
|
||||
* 1.25
|
||||
* Missed flamegraph.html from build
|
||||
|
||||
11-April-2013
|
||||
* 1.26
|
||||
* (minor) allow Rack::MiniProfilerRails.initialize!(Rails.application), for post config intialization
|
||||
|
||||
26-June-2013
|
||||
* 1.27
|
||||
* Disable global ajax handlers on MP requests @JP
|
||||
* Add Rack::MiniProfiler.config.backtrace_threshold_ms
|
||||
* jQuery 2.0 support
|
||||
|
||||
18-July-2013
|
||||
* 1.28
|
||||
* diagnostics in abstract storage was raising not implemented killing
|
||||
?pp=env and others
|
||||
* SOLR xml unescaped by mistake
|
||||
|
||||
20-August-2013
|
||||
* 1.29
|
||||
* Bugfix: SOLR patching had an incorrect monkey patch
|
||||
* Implemented exception tracing using TracePoint see pp=trace-exceptions
|
||||
|
||||
30-August-2013
|
||||
|
||||
* 1.30
|
||||
* Feature: Added Rack::MiniProfiler.counter_method(klass,name) for injecting counters
|
||||
* Bug: Counters were not shifting the table correctly
|
||||
|
||||
3-September-2013
|
||||
|
||||
* Ripped out flamegraph so it can be isolated into a gem
|
||||
* Flamegraph now has much increased fidelity
|
||||
* Ripped out pp=sample it just was never really used
|
||||
|
||||
17-September-2013 - Ross Wilson
|
||||
* Instead of supressing all "/assets/" requests we now check the configured
|
||||
config.assets.prefix path since developers can rename the path to serve Asset Pipeline
|
||||
files from
|
||||
|
||||
12-December-2013 - Sam Saffron
|
||||
* Version 0.9.0.pre (bumped up to reflect the stability of the project)
|
||||
* Improved reports for pp=profile-gc
|
||||
* pp=flamegraph&flamegraph_sample_rate=1 , allow you to specify sampling rates
|
||||
|
||||
13-March-2014 - Sam Saffron
|
||||
* Version 0.9.1
|
||||
* Added back Ruby 1.8 support (thanks Malet)
|
||||
* Corrected Rails 3.0 support (thanks Zlatko)
|
||||
* Corrected fix possible XSS (admin only)
|
||||
* Amend Railstie so MiniProfiler can be launched with action view or action controller (Thanks Akira)
|
||||
* Corrected Sql patching to avoid setting instance vars on nil which is frozen (thanks Andy, huoxito)
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
source 'http://rubygems.org'
|
||||
|
||||
gemspec
|
|
@ -0,0 +1,271 @@
|
|||
# rack-mini-profiler
|
||||
|
||||
[![Code Climate](https://codeclimate.com/github/MiniProfiler/rack-mini-profiler.png)](https://codeclimate.com/github/MiniProfiler/rack-mini-profiler) [![Build Status](https://travis-ci.org/MiniProfiler/rack-mini-profiler.png)](https://travis-ci.org/MiniProfiler/rack-mini-profiler)
|
||||
|
||||
Middleware that displays speed badge for every html page. Designed to work both in production and in development.
|
||||
|
||||
#### Features
|
||||
|
||||
* database profiling. Currently supports Mysql2, Postgres, and Mongoid3 (with fallback support to ActiveRecord)
|
||||
|
||||
#### Learn more
|
||||
|
||||
* [Visit our community](http://community.miniprofiler.com)
|
||||
* [Watch the RailsCast](http://railscasts.com/episodes/368-miniprofiler)
|
||||
* [Read about Flame graphs in rack-mini-profiler](http://samsaffron.com/archive/2013/03/19/flame-graphs-in-ruby-miniprofiler)
|
||||
* [Read the announcement posts from 2012](http://samsaffron.com/archive/2012/07/12/miniprofiler-ruby-edition)
|
||||
|
||||
## rack-mini-profiler needs your help
|
||||
|
||||
We have decided to restructure our repository so there is a central UI repo and the various language implementation have their own.
|
||||
|
||||
**WE NEED HELP.**
|
||||
|
||||
- Setting up a build that reuses https://github.com/MiniProfiler/ui
|
||||
- Migrating the internal data structures [per the spec](https://github.com/MiniProfiler/ui)
|
||||
- Cleaning up the [horrendous class structure that is using strings as keys and crazy non-objects](https://github.com/MiniProfiler/rack-mini-profiler/blob/master/lib/mini_profiler/sql_timer_struct.rb#L36-L44)
|
||||
|
||||
If you feel like taking on any of this start an issue and update us on your progress.
|
||||
|
||||
## Installation
|
||||
|
||||
Install/add to Gemfile
|
||||
|
||||
```ruby
|
||||
gem 'rack-mini-profiler'
|
||||
```
|
||||
|
||||
NOTE: Be sure to require rack_mini_profiler below the `pg` and `mysql` gems in your Gemfile. rack_mini_profiler will identify these gems if they are loaded to insert instrumentation. If included too early no SQL will show up.
|
||||
|
||||
#### Rails
|
||||
|
||||
All you have to do is include the Gem and you're good to go in development. See notes below for use in production.
|
||||
|
||||
#### Rails and manual initialization
|
||||
|
||||
In case you need to make sure rack_mini_profiler initialized after all other gems.
|
||||
Or you want to execute some code before rack_mini_profiler required.
|
||||
|
||||
```ruby
|
||||
gem 'rack-mini-profiler', require: false
|
||||
```
|
||||
Note the `require: false` part - if omitted, it will cause the Railtie for the mino-profiler to
|
||||
be loaded outright, and an attempt to re-initialize it manually will raise an exception.
|
||||
|
||||
Then put initialize code in file like `config/initializers/rack_profiler.rb`
|
||||
|
||||
```ruby
|
||||
if Rails.env == 'development'
|
||||
require 'rack-mini-profiler'
|
||||
|
||||
# initialization is skipped so trigger it
|
||||
Rack::MiniProfilerRails.initialize!(Rails.application)
|
||||
end
|
||||
```
|
||||
|
||||
#### Rack Builder
|
||||
|
||||
```ruby
|
||||
require 'rack-mini-profiler'
|
||||
builder = Rack::Builder.new do
|
||||
use Rack::MiniProfiler
|
||||
|
||||
map('/') { run get }
|
||||
end
|
||||
```
|
||||
|
||||
#### Sinatra
|
||||
|
||||
```ruby
|
||||
require 'rack-mini-profiler'
|
||||
class MyApp < Sinatra::Base
|
||||
use Rack::MiniProfiler
|
||||
end
|
||||
```
|
||||
|
||||
### Flamegraphs
|
||||
|
||||
To generate [flamegraphs](http://samsaffron.com/archive/2013/03/19/flame-graphs-in-ruby-miniprofiler):
|
||||
|
||||
* add the **flamegraph** gem to your Gemfile
|
||||
* visit a page in your app with `?pp=flamegraph`
|
||||
|
||||
Flamegraph generation is supported in MRI 2.0 and 2.1 only.
|
||||
|
||||
|
||||
## Access control in production
|
||||
|
||||
rack-mini-profiler is designed with production profiling in mind. To enable that just run `Rack::MiniProfiler.authorize_request` once you know a request is allowed to profile.
|
||||
|
||||
```ruby
|
||||
# A hook in your ApplicationController
|
||||
def authorize
|
||||
if current_user.is_admin?
|
||||
Rack::MiniProfiler.authorize_request
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Various aspects of rack-mini-profiler's behavior can be configured when your app boots.
|
||||
For example in a Rails app, this should be done in an initializer:
|
||||
**config/initializers/mini_profiler.rb**
|
||||
|
||||
### Storage
|
||||
|
||||
rack-mini-profiler stores its results so they can be shared later and aren't lost at the end of the request.
|
||||
|
||||
There are 4 storage options: `MemoryStore`, `RedisStore`, `MemcacheStore`, and `FileStore`.
|
||||
|
||||
`FileStore` is the default in Rails environments and will write files to `tmp/miniprofiler/*`. `MemoryStore` is the default otherwise.
|
||||
|
||||
```ruby
|
||||
# set MemoryStore
|
||||
Rack::MiniProfiler.config.storage = Rack::MiniProfiler::MemoryStore
|
||||
|
||||
# set RedisStore
|
||||
if Rails.env.production?
|
||||
uri = URI.parse(ENV["REDIS_SERVER_URL"])
|
||||
Rack::MiniProfiler.config.storage_options = { :host => uri.host, :port => uri.port, :password => uri.password }
|
||||
Rack::MiniProfiler.config.storage = Rack::MiniProfiler::RedisStore
|
||||
end
|
||||
```
|
||||
|
||||
MemoryStore stores results in a processes heap - something that does not work well in a multi process environment.
|
||||
FileStore stores results in the file system - something that may not work well in a multi machine environment.
|
||||
RedisStore/MemcacheStore work in multi process and multi machine environments (RedisStore only saves results for up to 24 hours so it won't continue to fill up Redis).
|
||||
|
||||
Additionally you may implement an AbstractStore for your own provider.
|
||||
|
||||
### User result segregation
|
||||
|
||||
MiniProfiler will attempt to keep all user results isolated, out-of-the-box the user provider uses the ip address:
|
||||
|
||||
```ruby
|
||||
Rack::MiniProfiler.config.user_provider = Proc.new{|env| Rack::Request.new(env).ip}
|
||||
```
|
||||
|
||||
You can override (something that is very important in a multi-machine production setup):
|
||||
|
||||
```ruby
|
||||
Rack::MiniProfiler.config.user_provider = Proc.new{ |env| CurrentUser.get(env) }
|
||||
```
|
||||
|
||||
The string this function returns should be unique for each user on the system (for anonymous you may need to fall back to ip address)
|
||||
|
||||
### Configuration Options
|
||||
|
||||
You can set configuration options using the configuration accessor on `Rack::MiniProfiler`.
|
||||
For example:
|
||||
|
||||
```ruby
|
||||
Rack::MiniProfiler.config.position = 'right'
|
||||
Rack::MiniProfiler.config.start_hidden = true
|
||||
```
|
||||
The available configuration options are:
|
||||
|
||||
* pre_authorize_cb - A lambda callback you can set to determine whether or not mini_profiler should be visible on a given request. Default in a Rails environment is only on in development mode. If in a Rack app, the default is always on.
|
||||
* position - Can either be 'right' or 'left'. Default is 'left'.
|
||||
* skip_schema_queries - Whether or not you want to log the queries about the schema of your tables. Default is 'false', 'true' in rails development.
|
||||
* auto_inject (default true) - when false the miniprofiler script is not injected in the page
|
||||
* backtrace_filter - a regex you can use to filter out unwanted lines from the backtraces
|
||||
* toggle_shortcut (default Alt+P) - a jquery.hotkeys.js-style keyboard shortcut, used to toggle the mini_profiler's visibility. See http://code.google.com/p/js-hotkeys/ for more info.
|
||||
* start_hidden (default false) - Whether or not you want the mini_profiler to be visible when loading a page
|
||||
* backtrace_threshold_ms (default zero) - Minimum SQL query elapsed time before a backtrace is recorded. Backtrace recording can take a couple of milliseconds on rubies earlier than 2.0, impacting performance for very small queries.
|
||||
* flamegraph_sample_rate (default 0.5ms) - How often fast_stack should get stack trace info to generate flamegraphs
|
||||
|
||||
### Custom middleware ordering (required if using `Rack::Deflate` with Rails)
|
||||
|
||||
If you are using `Rack::Deflate` with rails and rack-mini-profiler in its default configuration,
|
||||
`Rack::MiniProfiler` will be injected (as always) at position 0 in the middleware stack. This
|
||||
will result in it attempting to inject html into the already-compressed response body. To fix this,
|
||||
the middleware ordering must be overriden.
|
||||
|
||||
To do this, first add `, require: false` to the gemfile entry for rack-mini-profiler.
|
||||
This will prevent the railtie from running. Then, customize the initialization
|
||||
in the initializer like so:
|
||||
|
||||
```ruby
|
||||
require 'rack-mini-profiler'
|
||||
|
||||
Rack::MiniProfilerRails.initialize!(Rails.application)
|
||||
|
||||
Rails.application.middleware.delete(Rack::MiniProfiler)
|
||||
Rails.application.middleware.insert_after(Rack::Deflater, Rack::MiniProfiler)
|
||||
```
|
||||
|
||||
Deleting the middleware and then reinserting it is a bit inelegant, but
|
||||
a sufficient and costless solution. It is possible that rack-mini-profiler might
|
||||
support this scenario more directly if it is found that
|
||||
there is significant need for this confriguration or that
|
||||
the above recipe causes problems.
|
||||
|
||||
|
||||
## Special query strings
|
||||
|
||||
If you include the query string `pp=help` at the end of your request you will see the various options available. You can use these options to extend or contract the amount of diagnostics rack-mini-profiler gathers.
|
||||
|
||||
|
||||
## Rails 2.X support
|
||||
|
||||
To get MiniProfiler working with Rails 2.3.X you need to do the initialization manually as well as monkey patch away an incompatibility between activesupport and json_pure.
|
||||
|
||||
Add the following code to your environment.rb (or just in a specific environment such as development.rb) for initialization and configuration of MiniProfiler.
|
||||
|
||||
```ruby
|
||||
# configure and initialize MiniProfiler
|
||||
require 'rack-mini-profiler'
|
||||
c = ::Rack::MiniProfiler.config
|
||||
c.pre_authorize_cb = lambda { |env|
|
||||
Rails.env.development? || Rails.env.production?
|
||||
}
|
||||
tmp = Rails.root.to_s + "/tmp/miniprofiler"
|
||||
FileUtils.mkdir_p(tmp) unless File.exists?(tmp)
|
||||
c.storage_options = {:path => tmp}
|
||||
c.storage = ::Rack::MiniProfiler::FileStore
|
||||
config.middleware.use(::Rack::MiniProfiler)
|
||||
::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
|
||||
::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{path_without_format_and_extension}"}
|
||||
|
||||
# monkey patch away an activesupport and json_pure incompatability
|
||||
# http://pivotallabs.com/users/alex/blog/articles/1332-monkey-patch-of-the-day-activesupport-vs-json-pure-vs-ruby-1-8
|
||||
if JSON.const_defined?(:Pure)
|
||||
class JSON::Pure::Generator::State
|
||||
include ActiveSupport::CoreExtensions::Hash::Except
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Running the Specs
|
||||
|
||||
```
|
||||
$ rake build
|
||||
$ rake spec
|
||||
```
|
||||
|
||||
Additionally you can also run `autotest` if you like.
|
||||
|
||||
## Licence
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Sam Saffron
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,46 @@
|
|||
# Rakefile
|
||||
require 'rubygems'
|
||||
require 'bundler'
|
||||
Bundler.setup(:default, :test)
|
||||
|
||||
task :default => [:spec]
|
||||
|
||||
require 'rspec/core'
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new(:spec) do |spec|
|
||||
spec.pattern = FileList['spec/**/*_spec.rb']
|
||||
end
|
||||
|
||||
desc "builds a gem"
|
||||
task :build => :update_asset_version do
|
||||
`gem build rack-mini-profiler.gemspec 1>&2`
|
||||
end
|
||||
|
||||
desc "compile less"
|
||||
task :compile_less => :copy_files do
|
||||
`lessc lib/html/includes.less > lib/html/includes.css`
|
||||
end
|
||||
|
||||
desc "update asset version file"
|
||||
task :update_asset_version => :compile_less do
|
||||
require 'digest/md5'
|
||||
h = []
|
||||
Dir.glob('lib/html/*.{js,html,css,tmpl}').each do |f|
|
||||
h << Digest::MD5.hexdigest(::File.read(f))
|
||||
end
|
||||
File.open('lib/mini_profiler/version.rb','w') do |f|
|
||||
f.write \
|
||||
"module Rack
|
||||
class MiniProfiler
|
||||
VERSION = '#{Digest::MD5.hexdigest(h.sort.join(''))}'.freeze
|
||||
end
|
||||
end"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
desc "copy files from other parts of the tree"
|
||||
task :copy_files do
|
||||
# TODO grab files from MiniProfiler/UI
|
||||
end
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Autotest.add_discovery { "rspec2" }
|
||||
|
|
@ -0,0 +1,451 @@
|
|||
.profiler-result,
|
||||
.profiler-queries {
|
||||
color: #555;
|
||||
line-height: 1;
|
||||
font-size: 12px;
|
||||
}
|
||||
.profiler-result pre,
|
||||
.profiler-queries pre,
|
||||
.profiler-result code,
|
||||
.profiler-queries code,
|
||||
.profiler-result label,
|
||||
.profiler-queries label,
|
||||
.profiler-result table,
|
||||
.profiler-queries table,
|
||||
.profiler-result tbody,
|
||||
.profiler-queries tbody,
|
||||
.profiler-result thead,
|
||||
.profiler-queries thead,
|
||||
.profiler-result tfoot,
|
||||
.profiler-queries tfoot,
|
||||
.profiler-result tr,
|
||||
.profiler-queries tr,
|
||||
.profiler-result th,
|
||||
.profiler-queries th,
|
||||
.profiler-result td,
|
||||
.profiler-queries td {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
background-color: transparent;
|
||||
overflow: visible;
|
||||
max-height: none;
|
||||
}
|
||||
.profiler-result table,
|
||||
.profiler-queries table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
.profiler-result a,
|
||||
.profiler-queries a,
|
||||
.profiler-result a:hover,
|
||||
.profiler-queries a:hover {
|
||||
cursor: pointer;
|
||||
color: #0077cc;
|
||||
}
|
||||
.profiler-result a,
|
||||
.profiler-queries a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.profiler-result a:hover,
|
||||
.profiler-queries a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.profiler-result {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.profiler-result .profiler-toggle-duration-with-children {
|
||||
float: right;
|
||||
}
|
||||
.profiler-result table.profiler-client-timings {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.profiler-result .profiler-label {
|
||||
color: #555555;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.profiler-result .profiler-unit {
|
||||
color: #aaaaaa;
|
||||
}
|
||||
.profiler-result .profiler-trivial {
|
||||
display: none;
|
||||
}
|
||||
.profiler-result .profiler-trivial td,
|
||||
.profiler-result .profiler-trivial td * {
|
||||
color: #aaaaaa !important;
|
||||
}
|
||||
.profiler-result pre,
|
||||
.profiler-result code,
|
||||
.profiler-result .profiler-number,
|
||||
.profiler-result .profiler-unit {
|
||||
font-family: Consolas, monospace, serif;
|
||||
}
|
||||
.profiler-result .profiler-number {
|
||||
color: #111111;
|
||||
}
|
||||
.profiler-result .profiler-info {
|
||||
text-align: right;
|
||||
}
|
||||
.profiler-result .profiler-info .profiler-name {
|
||||
float: left;
|
||||
}
|
||||
.profiler-result .profiler-info .profiler-server-time {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.profiler-result .profiler-timings th {
|
||||
background-color: #fff;
|
||||
color: #aaaaaa;
|
||||
text-align: right;
|
||||
}
|
||||
.profiler-result .profiler-timings th,
|
||||
.profiler-result .profiler-timings td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.profiler-result .profiler-timings .profiler-duration-with-children {
|
||||
display: none;
|
||||
}
|
||||
.profiler-result .profiler-timings .profiler-duration {
|
||||
font-family: Consolas, monospace, serif;
|
||||
color: #111111;
|
||||
text-align: right;
|
||||
}
|
||||
.profiler-result .profiler-timings .profiler-indent {
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
.profiler-result .profiler-timings .profiler-queries-show .profiler-number,
|
||||
.profiler-result .profiler-timings .profiler-queries-show .profiler-unit {
|
||||
color: #0077cc;
|
||||
}
|
||||
.profiler-result .profiler-timings .profiler-queries-duration {
|
||||
padding-left: 6px;
|
||||
}
|
||||
.profiler-result .profiler-timings .profiler-percent-in-sql {
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
.profiler-result .profiler-timings tfoot td {
|
||||
padding-top: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
.profiler-result .profiler-timings tfoot td a {
|
||||
font-size: 95%;
|
||||
display: inline-block;
|
||||
margin-left: 12px;
|
||||
}
|
||||
.profiler-result .profiler-timings tfoot td a:first-child {
|
||||
float: left;
|
||||
margin-left: 0px;
|
||||
}
|
||||
.profiler-result .profiler-timings tfoot td a.profiler-custom-link {
|
||||
float: left;
|
||||
}
|
||||
.profiler-result .profiler-queries {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.profiler-result .profiler-queries .profiler-stack-trace {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.profiler-result .profiler-queries pre {
|
||||
font-family: Consolas, monospace, serif;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.profiler-result .profiler-queries th {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #555;
|
||||
font-weight: bold;
|
||||
padding: 15px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.profiler-result .profiler-queries td {
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
.profiler-result .profiler-queries td:last-child {
|
||||
padding-right: 25px;
|
||||
}
|
||||
.profiler-result .profiler-queries .profiler-odd td {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
.profiler-result .profiler-queries .profiler-since-start,
|
||||
.profiler-result .profiler-queries .profiler-duration {
|
||||
text-align: right;
|
||||
}
|
||||
.profiler-result .profiler-queries .profiler-info div {
|
||||
text-align: right;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.profiler-result .profiler-queries .profiler-gap-info,
|
||||
.profiler-result .profiler-queries .profiler-gap-info td {
|
||||
background-color: #ccc;
|
||||
}
|
||||
.profiler-result .profiler-queries .profiler-gap-info .profiler-unit {
|
||||
color: #777;
|
||||
}
|
||||
.profiler-result .profiler-queries .profiler-gap-info .profiler-info {
|
||||
text-align: right;
|
||||
}
|
||||
.profiler-result .profiler-queries .profiler-gap-info.profiler-trivial-gaps {
|
||||
display: none;
|
||||
}
|
||||
.profiler-result .profiler-queries .profiler-trivial-gap-container {
|
||||
text-align: center;
|
||||
}
|
||||
.profiler-result .profiler-queries .str {
|
||||
color: #800000;
|
||||
}
|
||||
.profiler-result .profiler-queries .kwd {
|
||||
color: #00008b;
|
||||
}
|
||||
.profiler-result .profiler-queries .com {
|
||||
color: #808080;
|
||||
}
|
||||
.profiler-result .profiler-queries .typ {
|
||||
color: #2b91af;
|
||||
}
|
||||
.profiler-result .profiler-queries .lit {
|
||||
color: #800000;
|
||||
}
|
||||
.profiler-result .profiler-queries .pun {
|
||||
color: #000000;
|
||||
}
|
||||
.profiler-result .profiler-queries .pln {
|
||||
color: #000000;
|
||||
}
|
||||
.profiler-result .profiler-queries .tag {
|
||||
color: #800000;
|
||||
}
|
||||
.profiler-result .profiler-queries .atn {
|
||||
color: #ff0000;
|
||||
}
|
||||
.profiler-result .profiler-queries .atv {
|
||||
color: #0000ff;
|
||||
}
|
||||
.profiler-result .profiler-queries .dec {
|
||||
color: #800080;
|
||||
}
|
||||
.profiler-result .profiler-warning,
|
||||
.profiler-result .profiler-warning *,
|
||||
.profiler-result .profiler-warning .profiler-queries-show,
|
||||
.profiler-result .profiler-warning .profiler-queries-show .profiler-unit {
|
||||
color: #f00;
|
||||
}
|
||||
.profiler-result .profiler-warning:hover,
|
||||
.profiler-result .profiler-warning *:hover,
|
||||
.profiler-result .profiler-warning .profiler-queries-show:hover,
|
||||
.profiler-result .profiler-warning .profiler-queries-show .profiler-unit:hover {
|
||||
color: #f00;
|
||||
}
|
||||
.profiler-result .profiler-nuclear {
|
||||
color: #f00;
|
||||
font-weight: bold;
|
||||
padding-right: 2px;
|
||||
}
|
||||
.profiler-result .profiler-nuclear:hover {
|
||||
color: #f00;
|
||||
}
|
||||
.profiler-results {
|
||||
z-index: 2147483643;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
}
|
||||
.profiler-results.profiler-left {
|
||||
left: 0px;
|
||||
}
|
||||
.profiler-results.profiler-left.profiler-no-controls .profiler-result:last-child .profiler-button,
|
||||
.profiler-results.profiler-left .profiler-controls {
|
||||
-webkit-border-bottom-right-radius: 10px;
|
||||
-moz-border-radius-bottomright: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
.profiler-results.profiler-left .profiler-button,
|
||||
.profiler-results.profiler-left .profiler-controls {
|
||||
border-right: 1px solid #888888;
|
||||
}
|
||||
.profiler-results.profiler-right {
|
||||
right: 0px;
|
||||
}
|
||||
.profiler-results.profiler-right.profiler-no-controls .profiler-result:last-child .profiler-button,
|
||||
.profiler-results.profiler-right .profiler-controls {
|
||||
-webkit-border-bottom-left-radius: 10px;
|
||||
-moz-border-radius-bottomleft: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
}
|
||||
.profiler-results.profiler-right .profiler-button,
|
||||
.profiler-results.profiler-right .profiler-controls {
|
||||
border-left: 1px solid #888888;
|
||||
}
|
||||
.profiler-results .profiler-button,
|
||||
.profiler-results .profiler-controls {
|
||||
display: none;
|
||||
z-index: 2147483640;
|
||||
border-bottom: 1px solid #888888;
|
||||
background-color: #fff;
|
||||
padding: 4px 7px;
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
.profiler-results .profiler-button.profiler-button-active,
|
||||
.profiler-results .profiler-controls.profiler-button-active {
|
||||
background-color: maroon;
|
||||
}
|
||||
.profiler-results .profiler-button.profiler-button-active .profiler-number,
|
||||
.profiler-results .profiler-controls.profiler-button-active .profiler-number,
|
||||
.profiler-results .profiler-button.profiler-button-active .profiler-nuclear,
|
||||
.profiler-results .profiler-controls.profiler-button-active .profiler-nuclear {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
.profiler-results .profiler-button.profiler-button-active .profiler-unit,
|
||||
.profiler-results .profiler-controls.profiler-button-active .profiler-unit {
|
||||
color: #fff;
|
||||
font-weight: normal;
|
||||
}
|
||||
.profiler-results .profiler-controls {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-family: Consolas, monospace, serif;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
}
|
||||
.profiler-results .profiler-controls span {
|
||||
border-right: 1px solid #aaaaaa;
|
||||
padding-right: 5px;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.profiler-results .profiler-controls span:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
.profiler-results .profiler-popup {
|
||||
display: none;
|
||||
z-index: 2147483641;
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
border: 1px solid #aaa;
|
||||
padding: 5px 10px;
|
||||
text-align: left;
|
||||
line-height: 18px;
|
||||
overflow: auto;
|
||||
-moz-box-shadow: 0px 1px 15px #555555;
|
||||
-webkit-box-shadow: 0px 1px 15px #555555;
|
||||
box-shadow: 0px 1px 15px #555555;
|
||||
}
|
||||
.profiler-results .profiler-popup .profiler-info {
|
||||
margin-bottom: 3px;
|
||||
padding-bottom: 2px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.profiler-results .profiler-popup .profiler-info .profiler-name {
|
||||
font-size: 110%;
|
||||
font-weight: bold;
|
||||
}
|
||||
.profiler-results .profiler-popup .profiler-info .profiler-name .profiler-overall-duration {
|
||||
display: none;
|
||||
}
|
||||
.profiler-results .profiler-popup .profiler-info .profiler-server-time {
|
||||
font-size: 95%;
|
||||
}
|
||||
.profiler-results .profiler-popup .profiler-timings th,
|
||||
.profiler-results .profiler-popup .profiler-timings td {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
.profiler-results .profiler-popup .profiler-timings th {
|
||||
font-size: 95%;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
.profiler-results .profiler-popup .profiler-timings .profiler-label {
|
||||
max-width: 275px;
|
||||
}
|
||||
.profiler-results .profiler-queries {
|
||||
display: none;
|
||||
z-index: 2147483643;
|
||||
position: absolute;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
.profiler-results .profiler-queries th {
|
||||
font-size: 17px;
|
||||
}
|
||||
.profiler-results.profiler-min .profiler-result {
|
||||
display: none;
|
||||
}
|
||||
.profiler-results.profiler-min .profiler-controls span {
|
||||
display: none;
|
||||
}
|
||||
.profiler-results.profiler-min .profiler-controls .profiler-min-max {
|
||||
border-right: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
.profiler-queries-bg {
|
||||
z-index: 2147483642;
|
||||
display: none;
|
||||
background: #000;
|
||||
opacity: 0.7;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
min-width: 100%;
|
||||
}
|
||||
.profiler-result-full .profiler-result {
|
||||
width: 950px;
|
||||
margin: 30px auto;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-button {
|
||||
display: none;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-popup .profiler-info {
|
||||
font-size: 25px;
|
||||
border-bottom: 1px solid #aaaaaa;
|
||||
padding-bottom: 3px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-popup .profiler-info .profiler-overall-duration {
|
||||
padding-right: 20px;
|
||||
font-size: 80%;
|
||||
color: #888;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-popup .profiler-timings td,
|
||||
.profiler-result-full .profiler-result .profiler-popup .profiler-timings th {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-popup .profiler-timings th {
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-popup .profiler-timings td {
|
||||
font-size: 14px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-popup .profiler-timings td:first-child {
|
||||
padding-left: 10px;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-popup .profiler-timings .profiler-label {
|
||||
max-width: 550px;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-queries {
|
||||
margin: 25px 0;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-queries table {
|
||||
width: 100%;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-queries th {
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
line-height: 20px;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-queries td {
|
||||
padding: 15px 10px;
|
||||
text-align: left;
|
||||
}
|
||||
.profiler-result-full .profiler-result .profiler-queries .profiler-info div {
|
||||
text-align: right;
|
||||
margin-bottom: 5px;
|
||||
}
|
|
@ -0,0 +1,960 @@
|
|||
"use strict";
|
||||
var MiniProfiler = (function () {
|
||||
var $;
|
||||
|
||||
var options,
|
||||
container,
|
||||
controls,
|
||||
fetchedIds = [],
|
||||
fetchingIds = [], // so we never pull down a profiler twice
|
||||
ajaxStartTime
|
||||
;
|
||||
|
||||
var hasLocalStorage = function () {
|
||||
try {
|
||||
return 'localStorage' in window && window['localStorage'] !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var getVersionedKey = function (keyPrefix) {
|
||||
return keyPrefix + '-' + options.version;
|
||||
};
|
||||
|
||||
var save = function (keyPrefix, value) {
|
||||
if (!hasLocalStorage()) { return; }
|
||||
|
||||
// clear old keys with this prefix, if any
|
||||
for (var i = 0; i < localStorage.length; i++) {
|
||||
if ((localStorage.key(i) || '').indexOf(keyPrefix) > -1) {
|
||||
localStorage.removeItem(localStorage.key(i));
|
||||
}
|
||||
}
|
||||
|
||||
// save under this version
|
||||
localStorage[getVersionedKey(keyPrefix)] = value;
|
||||
};
|
||||
|
||||
var load = function (keyPrefix) {
|
||||
if (!hasLocalStorage()) { return null; }
|
||||
|
||||
return localStorage[getVersionedKey(keyPrefix)];
|
||||
};
|
||||
|
||||
var fetchTemplates = function (success) {
|
||||
var key = 'templates',
|
||||
cached = load(key);
|
||||
|
||||
if (cached) {
|
||||
$('body').append(cached);
|
||||
success();
|
||||
}
|
||||
else {
|
||||
$.get(options.path + 'includes.tmpl?v=' + options.version, function (data) {
|
||||
if (data) {
|
||||
save(key, data);
|
||||
$('body').append(data);
|
||||
success();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var getClientPerformance = function() {
|
||||
return window.performance == null ? null : window.performance;
|
||||
};
|
||||
|
||||
var fetchResults = function (ids) {
|
||||
var clientPerformance, clientProbes, i, j, p, id, idx;
|
||||
|
||||
for (i = 0; i < ids.length; i++) {
|
||||
id = ids[i];
|
||||
|
||||
clientPerformance = null;
|
||||
clientProbes = null;
|
||||
|
||||
if (window.mPt) {
|
||||
clientProbes = mPt.results();
|
||||
for (j = 0; j < clientProbes.length; j++) {
|
||||
clientProbes[j].d = clientProbes[j].d.getTime();
|
||||
}
|
||||
mPt.flush();
|
||||
}
|
||||
|
||||
if (id == options.currentId) {
|
||||
|
||||
clientPerformance = getClientPerformance();
|
||||
|
||||
if (clientPerformance != null) {
|
||||
// ie is buggy strip out functions
|
||||
var copy = { navigation: {}, timing: {} };
|
||||
|
||||
var timing = $.extend({}, clientPerformance.timing);
|
||||
|
||||
for (p in timing) {
|
||||
if (timing.hasOwnProperty(p) && !$.isFunction(timing[p])) {
|
||||
copy.timing[p] = timing[p];
|
||||
}
|
||||
}
|
||||
if (clientPerformance.navigation) {
|
||||
copy.navigation.redirectCount = clientPerformance.navigation.redirectCount;
|
||||
}
|
||||
clientPerformance = copy;
|
||||
|
||||
// hack to add chrome timings
|
||||
if (window.chrome && window.chrome.loadTimes) {
|
||||
var chromeTimes = window.chrome.loadTimes();
|
||||
if (chromeTimes.firstPaintTime) {
|
||||
clientPerformance.timing["First Paint Time"] = Math.round(chromeTimes.firstPaintTime * 1000);
|
||||
}
|
||||
if (chromeTimes.firstPaintTime) {
|
||||
clientPerformance.timing["First Paint After Load Time"] = Math.round(chromeTimes.firstPaintAfterLoadTime * 1000);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else if (ajaxStartTime != null && clientProbes && clientProbes.length > 0) {
|
||||
clientPerformance = { timing: { navigationStart: ajaxStartTime.getTime() } };
|
||||
ajaxStartTime = null;
|
||||
}
|
||||
|
||||
if ($.inArray(id, fetchedIds) < 0 && $.inArray(id, fetchingIds) < 0) {
|
||||
idx = fetchingIds.push(id) - 1;
|
||||
|
||||
$.ajax({
|
||||
url: options.path + 'results',
|
||||
data: { id: id, clientPerformance: clientPerformance, clientProbes: clientProbes, popup: 1 },
|
||||
dataType: 'json',
|
||||
global: false,
|
||||
type: 'POST',
|
||||
success: function (json) {
|
||||
fetchedIds.push(id);
|
||||
if (json != "hidden") {
|
||||
buttonShow(json);
|
||||
}
|
||||
},
|
||||
complete: function () {
|
||||
fetchingIds.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var renderTemplate = function (json) {
|
||||
return $('#profilerTemplate').tmpl(json);
|
||||
};
|
||||
|
||||
var buttonShow = function (json) {
|
||||
var result = renderTemplate(json);
|
||||
|
||||
if (controls)
|
||||
result.insertBefore(controls);
|
||||
else
|
||||
result.appendTo(container);
|
||||
|
||||
var button = result.find('.profiler-button'),
|
||||
popup = result.find('.profiler-popup');
|
||||
|
||||
// button will appear in corner with the total profiling duration - click to show details
|
||||
button.click(function () { buttonClick(button, popup); });
|
||||
|
||||
// small duration steps and the column with aggregate durations are hidden by default; allow toggling
|
||||
toggleHidden(popup);
|
||||
|
||||
// lightbox in the queries
|
||||
popup.find('.profiler-queries-show').click(function () { queriesShow($(this), result); });
|
||||
|
||||
// limit count
|
||||
if (container.find('.profiler-result').length > options.maxTracesToShow)
|
||||
container.find('.profiler-result').first().remove();
|
||||
button.show();
|
||||
};
|
||||
|
||||
var toggleHidden = function (popup) {
|
||||
var trivial = popup.find('.profiler-toggle-trivial');
|
||||
var childrenTime = popup.find('.profiler-toggle-duration-with-children');
|
||||
var trivialGaps = popup.parent().find('.profiler-toggle-trivial-gaps');
|
||||
|
||||
var toggleIt = function (node) {
|
||||
var link = $(node),
|
||||
klass = "profiler-" + link.attr('class').substr('profiler-toggle-'.length),
|
||||
isHidden = link.text().indexOf('show') > -1;
|
||||
|
||||
popup.parent().find('.' + klass).toggle(isHidden);
|
||||
link.text(link.text().replace(isHidden ? 'show' : 'hide', isHidden ? 'hide' : 'show'));
|
||||
|
||||
popupPreventHorizontalScroll(popup);
|
||||
};
|
||||
|
||||
childrenTime.add(trivial).add(trivialGaps).click(function () {
|
||||
toggleIt(this);
|
||||
});
|
||||
|
||||
// if option is set or all our timings are trivial, go ahead and show them
|
||||
if (options.showTrivial || trivial.data('show-on-load')) {
|
||||
toggleIt(trivial);
|
||||
}
|
||||
// if option is set, go ahead and show time with children
|
||||
if (options.showChildrenTime) {
|
||||
toggleIt(childrenTime);
|
||||
}
|
||||
};
|
||||
|
||||
var buttonClick = function (button, popup) {
|
||||
// we're toggling this button/popup
|
||||
if (popup.is(':visible')) {
|
||||
popupHide(button, popup);
|
||||
}
|
||||
else {
|
||||
var visiblePopups = container.find('.profiler-popup:visible'),
|
||||
theirButtons = visiblePopups.siblings('.profiler-button');
|
||||
|
||||
// hide any other popups
|
||||
popupHide(theirButtons, visiblePopups);
|
||||
|
||||
// before showing the one we clicked
|
||||
popupShow(button, popup);
|
||||
}
|
||||
};
|
||||
|
||||
var popupShow = function (button, popup) {
|
||||
button.addClass('profiler-button-active');
|
||||
|
||||
popupSetDimensions(button, popup);
|
||||
|
||||
popup.show();
|
||||
|
||||
popupPreventHorizontalScroll(popup);
|
||||
};
|
||||
|
||||
var popupSetDimensions = function (button, popup) {
|
||||
var top = button.position().top - 1, // position next to the button we clicked
|
||||
windowHeight = $(window).height(),
|
||||
maxHeight = windowHeight - top - 40; // make sure the popup doesn't extend below the fold
|
||||
|
||||
popup
|
||||
.css({ 'top': top, 'max-height': maxHeight })
|
||||
.css(options.renderPosition, button.outerWidth() - 3); // move left or right, based on config
|
||||
};
|
||||
|
||||
var popupPreventHorizontalScroll = function (popup) {
|
||||
var childrenHeight = 0;
|
||||
|
||||
popup.children().each(function () { childrenHeight += $(this).height(); });
|
||||
|
||||
popup.css({ 'padding-right': childrenHeight > popup.height() ? 40 : 10 });
|
||||
};
|
||||
|
||||
var popupHide = function (button, popup) {
|
||||
button.removeClass('profiler-button-active');
|
||||
popup.hide();
|
||||
};
|
||||
|
||||
var queriesShow = function (link, result) {
|
||||
|
||||
var px = 30,
|
||||
win = $(window),
|
||||
width = win.width() - 2 * px,
|
||||
height = win.height() - 2 * px,
|
||||
queries = result.find('.profiler-queries');
|
||||
|
||||
// opaque background
|
||||
$('<div class="profiler-queries-bg"/>').appendTo('body').css({ 'height': $(document).height() }).show();
|
||||
|
||||
// center the queries and ensure long content is scrolled
|
||||
queries.css({ 'top': px, 'max-height': height, 'width': width }).css(options.renderPosition, px)
|
||||
.find('table').css({ 'width': width });
|
||||
|
||||
// have to show everything before we can get a position for the first query
|
||||
queries.show();
|
||||
|
||||
queriesScrollIntoView(link, queries, queries);
|
||||
|
||||
// syntax highlighting
|
||||
prettyPrint();
|
||||
};
|
||||
|
||||
var queriesScrollIntoView = function (link, queries, whatToScroll) {
|
||||
var id = link.closest('tr').attr('data-timing-id'),
|
||||
cells = queries.find('tr[data-timing-id="' + id + '"] td');
|
||||
|
||||
// ensure they're in view
|
||||
whatToScroll.scrollTop(whatToScroll.scrollTop() + cells.first().position().top - 100);
|
||||
|
||||
// highlight and then fade back to original bg color; do it ourselves to prevent any conflicts w/ jquery.UI or other implementations of Resig's color plugin
|
||||
cells.each(function () {
|
||||
var cell = $(this),
|
||||
highlightHex = '#FFFFBB',
|
||||
highlightRgb = getRGB(highlightHex),
|
||||
originalRgb = getRGB(cell.css('background-color')),
|
||||
getColorDiff = function (fx, i) {
|
||||
// adapted from John Resig's color plugin: http://plugins.jquery.com/project/color
|
||||
return Math.max(Math.min(parseInt((fx.pos * (originalRgb[i] - highlightRgb[i])) + highlightRgb[i], 10), 255), 0);
|
||||
};
|
||||
|
||||
// we need to animate some other property to piggy-back on the step function, so I choose you, opacity!
|
||||
cell.css({ 'opacity': 1, 'background-color': highlightHex })
|
||||
.animate({ 'opacity': 1 }, { duration: 2000, step: function (now, fx) {
|
||||
fx.elem.style.backgroundColor = "rgb(" + [getColorDiff(fx, 0), getColorDiff(fx, 1), getColorDiff(fx, 2)].join(",") + ")";
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Color Conversion functions from highlightFade
|
||||
// By Blair Mitchelmore
|
||||
// http://jquery.offput.ca/highlightFade/
|
||||
// Parse strings looking for color tuples [255,255,255]
|
||||
var getRGB = function (color) {
|
||||
var result;
|
||||
|
||||
// Check if we're already dealing with an array of colors
|
||||
if (color && color.constructor == Array && color.length == 3) return color;
|
||||
|
||||
// Look for rgb(num,num,num)
|
||||
if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) return [parseInt(result[1]), parseInt(result[2]), parseInt(result[3])];
|
||||
|
||||
// Look for rgb(num%,num%,num%)
|
||||
if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)) return [parseFloat(result[1]) * 2.55, parseFloat(result[2]) * 2.55, parseFloat(result[3]) * 2.55];
|
||||
|
||||
// Look for #a0b1c2
|
||||
if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)) return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)];
|
||||
|
||||
// Look for #fff
|
||||
if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)) return [parseInt(result[1] + result[1], 16), parseInt(result[2] + result[2], 16), parseInt(result[3] + result[3], 16)];
|
||||
|
||||
// Look for rgba(0, 0, 0, 0) == transparent in Safari 3
|
||||
if (result = /rgba\(0, 0, 0, 0\)/.exec(color)) return colors['transparent'];
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
var bindDocumentEvents = function () {
|
||||
$(document).bind('click keyup', function (e) {
|
||||
|
||||
// this happens on every keystroke, and :visible is crazy expensive in IE <9
|
||||
// and in this case, the display:none check is sufficient.
|
||||
var popup = $('.profiler-popup').filter(function () { return $(this).css("display") !== "none"; });
|
||||
|
||||
if (!popup.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var button = popup.siblings('.profiler-button'),
|
||||
queries = popup.closest('.profiler-result').find('.profiler-queries'),
|
||||
bg = $('.profiler-queries-bg'),
|
||||
isEscPress = e.type == 'keyup' && e.which == 27,
|
||||
hidePopup = false,
|
||||
hideQueries = false;
|
||||
|
||||
if (bg.is(':visible')) {
|
||||
hideQueries = isEscPress || (e.type == 'click' && !$.contains(queries[0], e.target) && !$.contains(popup[0], e.target));
|
||||
}
|
||||
else if (popup.is(':visible')) {
|
||||
hidePopup = isEscPress || (e.type == 'click' && !$.contains(popup[0], e.target) && !$.contains(button[0], e.target) && button[0] != e.target);
|
||||
}
|
||||
|
||||
if (hideQueries) {
|
||||
bg.remove();
|
||||
queries.hide();
|
||||
}
|
||||
|
||||
if (hidePopup) {
|
||||
popupHide(button, popup);
|
||||
}
|
||||
});
|
||||
$(document).bind('keydown', options.toggleShortcut, function(e) {
|
||||
$('.profiler-results').toggle();
|
||||
});
|
||||
};
|
||||
|
||||
var initFullView = function () {
|
||||
|
||||
// first, get jquery tmpl, then render and bind handlers
|
||||
fetchTemplates(function () {
|
||||
|
||||
// profiler will be defined in the full page's head
|
||||
renderTemplate(profiler).appendTo(container);
|
||||
|
||||
var popup = $('.profiler-popup');
|
||||
|
||||
toggleHidden(popup);
|
||||
|
||||
prettyPrint();
|
||||
|
||||
// since queries are already shown, just highlight and scroll when clicking a "1 sql" link
|
||||
popup.find('.profiler-queries-show').click(function () {
|
||||
queriesScrollIntoView($(this), $('.profiler-queries'), $(document));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var initControls = function (container) {
|
||||
if (options.showControls) {
|
||||
controls = $('<div class="profiler-controls"><span class="profiler-min-max">m</span><span class="profiler-clear">c</span></div>').appendTo(container);
|
||||
|
||||
$('.profiler-controls .profiler-min-max').click(function () {
|
||||
container.toggleClass('profiler-min');
|
||||
});
|
||||
|
||||
container.hover(function () {
|
||||
if ($(this).hasClass('profiler-min')) {
|
||||
$(this).find('.profiler-min-max').show();
|
||||
}
|
||||
},
|
||||
function () {
|
||||
if ($(this).hasClass('profiler-min')) {
|
||||
$(this).find('.profiler-min-max').hide();
|
||||
}
|
||||
});
|
||||
|
||||
$('.profiler-controls .profiler-clear').click(function () {
|
||||
container.find('.profiler-result').remove();
|
||||
});
|
||||
}
|
||||
else {
|
||||
container.addClass('profiler-no-controls');
|
||||
}
|
||||
};
|
||||
|
||||
var initPopupView = function () {
|
||||
|
||||
if (options.authorized) {
|
||||
// all fetched profilings will go in here
|
||||
container = $('<div class="profiler-results"/>').appendTo('body');
|
||||
|
||||
// MiniProfiler.RenderIncludes() sets which corner to render in - default is upper left
|
||||
container.addClass("profiler-" + options.renderPosition);
|
||||
|
||||
//initialize the controls
|
||||
initControls(container);
|
||||
|
||||
// we'll render results json via a jquery.tmpl - after we get the templates, we'll fetch the initial json to populate it
|
||||
fetchTemplates(function () {
|
||||
// get master page profiler results
|
||||
fetchResults(options.ids);
|
||||
});
|
||||
if (options.startHidden) container.hide();
|
||||
}
|
||||
else {
|
||||
fetchResults(options.ids);
|
||||
}
|
||||
|
||||
var jQueryAjaxComplete = function (e, xhr, settings) {
|
||||
if (xhr) {
|
||||
// should be an array of strings, e.g. ["008c4813-9bd7-443d-9376-9441ec4d6a8c","16ff377b-8b9c-4c20-a7b5-97cd9fa7eea7"]
|
||||
var stringIds = xhr.getResponseHeader('X-MiniProfiler-Ids');
|
||||
if (stringIds) {
|
||||
var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds);
|
||||
fetchResults(ids);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// fetch profile results for any ajax calls
|
||||
// note, this does not use $ cause we want to hook into the main jQuery
|
||||
if (jQuery && jQuery(document) && jQuery(document).ajaxComplete) {
|
||||
jQuery(document).ajaxComplete(jQueryAjaxComplete);
|
||||
}
|
||||
|
||||
if (jQuery && jQuery(document).ajaxStart)
|
||||
jQuery(document).ajaxStart(function () { ajaxStartTime = new Date(); });
|
||||
|
||||
// fetch results after ASP Ajax calls
|
||||
if (typeof (Sys) != 'undefined' && typeof (Sys.WebForms) != 'undefined' && typeof (Sys.WebForms.PageRequestManager) != 'undefined') {
|
||||
// Get the instance of PageRequestManager.
|
||||
var PageRequestManager = Sys.WebForms.PageRequestManager.getInstance();
|
||||
|
||||
PageRequestManager.add_endRequest(function (sender, args) {
|
||||
if (args) {
|
||||
var response = args.get_response();
|
||||
if (response.get_responseAvailable() && response._xmlHttpRequest != null) {
|
||||
var stringIds = args.get_response().getResponseHeader('X-MiniProfiler-Ids');
|
||||
if (stringIds) {
|
||||
var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds);
|
||||
fetchResults(ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// more Asp.Net callbacks
|
||||
if (typeof (WebForm_ExecuteCallback) == "function") {
|
||||
WebForm_ExecuteCallback = (function (callbackObject) {
|
||||
// Store original function
|
||||
var original = WebForm_ExecuteCallback;
|
||||
|
||||
return function (callbackObject) {
|
||||
original(callbackObject);
|
||||
|
||||
var stringIds = callbackObject.xmlRequest.getResponseHeader('X-MiniProfiler-Ids');
|
||||
if (stringIds) {
|
||||
var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds);
|
||||
fetchResults(ids);
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
}
|
||||
|
||||
// also fetch results after ExtJS requests, in case it is being used
|
||||
if (typeof (Ext) != 'undefined' && typeof (Ext.Ajax) != 'undefined' && typeof (Ext.Ajax.on) != 'undefined') {
|
||||
// Ext.Ajax is a singleton, so we just have to attach to its 'requestcomplete' event
|
||||
Ext.Ajax.on('requestcomplete', function (e, xhr, settings) {
|
||||
//iframed file uploads don't have headers
|
||||
if (!xhr || !xhr.getResponseHeader) {
|
||||
return;
|
||||
}
|
||||
|
||||
var stringIds = xhr.getResponseHeader('X-MiniProfiler-Ids');
|
||||
if (stringIds) {
|
||||
var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds);
|
||||
fetchResults(ids);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof (MooTools) != 'undefined' && typeof (Request) != 'undefined') {
|
||||
Request.prototype.addEvents({
|
||||
onComplete: function() {
|
||||
var stringIds = this.xhr.getResponseHeader('X-MiniProfiler-Ids');
|
||||
if (stringIds) {
|
||||
var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds);
|
||||
fetchResults(ids);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// add support for AngularJS, which use the basic XMLHttpRequest object.
|
||||
if (window.angular && typeof (XMLHttpRequest) != 'undefined') {
|
||||
var _send = XMLHttpRequest.prototype.send;
|
||||
|
||||
XMLHttpRequest.prototype.send = function sendReplacement(data) {
|
||||
this._onreadystatechange = this.onreadystatechange;
|
||||
|
||||
this.onreadystatechange = function onReadyStateChangeReplacement() {
|
||||
if (this.readyState == 4) {
|
||||
var stringIds = this.getResponseHeader('X-MiniProfiler-Ids');
|
||||
if (stringIds) {
|
||||
var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds);
|
||||
fetchResults(ids);
|
||||
}
|
||||
}
|
||||
|
||||
return this._onreadystatechange.apply(this, arguments);
|
||||
};
|
||||
|
||||
return _send.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
// some elements want to be hidden on certain doc events
|
||||
bindDocumentEvents();
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
init: function () {
|
||||
var script = document.getElementById('mini-profiler');
|
||||
if (!script || !script.getAttribute) return;
|
||||
|
||||
options = (function () {
|
||||
var version = script.getAttribute('data-version');
|
||||
var path = script.getAttribute('data-path');
|
||||
|
||||
var currentId = script.getAttribute('data-current-id');
|
||||
|
||||
var ids = script.getAttribute('data-ids');
|
||||
if (ids) ids = ids.split(',');
|
||||
|
||||
var position = script.getAttribute('data-position');
|
||||
|
||||
var toggleShortcut = script.getAttribute('data-toggle-shortcut');
|
||||
|
||||
if (script.getAttribute('data-max-traces')) {
|
||||
var maxTraces = parseInt(script.getAttribute('data-max-traces'), 10);
|
||||
}
|
||||
|
||||
if (script.getAttribute('data-trivial') === 'true') var trivial = true;
|
||||
if (script.getAttribute('data-children') == 'true') var children = true;
|
||||
if (script.getAttribute('data-controls') == 'true') var controls = true;
|
||||
if (script.getAttribute('data-authorized') == 'true') var authorized = true;
|
||||
if (script.getAttribute('data-start-hidden') == 'true') var startHidden = true;
|
||||
|
||||
return {
|
||||
ids: ids,
|
||||
path: path,
|
||||
version: version,
|
||||
renderPosition: position,
|
||||
showTrivial: trivial,
|
||||
showChildrenTime: children,
|
||||
maxTracesToShow: maxTraces,
|
||||
showControls: controls,
|
||||
currentId: currentId,
|
||||
authorized: authorized,
|
||||
toggleShortcut: toggleShortcut,
|
||||
startHidden: startHidden
|
||||
};
|
||||
})();
|
||||
|
||||
var doInit = function () {
|
||||
// when rendering a shared, full page, this div will exist
|
||||
container = $('.profiler-result-full');
|
||||
if (container.length) {
|
||||
if (window.location.href.indexOf("&trivial=1") > 0) {
|
||||
options.showTrivial = true;
|
||||
}
|
||||
initFullView();
|
||||
}
|
||||
else {
|
||||
initPopupView();
|
||||
}
|
||||
};
|
||||
|
||||
// this preserves debugging
|
||||
var load = function (s, f) {
|
||||
var sc = document.createElement("script");
|
||||
sc.async = "async";
|
||||
sc.type = "text/javascript";
|
||||
sc.src = s;
|
||||
var done = false;
|
||||
sc.onload = sc.onreadystatechange = function (_, abort) {
|
||||
if (!sc.readyState || /loaded|complete/.test(sc.readyState)) {
|
||||
if (!abort && !done) { done = true; f(); }
|
||||
}
|
||||
};
|
||||
document.getElementsByTagName('head')[0].appendChild(sc);
|
||||
};
|
||||
|
||||
var wait = 0;
|
||||
var finish = false;
|
||||
var deferInit = function() {
|
||||
if (finish) return;
|
||||
if (window.performance && window.performance.timing && window.performance.timing.loadEventEnd === 0 && wait < 10000) {
|
||||
setTimeout(deferInit, 100);
|
||||
wait += 100;
|
||||
} else {
|
||||
finish = true;
|
||||
init();
|
||||
}
|
||||
};
|
||||
|
||||
var init = function() {
|
||||
if (options.authorized) {
|
||||
var url = options.path + "includes.css?v=" + options.version;
|
||||
if (document.createStyleSheet) {
|
||||
document.createStyleSheet(url);
|
||||
} else {
|
||||
$('head').append($('<link rel="stylesheet" type="text/css" href="' + url + '" />'));
|
||||
}
|
||||
if (!$.tmpl) {
|
||||
load(options.path + 'jquery.tmpl.js?v=' + options.version, doInit);
|
||||
} else {
|
||||
doInit();
|
||||
}
|
||||
} else {
|
||||
doInit();
|
||||
}
|
||||
|
||||
// jquery.hotkeys.js
|
||||
// https://github.com/jeresig/jquery.hotkeys/blob/master/jquery.hotkeys.js
|
||||
|
||||
(function(d){function h(g){if("string"===typeof g.data){var h=g.handler,j=g.data.toLowerCase().split(" ");g.handler=function(b){if(!(this!==b.target&&(/textarea|select/i.test(b.target.nodeName)||"text"===b.target.type))){var c="keypress"!==b.type&&d.hotkeys.specialKeys[b.which],e=String.fromCharCode(b.which).toLowerCase(),a="",f={};b.altKey&&"alt"!==c&&(a+="alt+");b.ctrlKey&&"ctrl"!==c&&(a+="ctrl+");b.metaKey&&(!b.ctrlKey&&"meta"!==c)&&(a+="meta+");b.shiftKey&&"shift"!==c&&(a+="shift+");c?f[a+c]=
|
||||
!0:(f[a+e]=!0,f[a+d.hotkeys.shiftNums[e]]=!0,"shift+"===a&&(f[d.hotkeys.shiftNums[e]]=!0));c=0;for(e=j.length;c<e;c++)if(f[j[c]])return h.apply(this,arguments)}}}}d.hotkeys={version:"0.8",specialKeys:{8:"backspace",9:"tab",13:"return",16:"shift",17:"ctrl",18:"alt",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",
|
||||
109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scroll",191:"/",224:"meta"},shiftNums:{"`":"~",1:"!",2:"@",3:"#",4:"$",5:"%",6:"^",7:"&",8:"*",9:"(","0":")","-":"_","=":"+",";":": ","'":'"',",":"<",".":">","/":"?","\\":"|"}};d.each(["keydown","keyup","keypress"],function(){d.event.special[this]={add:h}})})(MiniProfiler.jQuery);
|
||||
|
||||
};
|
||||
|
||||
var major, minor;
|
||||
if (typeof(jQuery) == 'function') {
|
||||
var jQueryVersion = jQuery.fn.jquery.split('.');
|
||||
major = parseInt(jQueryVersion[0], 10);
|
||||
minor = parseInt(jQueryVersion[1], 10);
|
||||
}
|
||||
|
||||
|
||||
if (major === 2 || (major === 1 && minor >= 7)) {
|
||||
MiniProfiler.jQuery = $ = jQuery;
|
||||
$(deferInit);
|
||||
} else {
|
||||
load(options.path + "jquery.1.7.1.js?v=" + options.version, function() {
|
||||
MiniProfiler.jQuery = $ = jQuery.noConflict(true);
|
||||
$(deferInit);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getClientTimingByName: function (clientTiming, name) {
|
||||
|
||||
for (var i = 0; i < clientTiming.Timings.length; i++) {
|
||||
if (clientTiming.Timings[i].Name == name) {
|
||||
return clientTiming.Timings[i];
|
||||
}
|
||||
}
|
||||
return { Name: name, Duration: "", Start: "" };
|
||||
},
|
||||
|
||||
renderDate: function (jsonDate) { // JavaScriptSerializer sends dates as /Date(1308024322065)/
|
||||
if (jsonDate) {
|
||||
return (typeof jsonDate === 'string') ? new Date(parseInt(jsonDate.replace("/Date(", "").replace(")/", ""), 10)).toUTCString() : jsonDate;
|
||||
}
|
||||
},
|
||||
|
||||
renderIndent: function (depth) {
|
||||
var result = '';
|
||||
for (var i = 0; i < depth; i++) {
|
||||
result += ' ';
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
renderExecuteType: function (typeId) {
|
||||
// see StackExchange.Profiling.ExecuteType enum
|
||||
switch (typeId) {
|
||||
case 0: return 'None';
|
||||
case 1: return 'NonQuery';
|
||||
case 2: return 'Scalar';
|
||||
case 3: return 'Reader';
|
||||
}
|
||||
},
|
||||
|
||||
shareUrl: function (id) {
|
||||
return options.path + 'results?id=' + id;
|
||||
},
|
||||
|
||||
getClientTimings: function (clientTimings) {
|
||||
var list = [];
|
||||
var t;
|
||||
|
||||
if (!clientTimings.Timings) return [];
|
||||
|
||||
for (var i = 0; i < clientTimings.Timings.length; i++) {
|
||||
t = clientTimings.Timings[i];
|
||||
var trivial = t.Name != "Dom Complete" && t.Name != "Response" && t.Name != "First Paint Time";
|
||||
trivial = t.Duration < 2 ? trivial : false;
|
||||
list.push(
|
||||
{
|
||||
isTrivial: trivial,
|
||||
name: t.Name,
|
||||
duration: t.Duration,
|
||||
start: t.Start
|
||||
});
|
||||
}
|
||||
|
||||
list.sort(function (a, b) { return a.start - b.start; });
|
||||
return list;
|
||||
},
|
||||
|
||||
getSqlTimings: function (root) {
|
||||
var result = [],
|
||||
addToResults = function (timing) {
|
||||
if (timing.SqlTimings) {
|
||||
for (var i = 0, sqlTiming; i < timing.SqlTimings.length; i++) {
|
||||
sqlTiming = timing.SqlTimings[i];
|
||||
|
||||
// HACK: add info about the parent Timing to each SqlTiming so UI can render
|
||||
sqlTiming.ParentTimingName = timing.Name;
|
||||
result.push(sqlTiming);
|
||||
}
|
||||
}
|
||||
|
||||
if (timing.Children) {
|
||||
for (var i = 0; i < timing.Children.length; i++) {
|
||||
addToResults(timing.Children[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// start adding at the root and recurse down
|
||||
addToResults(root);
|
||||
|
||||
var removeDuration = function(list, duration) {
|
||||
|
||||
var newList = [];
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
|
||||
var item = list[i];
|
||||
if (duration.start > item.start) {
|
||||
if (duration.start > item.finish) {
|
||||
newList.push(item);
|
||||
continue;
|
||||
}
|
||||
newList.push({ start: item.start, finish: duration.start });
|
||||
}
|
||||
|
||||
if (duration.finish < item.finish) {
|
||||
if (duration.finish < item.start) {
|
||||
newList.push(item);
|
||||
continue;
|
||||
}
|
||||
newList.push({ start: duration.finish, finish: item.finish });
|
||||
}
|
||||
}
|
||||
|
||||
return newList;
|
||||
};
|
||||
|
||||
var processTimes = function (elem, parent) {
|
||||
var duration = { start: elem.StartMilliseconds, finish: (elem.StartMilliseconds + elem.DurationMilliseconds) };
|
||||
elem.richTiming = [duration];
|
||||
if (parent != null) {
|
||||
elem.parent = parent;
|
||||
elem.parent.richTiming = removeDuration(elem.parent.richTiming, duration);
|
||||
}
|
||||
|
||||
if (elem.Children) {
|
||||
for (var i = 0; i < elem.Children.length; i++) {
|
||||
processTimes(elem.Children[i], elem);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
processTimes(root, null);
|
||||
|
||||
// sort results by time
|
||||
result.sort(function (a, b) { return a.StartMilliseconds - b.StartMilliseconds; });
|
||||
|
||||
var determineOverlap = function(gap, node) {
|
||||
var overlap = 0;
|
||||
for (var i = 0; i < node.richTiming.length; i++) {
|
||||
var current = node.richTiming[i];
|
||||
if (current.start > gap.finish) {
|
||||
break;
|
||||
}
|
||||
if (current.finish < gap.start) {
|
||||
continue;
|
||||
}
|
||||
|
||||
overlap += Math.min(gap.finish, current.finish) - Math.max(gap.start, current.start);
|
||||
}
|
||||
return overlap;
|
||||
};
|
||||
|
||||
var determineGap = function (gap, node, match) {
|
||||
var overlap = determineOverlap(gap, node);
|
||||
if (match == null || overlap > match.duration) {
|
||||
match = { name: node.Name, duration: overlap };
|
||||
}
|
||||
else if (match.name == node.Name) {
|
||||
match.duration += overlap;
|
||||
}
|
||||
|
||||
if (node.Children) {
|
||||
for (var i = 0; i < node.Children.length; i++) {
|
||||
match = determineGap(gap, node.Children[i], match);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
};
|
||||
|
||||
var time = 0;
|
||||
var prev = null;
|
||||
$.each(result, function () {
|
||||
this.prevGap = {
|
||||
duration: (this.StartMilliseconds - time).toFixed(2),
|
||||
start: time,
|
||||
finish: this.StartMilliseconds
|
||||
};
|
||||
|
||||
this.prevGap.topReason = determineGap(this.prevGap, root, null);
|
||||
|
||||
time = this.StartMilliseconds + this.DurationMilliseconds;
|
||||
prev = this;
|
||||
});
|
||||
|
||||
|
||||
if (result.length > 0) {
|
||||
var me = result[result.length - 1];
|
||||
me.nextGap = {
|
||||
duration: (root.DurationMilliseconds - time).toFixed(2),
|
||||
start: time,
|
||||
finish: root.DurationMilliseconds
|
||||
};
|
||||
me.nextGap.topReason = determineGap(me.nextGap, root, null);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getSqlTimingsCount: function (root) {
|
||||
var result = 0,
|
||||
countSql = function (timing) {
|
||||
if (timing.SqlTimings) {
|
||||
result += timing.SqlTimings.length;
|
||||
}
|
||||
|
||||
if (timing.Children) {
|
||||
for (var i = 0; i < timing.Children.length; i++) {
|
||||
countSql(timing.Children[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
countSql(root);
|
||||
return result;
|
||||
},
|
||||
|
||||
fetchResultsExposed: function (ids) {
|
||||
return fetchResults(ids);
|
||||
},
|
||||
|
||||
formatDuration: function (duration) {
|
||||
return (duration || 0).toFixed(1);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
MiniProfiler.init();
|
||||
|
||||
if (typeof prettyPrint === "undefined") {
|
||||
|
||||
// prettify.js
|
||||
// http://code.google.com/p/google-code-prettify/
|
||||
|
||||
window.PR_SHOULD_USE_CONTINUATION=true;window.PR_TAB_WIDTH=8;window.PR_normalizedHtml=window.PR=window.prettyPrintOne=window.prettyPrint=void 0;window._pr_isIE6=function(){var y=navigator&&navigator.userAgent&&navigator.userAgent.match(/\bMSIE ([678])\./);y=y?+y[1]:false;window._pr_isIE6=function(){return y};return y};
|
||||
(function(){function y(b){return b.replace(L,"&").replace(M,"<").replace(N,">")}function H(b,f,i){switch(b.nodeType){case 1:var o=b.tagName.toLowerCase();f.push("<",o);var l=b.attributes,n=l.length;if(n){if(i){for(var r=[],j=n;--j>=0;)r[j]=l[j];r.sort(function(q,m){return q.name<m.name?-1:q.name===m.name?0:1});l=r}for(j=0;j<n;++j){r=l[j];r.specified&&f.push(" ",r.name.toLowerCase(),'="',r.value.replace(L,"&").replace(M,"<").replace(N,">").replace(X,"""),'"')}}f.push(">");
|
||||
for(l=b.firstChild;l;l=l.nextSibling)H(l,f,i);if(b.firstChild||!/^(?:br|link|img)$/.test(o))f.push("</",o,">");break;case 3:case 4:f.push(y(b.nodeValue));break}}function O(b){function f(c){if(c.charAt(0)!=="\\")return c.charCodeAt(0);switch(c.charAt(1)){case "b":return 8;case "t":return 9;case "n":return 10;case "v":return 11;case "f":return 12;case "r":return 13;case "u":case "x":return parseInt(c.substring(2),16)||c.charCodeAt(1);case "0":case "1":case "2":case "3":case "4":case "5":case "6":case "7":return parseInt(c.substring(1),
|
||||
8);default:return c.charCodeAt(1)}}function i(c){if(c<32)return(c<16?"\\x0":"\\x")+c.toString(16);c=String.fromCharCode(c);if(c==="\\"||c==="-"||c==="["||c==="]")c="\\"+c;return c}function o(c){var d=c.substring(1,c.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));c=[];for(var a=[],k=d[0]==="^",e=k?1:0,h=d.length;e<h;++e){var g=d[e];switch(g){case "\\B":case "\\b":case "\\D":case "\\d":case "\\S":case "\\s":case "\\W":case "\\w":c.push(g);
|
||||
continue}g=f(g);var s;if(e+2<h&&"-"===d[e+1]){s=f(d[e+2]);e+=2}else s=g;a.push([g,s]);if(!(s<65||g>122)){s<65||g>90||a.push([Math.max(65,g)|32,Math.min(s,90)|32]);s<97||g>122||a.push([Math.max(97,g)&-33,Math.min(s,122)&-33])}}a.sort(function(v,w){return v[0]-w[0]||w[1]-v[1]});d=[];g=[NaN,NaN];for(e=0;e<a.length;++e){h=a[e];if(h[0]<=g[1]+1)g[1]=Math.max(g[1],h[1]);else d.push(g=h)}a=["["];k&&a.push("^");a.push.apply(a,c);for(e=0;e<d.length;++e){h=d[e];a.push(i(h[0]));if(h[1]>h[0]){h[1]+1>h[0]&&a.push("-");
|
||||
a.push(i(h[1]))}}a.push("]");return a.join("")}function l(c){for(var d=c.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g")),a=d.length,k=[],e=0,h=0;e<a;++e){var g=d[e];if(g==="(")++h;else if("\\"===g.charAt(0))if((g=+g.substring(1))&&g<=h)k[g]=-1}for(e=1;e<k.length;++e)if(-1===k[e])k[e]=++n;for(h=e=0;e<a;++e){g=d[e];if(g==="("){++h;if(k[h]===undefined)d[e]="(?:"}else if("\\"===
|
||||
g.charAt(0))if((g=+g.substring(1))&&g<=h)d[e]="\\"+k[h]}for(h=e=0;e<a;++e)if("^"===d[e]&&"^"!==d[e+1])d[e]="";if(c.ignoreCase&&r)for(e=0;e<a;++e){g=d[e];c=g.charAt(0);if(g.length>=2&&c==="[")d[e]=o(g);else if(c!=="\\")d[e]=g.replace(/[a-zA-Z]/g,function(s){s=s.charCodeAt(0);return"["+String.fromCharCode(s&-33,s|32)+"]"})}return d.join("")}for(var n=0,r=false,j=false,q=0,m=b.length;q<m;++q){var t=b[q];if(t.ignoreCase)j=true;else if(/[a-z]/i.test(t.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,
|
||||
""))){r=true;j=false;break}}var p=[];q=0;for(m=b.length;q<m;++q){t=b[q];if(t.global||t.multiline)throw Error(""+t);p.push("(?:"+l(t)+")")}return RegExp(p.join("|"),j?"gi":"g")}function Y(b){var f=0;return function(i){for(var o=null,l=0,n=0,r=i.length;n<r;++n)switch(i.charAt(n)){case "\t":o||(o=[]);o.push(i.substring(l,n));l=b-f%b;for(f+=l;l>=0;l-=16)o.push(" ".substring(0,l));l=n+1;break;case "\n":f=0;break;default:++f}if(!o)return i;o.push(i.substring(l));return o.join("")}}function I(b,
|
||||
f,i,o){if(f){b={source:f,c:b};i(b);o.push.apply(o,b.d)}}function B(b,f){var i={},o;(function(){for(var r=b.concat(f),j=[],q={},m=0,t=r.length;m<t;++m){var p=r[m],c=p[3];if(c)for(var d=c.length;--d>=0;)i[c.charAt(d)]=p;p=p[1];c=""+p;if(!q.hasOwnProperty(c)){j.push(p);q[c]=null}}j.push(/[\0-\uffff]/);o=O(j)})();var l=f.length;function n(r){for(var j=r.c,q=[j,z],m=0,t=r.source.match(o)||[],p={},c=0,d=t.length;c<d;++c){var a=t[c],k=p[a],e=void 0,h;if(typeof k==="string")h=false;else{var g=i[a.charAt(0)];
|
||||
if(g){e=a.match(g[1]);k=g[0]}else{for(h=0;h<l;++h){g=f[h];if(e=a.match(g[1])){k=g[0];break}}e||(k=z)}if((h=k.length>=5&&"lang-"===k.substring(0,5))&&!(e&&typeof e[1]==="string")){h=false;k=P}h||(p[a]=k)}g=m;m+=a.length;if(h){h=e[1];var s=a.indexOf(h),v=s+h.length;if(e[2]){v=a.length-e[2].length;s=v-h.length}k=k.substring(5);I(j+g,a.substring(0,s),n,q);I(j+g+s,h,Q(k,h),q);I(j+g+v,a.substring(v),n,q)}else q.push(j+g,k)}r.d=q}return n}function x(b){var f=[],i=[];if(b.tripleQuotedStrings)f.push([A,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
|
||||
null,"'\""]);else b.multiLineStrings?f.push([A,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"]):f.push([A,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"]);b.verbatimStrings&&i.push([A,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null]);if(b.hashComments)if(b.cStyleComments){f.push([C,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"]);i.push([A,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,
|
||||
null])}else f.push([C,/^#[^\r\n]*/,null,"#"]);if(b.cStyleComments){i.push([C,/^\/\/[^\r\n]*/,null]);i.push([C,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}b.regexLiterals&&i.push(["lang-regex",RegExp("^"+Z+"(/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/)")]);b=b.keywords.replace(/^\s+|\s+$/g,"");b.length&&i.push([R,RegExp("^(?:"+b.replace(/\s+/g,"|")+")\\b"),null]);f.push([z,/^\s+/,null," \r\n\t\u00a0"]);i.push([J,/^@[a-z_$][a-z_$@0-9]*/i,null],[S,/^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/,
|
||||
null],[z,/^[a-z_$][a-z_$@0-9]*/i,null],[J,/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],[E,/^.[^\s\w\.$@\'\"\`\/\#]*/,null]);return B(f,i)}function $(b){function f(D){if(D>r){if(j&&j!==q){n.push("</span>");j=null}if(!j&&q){j=q;n.push('<span class="',j,'">')}var T=y(p(i.substring(r,D))).replace(e?d:c,"$1 ");e=k.test(T);n.push(T.replace(a,s));r=D}}var i=b.source,o=b.g,l=b.d,n=[],r=0,j=null,q=null,m=0,t=0,p=Y(window.PR_TAB_WIDTH),c=/([\r\n ]) /g,
|
||||
d=/(^| ) /gm,a=/\r\n?|\n/g,k=/[ \r\n]$/,e=true,h=window._pr_isIE6();h=h?b.b.tagName==="PRE"?h===6?" \r\n":h===7?" <br>\r":" \r":" <br />":"<br />";var g=b.b.className.match(/\blinenums\b(?::(\d+))?/),s;if(g){for(var v=[],w=0;w<10;++w)v[w]=h+'</li><li class="L'+w+'">';var F=g[1]&&g[1].length?g[1]-1:0;n.push('<ol class="linenums"><li class="L',F%10,'"');F&&n.push(' value="',F+1,'"');n.push(">");s=function(){var D=v[++F%10];return j?"</span>"+D+'<span class="'+j+'">':D}}else s=h;
|
||||
for(;;)if(m<o.length?t<l.length?o[m]<=l[t]:true:false){f(o[m]);if(j){n.push("</span>");j=null}n.push(o[m+1]);m+=2}else if(t<l.length){f(l[t]);q=l[t+1];t+=2}else break;f(i.length);j&&n.push("</span>");g&&n.push("</li></ol>");b.a=n.join("")}function u(b,f){for(var i=f.length;--i>=0;){var o=f[i];if(G.hasOwnProperty(o))"console"in window&&console.warn("cannot override language handler %s",o);else G[o]=b}}function Q(b,f){b&&G.hasOwnProperty(b)||(b=/^\s*</.test(f)?"default-markup":"default-code");return G[b]}
|
||||
function U(b){var f=b.f,i=b.e;b.a=f;try{var o,l=f.match(aa);f=[];var n=0,r=[];if(l)for(var j=0,q=l.length;j<q;++j){var m=l[j];if(m.length>1&&m.charAt(0)==="<"){if(!ba.test(m))if(ca.test(m)){f.push(m.substring(9,m.length-3));n+=m.length-12}else if(da.test(m)){f.push("\n");++n}else if(m.indexOf(V)>=0&&m.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,' $1="$2$3$4"').match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/)){var t=m.match(W)[2],p=1,c;c=j+1;a:for(;c<q;++c){var d=l[c].match(W);if(d&&
|
||||
d[2]===t)if(d[1]==="/"){if(--p===0)break a}else++p}if(c<q){r.push(n,l.slice(j,c+1).join(""));j=c}else r.push(n,m)}else r.push(n,m)}else{var a;p=m;var k=p.indexOf("&");if(k<0)a=p;else{for(--k;(k=p.indexOf("&#",k+1))>=0;){var e=p.indexOf(";",k);if(e>=0){var h=p.substring(k+3,e),g=10;if(h&&h.charAt(0)==="x"){h=h.substring(1);g=16}var s=parseInt(h,g);isNaN(s)||(p=p.substring(0,k)+String.fromCharCode(s)+p.substring(e+1))}}a=p.replace(ea,"<").replace(fa,">").replace(ga,"'").replace(ha,'"').replace(ia," ").replace(ja,
|
||||
"&")}f.push(a);n+=a.length}}o={source:f.join(""),h:r};var v=o.source;b.source=v;b.c=0;b.g=o.h;Q(i,v)(b);$(b)}catch(w){if("console"in window)console.log(w&&w.stack?w.stack:w)}}var A="str",R="kwd",C="com",S="typ",J="lit",E="pun",z="pln",P="src",V="nocode",Z=function(){for(var b=["!","!=","!==","#","%","%=","&","&&","&&=","&=","(","*","*=","+=",",","-=","->","/","/=",":","::",";","<","<<","<<=","<=","=","==","===",">",">=",">>",">>=",">>>",">>>=","?","@","[","^","^=","^^","^^=","{","|","|=","||","||=",
|
||||
"~","break","case","continue","delete","do","else","finally","instanceof","return","throw","try","typeof"],f="(?:^^|[+-]",i=0;i<b.length;++i)f+="|"+b[i].replace(/([^=<>:&a-z])/g,"\\$1");f+=")\\s*";return f}(),L=/&/g,M=/</g,N=/>/g,X=/\"/g,ea=/</g,fa=/>/g,ga=/'/g,ha=/"/g,ja=/&/g,ia=/ /g,ka=/[\r\n]/g,K=null,aa=RegExp("[^<]+|<!--[\\s\\S]*?--\>|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>|</?[a-zA-Z](?:[^>\"']|'[^']*'|\"[^\"]*\")*>|<","g"),ba=/^<\!--/,ca=/^<!\[CDATA\[/,da=/^<br\b/i,W=/^<(\/?)([a-zA-Z][a-zA-Z0-9]*)/,
|
||||
la=x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename using virtual wchar_t where break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof abstract boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof debugger eval export function get null set undefined var with Infinity NaN caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END break continue do else for if return while case done elif esac eval fi function in local set then until ",
|
||||
hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true}),G={};u(la,["default-code"]);u(B([],[[z,/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],[C,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[E,/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup",
|
||||
"htm","html","mxml","xhtml","xml","xsl"]);u(B([[z,/^[\s]+/,null," \t\r\n"],["atv",/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[E,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],
|
||||
["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);u(B([],[["atv",/^[\s\S]+/]]),["uq.val"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename using virtual wchar_t where ",
|
||||
hashComments:true,cStyleComments:true}),["c","cc","cpp","cxx","cyc","m"]);u(x({keywords:"null true false"}),["json"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof abstract boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var ",
|
||||
hashComments:true,cStyleComments:true,verbatimStrings:true}),["cs"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof abstract boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient ",
|
||||
cStyleComments:true}),["java"]);u(x({keywords:"break continue do else for if return while case done elif esac eval fi function in local set then until ",hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);u(x({keywords:"break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None ",hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);
|
||||
u(x({keywords:"caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END ",hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);u(x({keywords:"break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END ",hashComments:true,
|
||||
multiLineStrings:true,regexLiterals:true}),["rb"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof debugger eval export function get null set undefined var with Infinity NaN ",cStyleComments:true,regexLiterals:true}),["js"]);u(B([],[[A,/^[\s\S]+/]]),
|
||||
["regex"]);window.PR_normalizedHtml=H;window.prettyPrintOne=function(b,f){var i={f:b,e:f};U(i);return i.a};window.prettyPrint=function(b){function f(){for(var t=window.PR_SHOULD_USE_CONTINUATION?j.now()+250:Infinity;q<o.length&&j.now()<t;q++){var p=o[q];if(p.className&&p.className.indexOf("prettyprint")>=0){var c=p.className.match(/\blang-(\w+)\b/);if(c)c=c[1];for(var d=false,a=p.parentNode;a;a=a.parentNode)if((a.tagName==="pre"||a.tagName==="code"||a.tagName==="xmp")&&a.className&&a.className.indexOf("prettyprint")>=
|
||||
0){d=true;break}if(!d){a=p;if(null===K){d=document.createElement("PRE");d.appendChild(document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />'));K=!/</.test(d.innerHTML)}if(K){d=a.innerHTML;if("XMP"===a.tagName)d=y(d);else{a=a;if("PRE"===a.tagName)a=true;else if(ka.test(d)){var k="";if(a.currentStyle)k=a.currentStyle.whiteSpace;else if(window.getComputedStyle)k=window.getComputedStyle(a,null).whiteSpace;a=!k||k==="pre"}else a=true;a||(d=d.replace(/(<br\s*\/?>)[\r\n]+/g,"$1").replace(/(?:[\r\n]+[ \t]*)+/g,
|
||||
" "))}d=d}else{d=[];for(a=a.firstChild;a;a=a.nextSibling)H(a,d);d=d.join("")}d=d.replace(/(?:\r\n?|\n)$/,"");m={f:d,e:c,b:p};U(m);if(p=m.a){c=m.b;if("XMP"===c.tagName){d=document.createElement("PRE");for(a=0;a<c.attributes.length;++a){k=c.attributes[a];if(k.specified)if(k.name.toLowerCase()==="class")d.className=k.value;else d.setAttribute(k.name,k.value)}d.innerHTML=p;c.parentNode.replaceChild(d,c)}else c.innerHTML=p}}}}if(q<o.length)setTimeout(f,250);else b&&b()}for(var i=[document.getElementsByTagName("pre"),
|
||||
document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],o=[],l=0;l<i.length;++l)for(var n=0,r=i[l].length;n<r;++n)o.push(i[l][n]);i=null;var j=Date;j.now||(j={now:function(){return(new Date).getTime()}});var q=0,m;f()};window.PR={combinePrefixPatterns:O,createSimpleLexer:B,registerLangHandler:u,sourceDecorator:x,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:C,PR_DECLARATION:"dec",PR_KEYWORD:R,PR_LITERAL:J,PR_NOCODE:V,PR_PLAIN:z,PR_PUNCTUATION:E,PR_SOURCE:P,PR_STRING:A,
|
||||
PR_TAG:"tag",PR_TYPE:S}})()
|
||||
|
||||
;
|
||||
|
||||
// lang-sql.js
|
||||
// http://code.google.com/p/google-code-prettify/
|
||||
|
||||
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^(?:"(?:[^\"\\]|\\.)*"|'(?:[^\'\\]|\\.)*')/,null,"\"'"]],[["com",/^(?:--[^\r\n]*|\/\*[\s\S]*?(?:\*\/|$))/],["kwd",/^(?:ADD|ALL|ALTER|AND|ANY|AS|ASC|AUTHORIZATION|BACKUP|BEGIN|BETWEEN|BREAK|BROWSE|BULK|BY|CASCADE|CASE|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMN|COMMIT|COMPUTE|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATABASE|DBCC|DEALLOCATE|DECLARE|DEFAULT|DELETE|DENY|DESC|DISK|DISTINCT|DISTRIBUTED|DOUBLE|DROP|DUMMY|DUMP|ELSE|END|ERRLVL|ESCAPE|EXCEPT|EXEC|EXECUTE|EXISTS|EXIT|FETCH|FILE|FILLFACTOR|FOR|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GOTO|GRANT|GROUP|HAVING|HOLDLOCK|IDENTITY|IDENTITYCOL|IDENTITY_INSERT|IF|IN|INDEX|INNER|INSERT|INTERSECT|INTO|IS|JOIN|KEY|KILL|LEFT|LIKE|LINENO|LOAD|NATIONAL|NOCHECK|NONCLUSTERED|NOT|NULL|NULLIF|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPENXML|OPTION|OR|ORDER|OUTER|OVER|PERCENT|PLAN|PRECISION|PRIMARY|PRINT|PROC|PROCEDURE|PUBLIC|RAISERROR|READ|READTEXT|RECONFIGURE|REFERENCES|REPLICATION|RESTORE|RESTRICT|RETURN|REVOKE|RIGHT|ROLLBACK|ROWCOUNT|ROWGUIDCOL|RULE|SAVE|SCHEMA|SELECT|SESSION_USER|SET|SETUSER|SHUTDOWN|SOME|STATISTICS|SYSTEM_USER|TABLE|TEXTSIZE|THEN|TO|TOP|TRAN|TRANSACTION|TRIGGER|TRUNCATE|TSEQUAL|UNION|UNIQUE|UPDATE|UPDATETEXT|USE|USER|VALUES|VARYING|VIEW|WAITFOR|WHEN|WHERE|WHILE|WITH|WRITETEXT)(?=[^\w-]|$)/i,
|
||||
null],["lit",/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],["pln",/^[a-z_][\w-]*/i],["pun",/^[^\w\t\n\r \xA0\"\'][^\w\t\n\r \xA0+\-\"\']*/]]),["sql"])
|
||||
|
||||
;
|
||||
}
|
|
@ -0,0 +1,471 @@
|
|||
.box-shadow(@dx, @dy, @radius, @color) {
|
||||
-moz-box-shadow: @dx @dy @radius @color;
|
||||
-webkit-box-shadow: @dx @dy @radius @color;
|
||||
box-shadow: @dx @dy @radius @color;
|
||||
}
|
||||
|
||||
@anchorColor: #0077CC;
|
||||
@buttonBorderColor: #888;
|
||||
@numberColor: #111;
|
||||
@textColor: #555;
|
||||
@mutedColor: #aaa;
|
||||
@normalFonts: Helvetica, Arial, sans-serif;
|
||||
@codeFonts: Consolas, monospace, serif;
|
||||
@zindex:2147483640; // near 32bit max 2147483647
|
||||
|
||||
// do some resets
|
||||
.profiler-result, .profiler-queries {
|
||||
color:#555;
|
||||
line-height:1;
|
||||
font-size:12px;
|
||||
|
||||
pre, code, label, table, tbody, thead, tfoot, tr, th, td {
|
||||
margin:0;
|
||||
padding:0;
|
||||
border:0;
|
||||
font-size:100%;
|
||||
font:inherit;
|
||||
vertical-align:baseline;
|
||||
background-color:transparent;
|
||||
overflow:visible;
|
||||
max-height:none;
|
||||
}
|
||||
table {
|
||||
border-collapse:collapse;
|
||||
border-spacing:0;
|
||||
}
|
||||
a, a:hover {
|
||||
cursor:pointer;
|
||||
color:@anchorColor;
|
||||
}
|
||||
a {
|
||||
text-decoration:none;
|
||||
&:hover {
|
||||
text-decoration:underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// styles shared between popup view and full view
|
||||
.profiler-result
|
||||
{
|
||||
|
||||
.profiler-toggle-duration-with-children
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
|
||||
table.profiler-client-timings
|
||||
{
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
font-family:@normalFonts;
|
||||
|
||||
.profiler-label {
|
||||
color:@textColor;
|
||||
overflow:hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.profiler-unit {
|
||||
color:@mutedColor;
|
||||
}
|
||||
|
||||
.profiler-trivial {
|
||||
display:none;
|
||||
td, td * {
|
||||
color:@mutedColor !important;
|
||||
}
|
||||
}
|
||||
|
||||
pre, code, .profiler-number, .profiler-unit {
|
||||
font-family:@codeFonts;
|
||||
}
|
||||
|
||||
.profiler-number {
|
||||
color:@numberColor;
|
||||
}
|
||||
|
||||
.profiler-info {
|
||||
text-align:right;
|
||||
.profiler-name {
|
||||
float:left;
|
||||
}
|
||||
.profiler-server-time {
|
||||
white-space:nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-timings {
|
||||
th {
|
||||
background-color:#fff;
|
||||
color:@mutedColor;
|
||||
text-align:right;
|
||||
}
|
||||
th, td {
|
||||
white-space:nowrap;
|
||||
}
|
||||
.profiler-duration-with-children {
|
||||
display:none;
|
||||
}
|
||||
.profiler-duration {
|
||||
.profiler-number;
|
||||
text-align:right;
|
||||
}
|
||||
.profiler-indent {
|
||||
letter-spacing:4px;
|
||||
}
|
||||
.profiler-queries-show {
|
||||
.profiler-number, .profiler-unit {
|
||||
color:@anchorColor;
|
||||
}
|
||||
}
|
||||
.profiler-queries-duration {
|
||||
padding-left:6px;
|
||||
}
|
||||
.profiler-percent-in-sql {
|
||||
white-space:nowrap;
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
tfoot {
|
||||
td {
|
||||
padding-top:10px;
|
||||
text-align:right;
|
||||
|
||||
a {
|
||||
font-size:95%;
|
||||
display:inline-block;
|
||||
margin-left:12px;
|
||||
|
||||
&:first-child {
|
||||
float:left;
|
||||
margin-left:0px;
|
||||
}
|
||||
&.profiler-custom-link {
|
||||
float:left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-queries {
|
||||
font-family:@normalFonts;
|
||||
|
||||
.profiler-stack-trace {
|
||||
margin-bottom:15px;
|
||||
}
|
||||
pre {
|
||||
font-family:@codeFonts;
|
||||
white-space:pre-wrap;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color:#fff;
|
||||
border-bottom:1px solid #555;
|
||||
font-weight:bold;
|
||||
padding:15px;
|
||||
white-space:nowrap;
|
||||
}
|
||||
|
||||
td {
|
||||
padding:15px;
|
||||
text-align:left;
|
||||
background-color:#fff;
|
||||
|
||||
&:last-child {
|
||||
padding-right:25px; // compensate for scrollbars
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-odd td {
|
||||
background-color:#e5e5e5;
|
||||
}
|
||||
|
||||
.profiler-since-start, .profiler-duration {
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
.profiler-info div {
|
||||
text-align:right;
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
.profiler-gap-info, .profiler-gap-info td { background-color: #ccc;}
|
||||
.profiler-gap-info {
|
||||
.profiler-unit {color: #777;}
|
||||
.profiler-info {text-align: right}
|
||||
&.profiler-trivial-gaps {display: none}
|
||||
}
|
||||
|
||||
.profiler-trivial-gap-container { text-align: center;}
|
||||
|
||||
// prettify colors
|
||||
.str{color:maroon}
|
||||
.kwd{color:#00008b}
|
||||
.com{color:gray}
|
||||
.typ{color:#2b91af}
|
||||
.lit{color:maroon}
|
||||
.pun{color:#000}
|
||||
.pln{color:#000}
|
||||
.tag{color:maroon}
|
||||
.atn{color:red}
|
||||
.atv{color:blue}
|
||||
.dec{color:purple}
|
||||
}
|
||||
|
||||
.profiler-warning, .profiler-warning *, .profiler-warning .profiler-queries-show, .profiler-warning .profiler-queries-show .profiler-unit { // i'm no good at css
|
||||
color:#f00;
|
||||
&:hover {
|
||||
color:#f00;
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-nuclear {
|
||||
.profiler-warning;
|
||||
font-weight:bold;
|
||||
padding-right:2px;
|
||||
}
|
||||
}
|
||||
|
||||
// ajaxed-in results will be appended to this
|
||||
.profiler-results
|
||||
{
|
||||
z-index:@zindex + 3;
|
||||
position:fixed;
|
||||
top:0px;
|
||||
|
||||
@radius:10px;
|
||||
|
||||
&.profiler-left {
|
||||
left:0px;
|
||||
|
||||
&.profiler-no-controls .profiler-result:last-child .profiler-button, .profiler-controls {
|
||||
-webkit-border-bottom-right-radius: @radius;
|
||||
-moz-border-radius-bottomright: @radius;
|
||||
border-bottom-right-radius: @radius;
|
||||
}
|
||||
|
||||
.profiler-button, .profiler-controls {
|
||||
border-right: 1px solid @buttonBorderColor;
|
||||
}
|
||||
}
|
||||
|
||||
&.profiler-right {
|
||||
right:0px;
|
||||
|
||||
&.profiler-no-controls .profiler-result:last-child .profiler-button, .profiler-controls {
|
||||
-webkit-border-bottom-left-radius: @radius;
|
||||
-moz-border-radius-bottomleft: @radius;
|
||||
border-bottom-left-radius: @radius;
|
||||
}
|
||||
|
||||
.profiler-button, .profiler-controls {
|
||||
border-left: 1px solid @buttonBorderColor;
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-button, .profiler-controls {
|
||||
display:none;
|
||||
z-index:@zindex;
|
||||
border-bottom: 1px solid @buttonBorderColor;
|
||||
background-color:#fff;
|
||||
padding: 4px 7px;
|
||||
text-align:right;
|
||||
cursor:pointer;
|
||||
|
||||
&.profiler-button-active {
|
||||
background-color:maroon;
|
||||
|
||||
.profiler-number, .profiler-nuclear {
|
||||
color:#fff;
|
||||
font-weight:bold;
|
||||
}
|
||||
.profiler-unit {
|
||||
color:#fff;
|
||||
font-weight:normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-controls {
|
||||
display: block;
|
||||
font-size:12px;
|
||||
font-family: @codeFonts;
|
||||
cursor:default;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
border-right: 1px solid @mutedColor;
|
||||
padding-right: 5px;
|
||||
margin-right: 5px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
span:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-popup {
|
||||
display:none;
|
||||
z-index:@zindex + 1;
|
||||
position:absolute;
|
||||
background-color:#fff;
|
||||
border: 1px solid #aaa;
|
||||
padding:5px 10px;
|
||||
text-align:left;
|
||||
line-height:18px;
|
||||
overflow:auto;
|
||||
|
||||
.box-shadow(0px, 1px, 15px, #555);
|
||||
|
||||
.profiler-info {
|
||||
margin-bottom:3px;
|
||||
padding-bottom:2px;
|
||||
border-bottom:1px solid #ddd;
|
||||
|
||||
.profiler-name {
|
||||
font-size:110%;
|
||||
font-weight:bold;
|
||||
.profiler-overall-duration {
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
.profiler-server-time {
|
||||
font-size:95%;
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-timings {
|
||||
|
||||
th, td {
|
||||
padding-left:6px;
|
||||
padding-right:6px;
|
||||
}
|
||||
|
||||
th {
|
||||
font-size:95%;
|
||||
padding-bottom:3px;
|
||||
}
|
||||
|
||||
.profiler-label {
|
||||
max-width:275px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-queries {
|
||||
display:none;
|
||||
z-index:@zindex + 3;
|
||||
position:absolute;
|
||||
overflow-y:auto;
|
||||
overflow-x:auto;
|
||||
background-color:#fff;
|
||||
|
||||
th {
|
||||
font-size:17px;
|
||||
}
|
||||
}
|
||||
|
||||
&.profiler-min .profiler-result {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.profiler-min .profiler-controls span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.profiler-min .profiler-controls .profiler-min-max {
|
||||
border-right: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
// popup results' queries will be displayed in front of this
|
||||
.profiler-queries-bg {
|
||||
z-index:@zindex + 2;
|
||||
display:none;
|
||||
background:#000;
|
||||
opacity:0.7;
|
||||
position:absolute;
|
||||
top:0px;
|
||||
left:0px;
|
||||
min-width:100%;
|
||||
}
|
||||
|
||||
// used when viewing a shared, full page result
|
||||
.profiler-result-full {
|
||||
.profiler-result {
|
||||
|
||||
width:950px;
|
||||
margin:30px auto;
|
||||
|
||||
.profiler-button {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.profiler-popup {
|
||||
|
||||
.profiler-info {
|
||||
font-size: 25px;
|
||||
border-bottom:1px solid @mutedColor;
|
||||
padding-bottom:3px;
|
||||
margin-bottom:25px;
|
||||
|
||||
.profiler-overall-duration {
|
||||
padding-right:20px;
|
||||
font-size:80%;
|
||||
color:#888;
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-timings {
|
||||
td, th {
|
||||
padding-left:8px;
|
||||
padding-right:8px;
|
||||
}
|
||||
th {
|
||||
padding-bottom:7px;
|
||||
}
|
||||
td {
|
||||
font-size:14px;
|
||||
padding-bottom:4px;
|
||||
|
||||
&:first-child {
|
||||
padding-left:10px;
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-label {
|
||||
max-width:550px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profiler-queries {
|
||||
margin:25px 0;
|
||||
table {
|
||||
width:100%;
|
||||
}
|
||||
th {
|
||||
font-size:16px;
|
||||
color:#555;
|
||||
line-height:20px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding:15px 10px;
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.profiler-info div {
|
||||
text-align:right;
|
||||
margin-bottom:5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
<script id="profilerTemplate" type="text/x-jquery-tmpl">
|
||||
|
||||
<div class="profiler-result">
|
||||
|
||||
<div class="profiler-button {{if HasDuplicateSqlTimings}}profiler-warning{{/if}}">
|
||||
{{if HasDuplicateSqlTimings}}<span class="profiler-nuclear">!</span>{{/if}}
|
||||
<span class="profiler-number">
|
||||
${MiniProfiler.formatDuration(DurationMilliseconds)} <span class="profiler-unit">ms</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="profiler-popup">
|
||||
<div class="profiler-info">
|
||||
<span class="profiler-name">
|
||||
${Name} <span class="profiler-overall-duration">(${MiniProfiler.formatDuration(DurationMilliseconds)} ms)</span>
|
||||
</span>
|
||||
<span class="profiler-server-time">${MachineName} on ${MiniProfiler.renderDate(Started)}</span>
|
||||
</div>
|
||||
<div class="profiler-output">
|
||||
<table class="profiler-timings">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>duration (ms)</th>
|
||||
<th class="profiler-duration-with-children">with children (ms)</th>
|
||||
<th class="time-from-start">from start (ms)</th>
|
||||
{{if HasSqlTimings}}
|
||||
<th colspan="2">query time (ms)</th>
|
||||
{{/if}}
|
||||
{{each CustomTimingNames}}
|
||||
<th colspan="2">${$value.toLowerCase()} (ms)</th>
|
||||
{{/each}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{tmpl({timing:Root, page:this.data}) "#timingTemplate"}}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
{{if !ClientTimings}}
|
||||
{{tmpl "#linksTemplate"}}
|
||||
{{/if}}
|
||||
<a class="profiler-toggle-duration-with-children" title="toggles column with aggregate child durations">show time with children</a>
|
||||
</td>
|
||||
{{if HasSqlTimings}}
|
||||
<td colspan="2" class="profiler-number profiler-percent-in-sql" title="${MiniProfiler.getSqlTimingsCount(Root)} queries spent ${MiniProfiler.formatDuration(DurationMillisecondsInSql)} ms of total request time">
|
||||
${MiniProfiler.formatDuration(DurationMillisecondsInSql / DurationMilliseconds * 100)}
|
||||
<span class="profiler-unit">% in sql</span>
|
||||
</td>
|
||||
{{/if}}
|
||||
{{each CustomTimingNames}}
|
||||
<td colspan="2" class="profiler-number profiler-percentage-in-sql" title="${CustomTimingStats[$value].Count} ${$value.toLowerCase()} invocations spent ${MiniProfiler.formatDuration(CustomTimingStats[$value].Duration)} ms of total request time">
|
||||
${MiniProfiler.formatDuration(CustomTimingStats[$value].Duration / DurationMilliseconds * 100)}
|
||||
<span class="profiler-unit">% in ${$value.toLowerCase()}</span>
|
||||
</td>
|
||||
{{/each}}
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
{{if ClientTimings}}
|
||||
<table class="profiler-timings profiler-client-timings">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>client event</th>
|
||||
<th>duration (ms)</th>
|
||||
<th>from start (ms)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{each MiniProfiler.getClientTimings(ClientTimings)}}
|
||||
<tr class="{{if $value.isTrivial }}profiler-trivial{{/if}}">
|
||||
<td class="profiler-label">${$value.name}</td>
|
||||
<td class="profiler-duration">
|
||||
{{if $value.duration >= 0}}
|
||||
<span class="profiler-unit"></span>${MiniProfiler.formatDuration($value.duration)}
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="profiler-duration time-from-start">
|
||||
<span class="profiler-unit">+</span>${MiniProfiler.formatDuration($value.start)}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<td colspan="3">
|
||||
{{tmpl "#linksTemplate"}}
|
||||
</td>
|
||||
</tfoot>
|
||||
</table>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if HasSqlTimings}}
|
||||
<div class="profiler-queries">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align:right">step<br />time from start<br />query type<br />duration</th>
|
||||
<th style="text-align:left">call stack<br />query</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{each(i, s) MiniProfiler.getSqlTimings(Root)}}
|
||||
{{tmpl({ g:s.prevGap }) "#sqlGapTemplate"}}
|
||||
{{tmpl({ i:i, s:s }) "#sqlTimingTemplate"}}
|
||||
{{if s.nextGap}}
|
||||
{{tmpl({ g:s.nextGap }) "#sqlGapTemplate"}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="profiler-trivial-gap-container">
|
||||
<a class="profiler-toggle-trivial-gaps" href="#">show trivial gaps</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
|
||||
</script>
|
||||
|
||||
<script id="linksTemplate" type="text/x-jquery-tmpl">
|
||||
<a href="${MiniProfiler.shareUrl(Id)}" class="profiler-share-profiler-results" target="_blank">share</a>
|
||||
{{if CustomLink}}
|
||||
<a href="${CustomLink}" class="profiler-custom-link" target="_blank">${CustomLinkName}</a>
|
||||
{{/if}}
|
||||
{{if HasTrivialTimings}}
|
||||
<a class="profiler-toggle-trivial" data-show-on-load="${HasAllTrivialTimings}" title="toggles any rows with < ${TrivialDurationThresholdMilliseconds} ms">
|
||||
show trivial
|
||||
</a>
|
||||
{{/if}}
|
||||
</script>
|
||||
|
||||
<script id="timingTemplate" type="text/x-jquery-tmpl">
|
||||
|
||||
<tr class="{{if timing.IsTrivial }}profiler-trivial{{/if}}" data-timing-id="${timing.Id}">
|
||||
<td class="profiler-label" title="{{if timing.Name && timing.Name.length > 45 }}${timing.Name}{{/if}}">
|
||||
<span class="profiler-indent">${MiniProfiler.renderIndent(timing.Depth)}</span> ${timing.Name.slice(0,45)}{{if timing.Name && timing.Name.length > 45 }}...{{/if}}
|
||||
</td>
|
||||
<td class="profiler-duration" title="duration of this step without any children's durations">
|
||||
${MiniProfiler.formatDuration(timing.DurationWithoutChildrenMilliseconds)}
|
||||
</td>
|
||||
<td class="profiler-duration profiler-duration-with-children" title="duration of this step and its children">
|
||||
${MiniProfiler.formatDuration(timing.DurationMilliseconds)}
|
||||
</td>
|
||||
<td class="profiler-duration time-from-start" title="time elapsed since profiling started">
|
||||
<span class="profiler-unit">+</span>${MiniProfiler.formatDuration(timing.StartMilliseconds)}
|
||||
</td>
|
||||
|
||||
{{if timing.HasSqlTimings}}
|
||||
<td class="profiler-duration {{if timing.HasDuplicateSqlTimings}}profiler-warning{{/if}}" title="{{if timing.HasDuplicateSqlTimings}}duplicate queries detected - {{/if}}{{if timing.ExecutedReaders > 0 || timing.ExecutedScalars > 0 || timing.ExecutedNonQueries > 0}}${timing.ExecutedReaders} reader, ${timing.ExecutedScalars} scalar, ${timing.ExecutedNonQueries} non-query statements executed{{/if}}">
|
||||
<a class="profiler-queries-show">
|
||||
{{if timing.HasDuplicateSqlTimings}}<span class="profiler-nuclear">!</span>{{/if}}
|
||||
${timing.SqlTimings.length} <span class="profiler-unit">sql</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="profiler-duration" title="aggregate duration of all queries in this step (excludes children)">
|
||||
${MiniProfiler.formatDuration(timing.SqlTimingsDurationMilliseconds)}
|
||||
</td>
|
||||
{{else}}
|
||||
<td colspan="2"></td>
|
||||
{{/if}}
|
||||
|
||||
{{each page.CustomTimingNames}}
|
||||
{{if timing.CustomTimings && timing.CustomTimings[$value]}}
|
||||
<td class="profiler-duration" title="aggregate number of all ${$value.toLowerCase()} invocations in this step (excludes children)">
|
||||
${timing.CustomTimings[$value].length} ${$value.toLowerCase()}
|
||||
</td>
|
||||
<td class="profiler-duration" title="aggregate duration of all ${$value.toLowerCase()} invocations in this step (excludes children)">
|
||||
${MiniProfiler.formatDuration(timing.CustomTimingStats[$value].Duration)}
|
||||
</td>
|
||||
{{else}}
|
||||
<td colspan="2"></td>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
</tr>
|
||||
|
||||
{{if timing.HasChildren}}
|
||||
{{each timing.Children}}
|
||||
{{tmpl({timing: $value, page: page}) "#timingTemplate"}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
</script>
|
||||
|
||||
<script id="sqlTimingTemplate" type="text/x-jquery-tmpl">
|
||||
|
||||
<tr class="{{if i % 2 == 1}}profiler-odd{{/if}}" data-timing-id="${s.ParentTimingId}">
|
||||
<td class="profiler-info">
|
||||
<div>${s.ParentTimingName}</div>
|
||||
<div class="profiler-number"><span class="profiler-unit">T+</span>${MiniProfiler.formatDuration(s.StartMilliseconds)} <span class="profiler-unit">ms</span></div>
|
||||
<div>
|
||||
{{if s.IsDuplicate}}<span class="profiler-warning">DUPLICATE</span>{{/if}}
|
||||
${MiniProfiler.renderExecuteType(s.ExecuteType)}
|
||||
</div>
|
||||
<div title="{{if s.ExecuteType == 3}}first result fetched: ${s.FirstFetchDurationMilliseconds}ms{{/if}}">${MiniProfiler.formatDuration(s.DurationMilliseconds)} <span class="profiler-unit">ms</span></div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="query">
|
||||
<pre class="profiler-stack-trace">${s.StackTraceSnippet}</pre>
|
||||
<pre class="prettyprint lang-sql"><code>${s.FormattedCommandString} </code></pre>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</script>
|
||||
|
||||
<script id="sqlGapTemplate" type="text/x-jquery-tmpl">
|
||||
|
||||
<tr class="profiler-gap-info{{if g.duration < 4}} profiler-trivial-gaps{{/if}}">
|
||||
<td class="profiler-info">
|
||||
${g.duration} <span class="profiler-unit">ms</span>
|
||||
</td>
|
||||
<td class="query">
|
||||
<div>${g.topReason.name} — ${g.topReason.duration.toFixed(2)} <span class="profiler-unit">ms</span></div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</script>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,486 @@
|
|||
/*
|
||||
* jQuery Templating Plugin
|
||||
* Copyright 2010, John Resig
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*/
|
||||
(function (jQuery, undefined) {
|
||||
var oldManip = jQuery.fn.domManip, tmplItmAtt = "_tmplitem", htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
|
||||
newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = [];
|
||||
|
||||
function newTmplItem(options, parentItem, fn, data) {
|
||||
// Returns a template item data structure for a new rendered instance of a template (a 'template item').
|
||||
// The content field is a hierarchical array of strings and nested items (to be
|
||||
// removed and replaced by nodes field of dom elements, once inserted in DOM).
|
||||
var newItem = {
|
||||
data: data || (parentItem ? parentItem.data : {}),
|
||||
_wrap: parentItem ? parentItem._wrap : null,
|
||||
tmpl: null,
|
||||
parent: parentItem || null,
|
||||
nodes: [],
|
||||
calls: tiCalls,
|
||||
nest: tiNest,
|
||||
wrap: tiWrap,
|
||||
html: tiHtml,
|
||||
update: tiUpdate
|
||||
};
|
||||
if (options) {
|
||||
jQuery.extend(newItem, options, { nodes: [], parent: parentItem });
|
||||
}
|
||||
if (fn) {
|
||||
// Build the hierarchical content to be used during insertion into DOM
|
||||
newItem.tmpl = fn;
|
||||
newItem._ctnt = newItem._ctnt || newItem.tmpl(jQuery, newItem);
|
||||
newItem.key = ++itemKey;
|
||||
// Keep track of new template item, until it is stored as jQuery Data on DOM element
|
||||
(stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem;
|
||||
}
|
||||
return newItem;
|
||||
}
|
||||
|
||||
// Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core).
|
||||
jQuery.each({
|
||||
appendTo: "append",
|
||||
prependTo: "prepend",
|
||||
insertBefore: "before",
|
||||
insertAfter: "after",
|
||||
replaceAll: "replaceWith"
|
||||
}, function (name, original) {
|
||||
jQuery.fn[name] = function (selector) {
|
||||
var ret = [], insert = jQuery(selector), elems, i, l, tmplItems,
|
||||
parent = this.length === 1 && this[0].parentNode;
|
||||
|
||||
appendToTmplItems = newTmplItems || {};
|
||||
if (parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1) {
|
||||
insert[original](this[0]);
|
||||
ret = this;
|
||||
} else {
|
||||
for (i = 0, l = insert.length; i < l; i++) {
|
||||
cloneIndex = i;
|
||||
elems = (i > 0 ? this.clone(true) : this).get();
|
||||
jQuery.fn[original].apply(jQuery(insert[i]), elems);
|
||||
ret = ret.concat(elems);
|
||||
}
|
||||
cloneIndex = 0;
|
||||
ret = this.pushStack(ret, name, insert.selector);
|
||||
}
|
||||
tmplItems = appendToTmplItems;
|
||||
appendToTmplItems = null;
|
||||
jQuery.tmpl.complete(tmplItems);
|
||||
return ret;
|
||||
};
|
||||
});
|
||||
|
||||
jQuery.fn.extend({
|
||||
// Use first wrapped element as template markup.
|
||||
// Return wrapped set of template items, obtained by rendering template against data.
|
||||
tmpl: function (data, options, parentItem) {
|
||||
return jQuery.tmpl(this[0], data, options, parentItem);
|
||||
},
|
||||
|
||||
// Find which rendered template item the first wrapped DOM element belongs to
|
||||
tmplItem: function () {
|
||||
return jQuery.tmplItem(this[0]);
|
||||
},
|
||||
|
||||
// Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
|
||||
template: function (name) {
|
||||
return jQuery.template(name, this[0]);
|
||||
},
|
||||
|
||||
domManip: function (args, table, callback, options) {
|
||||
// This appears to be a bug in the appendTo, etc. implementation
|
||||
// it should be doing .call() instead of .apply(). See #6227
|
||||
if (args[0] && args[0].nodeType) {
|
||||
var dmArgs = jQuery.makeArray(arguments), argsLength = args.length, i = 0, tmplItem;
|
||||
while (i < argsLength && !(tmplItem = jQuery.data(args[i++], "tmplItem"))) { }
|
||||
if (argsLength > 1) {
|
||||
dmArgs[0] = [jQuery.makeArray(args)];
|
||||
}
|
||||
if (tmplItem && cloneIndex) {
|
||||
dmArgs[2] = function (fragClone) {
|
||||
// Handler called by oldManip when rendered template has been inserted into DOM.
|
||||
jQuery.tmpl.afterManip(this, fragClone, callback);
|
||||
};
|
||||
}
|
||||
oldManip.apply(this, dmArgs);
|
||||
} else {
|
||||
oldManip.apply(this, arguments);
|
||||
}
|
||||
cloneIndex = 0;
|
||||
if (!appendToTmplItems) {
|
||||
jQuery.tmpl.complete(newTmplItems);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
jQuery.extend({
|
||||
// Return wrapped set of template items, obtained by rendering template against data.
|
||||
tmpl: function (tmpl, data, options, parentItem) {
|
||||
var ret, topLevel = !parentItem;
|
||||
if (topLevel) {
|
||||
// This is a top-level tmpl call (not from a nested template using {{tmpl}})
|
||||
parentItem = topTmplItem;
|
||||
tmpl = jQuery.template[tmpl] || jQuery.template(null, tmpl);
|
||||
wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level
|
||||
} else if (!tmpl) {
|
||||
// The template item is already associated with DOM - this is a refresh.
|
||||
// Re-evaluate rendered template for the parentItem
|
||||
tmpl = parentItem.tmpl;
|
||||
newTmplItems[parentItem.key] = parentItem;
|
||||
parentItem.nodes = [];
|
||||
if (parentItem.wrapped) {
|
||||
updateWrapped(parentItem, parentItem.wrapped);
|
||||
}
|
||||
// Rebuild, without creating a new template item
|
||||
return jQuery(build(parentItem, null, parentItem.tmpl(jQuery, parentItem)));
|
||||
}
|
||||
if (!tmpl) {
|
||||
return []; // Could throw...
|
||||
}
|
||||
if (typeof data === "function") {
|
||||
data = data.call(parentItem || {});
|
||||
}
|
||||
if (options && options.wrapped) {
|
||||
updateWrapped(options, options.wrapped);
|
||||
}
|
||||
ret = jQuery.isArray(data) ?
|
||||
jQuery.map(data, function (dataItem) {
|
||||
return dataItem ? newTmplItem(options, parentItem, tmpl, dataItem) : null;
|
||||
}) :
|
||||
[newTmplItem(options, parentItem, tmpl, data)];
|
||||
return topLevel ? jQuery(build(parentItem, null, ret)) : ret;
|
||||
},
|
||||
|
||||
// Return rendered template item for an element.
|
||||
tmplItem: function (elem) {
|
||||
var tmplItem;
|
||||
if (elem instanceof jQuery) {
|
||||
elem = elem[0];
|
||||
}
|
||||
while (elem && elem.nodeType === 1 && !(tmplItem = jQuery.data(elem, "tmplItem")) && (elem = elem.parentNode)) { }
|
||||
return tmplItem || topTmplItem;
|
||||
},
|
||||
|
||||
// Set:
|
||||
// Use $.template( name, tmpl ) to cache a named template,
|
||||
// where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc.
|
||||
// Use $( "selector" ).template( name ) to provide access by name to a script block template declaration.
|
||||
|
||||
// Get:
|
||||
// Use $.template( name ) to access a cached template.
|
||||
// Also $( selectorToScriptBlock ).template(), or $.template( null, templateString )
|
||||
// will return the compiled template, without adding a name reference.
|
||||
// If templateString includes at least one HTML tag, $.template( templateString ) is equivalent
|
||||
// to $.template( null, templateString )
|
||||
template: function (name, tmpl) {
|
||||
if (tmpl) {
|
||||
// Compile template and associate with name
|
||||
if (typeof tmpl === "string") {
|
||||
// This is an HTML string being passed directly in.
|
||||
tmpl = buildTmplFn(tmpl)
|
||||
} else if (tmpl instanceof jQuery) {
|
||||
tmpl = tmpl[0] || {};
|
||||
}
|
||||
if (tmpl.nodeType) {
|
||||
// If this is a template block, use cached copy, or generate tmpl function and cache.
|
||||
tmpl = jQuery.data(tmpl, "tmpl") || jQuery.data(tmpl, "tmpl", buildTmplFn(tmpl.innerHTML));
|
||||
}
|
||||
return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl;
|
||||
}
|
||||
// Return named compiled template
|
||||
return name ? (typeof name !== "string" ? jQuery.template(null, name) :
|
||||
(jQuery.template[name] ||
|
||||
// If not in map, treat as a selector. (If integrated with core, use quickExpr.exec)
|
||||
jQuery.template(null, htmlExpr.test(name) ? name : jQuery(name)))) : null;
|
||||
},
|
||||
|
||||
encode: function (text) {
|
||||
// Do HTML encoding replacing < > & and ' and " by corresponding entities.
|
||||
return ("" + text).split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'");
|
||||
}
|
||||
});
|
||||
|
||||
jQuery.extend(jQuery.tmpl, {
|
||||
tag: {
|
||||
"tmpl": {
|
||||
_default: { $2: "null" },
|
||||
open: "if($notnull_1){_=_.concat($item.nest($1,$2));}"
|
||||
// tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions)
|
||||
// This means that {{tmpl foo}} treats foo as a template (which IS a function).
|
||||
// Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}.
|
||||
},
|
||||
"wrap": {
|
||||
_default: { $2: "null" },
|
||||
open: "$item.calls(_,$1,$2);_=[];",
|
||||
close: "call=$item.calls();_=call._.concat($item.wrap(call,_));"
|
||||
},
|
||||
"each": {
|
||||
_default: { $2: "$index, $value" },
|
||||
open: "if($notnull_1){$.each($1a,function($2){with(this){",
|
||||
close: "}});}"
|
||||
},
|
||||
"if": {
|
||||
open: "if(($notnull_1) && $1a){",
|
||||
close: "}"
|
||||
},
|
||||
"else": {
|
||||
_default: { $1: "true" },
|
||||
open: "}else if(($notnull_1) && $1a){"
|
||||
},
|
||||
"html": {
|
||||
// Unecoded expression evaluation.
|
||||
open: "if($notnull_1){_.push($1a);}"
|
||||
},
|
||||
"=": {
|
||||
// Encoded expression evaluation. Abbreviated form is ${}.
|
||||
_default: { $1: "$data" },
|
||||
open: "if($notnull_1){_.push($.encode($1a));}"
|
||||
},
|
||||
"!": {
|
||||
// Comment tag. Skipped by parser
|
||||
open: ""
|
||||
}
|
||||
},
|
||||
|
||||
// This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events
|
||||
complete: function (items) {
|
||||
newTmplItems = {};
|
||||
},
|
||||
|
||||
// Call this from code which overrides domManip, or equivalent
|
||||
// Manage cloning/storing template items etc.
|
||||
afterManip: function afterManip(elem, fragClone, callback) {
|
||||
// Provides cloned fragment ready for fixup prior to and after insertion into DOM
|
||||
var content = fragClone.nodeType === 11 ?
|
||||
jQuery.makeArray(fragClone.childNodes) :
|
||||
fragClone.nodeType === 1 ? [fragClone] : [];
|
||||
|
||||
// Return fragment to original caller (e.g. append) for DOM insertion
|
||||
callback.call(elem, fragClone);
|
||||
|
||||
// Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data.
|
||||
storeTmplItems(content);
|
||||
cloneIndex++;
|
||||
}
|
||||
});
|
||||
|
||||
//========================== Private helper functions, used by code above ==========================
|
||||
|
||||
function build(tmplItem, nested, content) {
|
||||
// Convert hierarchical content into flat string array
|
||||
// and finally return array of fragments ready for DOM insertion
|
||||
var frag, ret = content ? jQuery.map(content, function (item) {
|
||||
return (typeof item === "string") ?
|
||||
// Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM.
|
||||
(tmplItem.key ? item.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2") : item) :
|
||||
// This is a child template item. Build nested template.
|
||||
build(item, tmplItem, item._ctnt);
|
||||
}) :
|
||||
// If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}.
|
||||
tmplItem;
|
||||
if (nested) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// top-level template
|
||||
ret = ret.join("");
|
||||
|
||||
// Support templates which have initial or final text nodes, or consist only of text
|
||||
// Also support HTML entities within the HTML markup.
|
||||
ret.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function (all, before, middle, after) {
|
||||
frag = jQuery(middle).get();
|
||||
|
||||
storeTmplItems(frag);
|
||||
if (before) {
|
||||
frag = unencode(before).concat(frag);
|
||||
}
|
||||
if (after) {
|
||||
frag = frag.concat(unencode(after));
|
||||
}
|
||||
});
|
||||
return frag ? frag : unencode(ret);
|
||||
}
|
||||
|
||||
function unencode(text) {
|
||||
// Use createElement, since createTextNode will not render HTML entities correctly
|
||||
var el = document.createElement("div");
|
||||
el.innerHTML = text;
|
||||
return jQuery.makeArray(el.childNodes);
|
||||
}
|
||||
|
||||
// Generate a reusable function that will serve to render a template against data
|
||||
function buildTmplFn(markup) {
|
||||
return new Function("jQuery", "$item",
|
||||
"var $=jQuery,call,_=[],$data=$item.data;" +
|
||||
|
||||
// Introduce the data as local variables using with(){}
|
||||
"with($data){_.push('" +
|
||||
|
||||
// Convert the template into pure JavaScript
|
||||
jQuery.trim(markup)
|
||||
.replace(/([\\'])/g, "\\$1")
|
||||
.replace(/[\r\t\n]/g, " ")
|
||||
.replace(/\$\{([^\}]*)\}/g, "{{= $1}}")
|
||||
.replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,
|
||||
function (all, slash, type, fnargs, target, parens, args) {
|
||||
var tag = jQuery.tmpl.tag[type], def, expr, exprAutoFnDetect;
|
||||
if (!tag) {
|
||||
throw "Template command not found: " + type;
|
||||
}
|
||||
def = tag._default || [];
|
||||
if (parens && !/\w$/.test(target)) {
|
||||
target += parens;
|
||||
parens = "";
|
||||
}
|
||||
if (target) {
|
||||
target = unescape(target);
|
||||
args = args ? ("," + unescape(args) + ")") : (parens ? ")" : "");
|
||||
// Support for target being things like a.toLowerCase();
|
||||
// In that case don't call with template item as 'this' pointer. Just evaluate...
|
||||
expr = parens ? (target.indexOf(".") > -1 ? target + parens : ("(" + target + ").call($item" + args)) : target;
|
||||
exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))";
|
||||
} else {
|
||||
exprAutoFnDetect = expr = def.$1 || "null";
|
||||
}
|
||||
fnargs = unescape(fnargs);
|
||||
return "');" +
|
||||
tag[slash ? "close" : "open"]
|
||||
.split("$notnull_1").join(target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true")
|
||||
.split("$1a").join(exprAutoFnDetect)
|
||||
.split("$1").join(expr)
|
||||
.split("$2").join(fnargs ?
|
||||
fnargs.replace(/\s*([^\(]+)\s*(\((.*?)\))?/g, function (all, name, parens, params) {
|
||||
params = params ? ("," + params + ")") : (parens ? ")" : "");
|
||||
return params ? ("(" + name + ").call($item" + params) : all;
|
||||
})
|
||||
: (def.$2 || "")
|
||||
) +
|
||||
"_.push('";
|
||||
}) +
|
||||
"');}return _;"
|
||||
);
|
||||
}
|
||||
function updateWrapped(options, wrapped) {
|
||||
// Build the wrapped content.
|
||||
options._wrap = build(options, true,
|
||||
// Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string.
|
||||
jQuery.isArray(wrapped) ? wrapped : [htmlExpr.test(wrapped) ? wrapped : jQuery(wrapped).html()]
|
||||
).join("");
|
||||
}
|
||||
|
||||
function unescape(args) {
|
||||
return args ? args.replace(/\\'/g, "'").replace(/\\\\/g, "\\") : null;
|
||||
}
|
||||
function outerHtml(elem) {
|
||||
var div = document.createElement("div");
|
||||
div.appendChild(elem.cloneNode(true));
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance.
|
||||
function storeTmplItems(content) {
|
||||
var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m;
|
||||
for (i = 0, l = content.length; i < l; i++) {
|
||||
if ((elem = content[i]).nodeType !== 1) {
|
||||
continue;
|
||||
}
|
||||
elems = elem.getElementsByTagName("*");
|
||||
for (m = elems.length - 1; m >= 0; m--) {
|
||||
processItemKey(elems[m]);
|
||||
}
|
||||
processItemKey(elem);
|
||||
}
|
||||
function processItemKey(el) {
|
||||
var pntKey, pntNode = el, pntItem, tmplItem, key;
|
||||
// Ensure that each rendered template inserted into the DOM has its own template item,
|
||||
if ((key = el.getAttribute(tmplItmAtt))) {
|
||||
while (pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute(tmplItmAtt))) { }
|
||||
if (pntKey !== key) {
|
||||
// The next ancestor with a _tmplitem expando is on a different key than this one.
|
||||
// So this is a top-level element within this template item
|
||||
// Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment.
|
||||
pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute(tmplItmAtt) || 0)) : 0;
|
||||
if (!(tmplItem = newTmplItems[key])) {
|
||||
// The item is for wrapped content, and was copied from the temporary parent wrappedItem.
|
||||
tmplItem = wrappedItems[key];
|
||||
tmplItem = newTmplItem(tmplItem, newTmplItems[pntNode] || wrappedItems[pntNode], null, true);
|
||||
tmplItem.key = ++itemKey;
|
||||
newTmplItems[itemKey] = tmplItem;
|
||||
}
|
||||
if (cloneIndex) {
|
||||
cloneTmplItem(key);
|
||||
}
|
||||
}
|
||||
el.removeAttribute(tmplItmAtt);
|
||||
} else if (cloneIndex && (tmplItem = jQuery.data(el, "tmplItem"))) {
|
||||
// This was a rendered element, cloned during append or appendTo etc.
|
||||
// TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem.
|
||||
cloneTmplItem(tmplItem.key);
|
||||
newTmplItems[tmplItem.key] = tmplItem;
|
||||
pntNode = jQuery.data(el.parentNode, "tmplItem");
|
||||
pntNode = pntNode ? pntNode.key : 0;
|
||||
}
|
||||
if (tmplItem) {
|
||||
pntItem = tmplItem;
|
||||
// Find the template item of the parent element.
|
||||
// (Using !=, not !==, since pntItem.key is number, and pntNode may be a string)
|
||||
while (pntItem && pntItem.key != pntNode) {
|
||||
// Add this element as a top-level node for this rendered template item, as well as for any
|
||||
// ancestor items between this item and the item of its parent element
|
||||
pntItem.nodes.push(el);
|
||||
pntItem = pntItem.parent;
|
||||
}
|
||||
// Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering...
|
||||
delete tmplItem._ctnt;
|
||||
delete tmplItem._wrap;
|
||||
// Store template item as jQuery data on the element
|
||||
jQuery.data(el, "tmplItem", tmplItem);
|
||||
}
|
||||
function cloneTmplItem(key) {
|
||||
key = key + keySuffix;
|
||||
tmplItem = newClonedItems[key] =
|
||||
(newClonedItems[key] || newTmplItem(tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent, null, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//---- Helper functions for template item ----
|
||||
|
||||
function tiCalls(content, tmpl, data, options) {
|
||||
if (!content) {
|
||||
return stack.pop();
|
||||
}
|
||||
stack.push({ _: content, tmpl: tmpl, item: this, data: data, options: options });
|
||||
}
|
||||
|
||||
function tiNest(tmpl, data, options) {
|
||||
// nested template, using {{tmpl}} tag
|
||||
return jQuery.tmpl(jQuery.template(tmpl), data, options, this);
|
||||
}
|
||||
|
||||
function tiWrap(call, wrapped) {
|
||||
// nested template, using {{wrap}} tag
|
||||
var options = call.options || {};
|
||||
options.wrapped = wrapped;
|
||||
// Apply the template, which may incorporate wrapped content,
|
||||
return jQuery.tmpl(jQuery.template(call.tmpl), call.data, options, call.item);
|
||||
}
|
||||
|
||||
function tiHtml(filter, textOnly) {
|
||||
var wrapped = this._wrap;
|
||||
return jQuery.map(
|
||||
jQuery(jQuery.isArray(wrapped) ? wrapped.join("") : wrapped).filter(filter || "*"),
|
||||
function (e) {
|
||||
return textOnly ?
|
||||
e.innerText || e.textContent :
|
||||
e.outerHTML || outerHtml(e);
|
||||
});
|
||||
}
|
||||
|
||||
function tiUpdate() {
|
||||
var coll = this.nodes;
|
||||
jQuery.tmpl(null, null, null, this).insertBefore(coll[0]);
|
||||
jQuery(coll).remove();
|
||||
}
|
||||
})(MiniProfiler.jQuery);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue