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:
xianbo 2014-05-30 11:18:05 +08:00
commit c9e4fdee9d
150 changed files with 10620 additions and 282 deletions

16
Gemfile
View File

@ -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`

View File

@ -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!

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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; }

View File

@ -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]

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -49,5 +49,9 @@ module TagsHelper
end
return @result
end
end
def tagname_val
("#tag_name_name").value
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -686,7 +686,12 @@ class User < Principal
(block_given? ? yield(role, self) : true)
}
else
false
if admin?
return true
end
#无项目时 查看Non memberid为1角色是否有权限执行action
Role.find('1').allowed_to?(action)
# false
end
end

View File

@ -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>

View File

@ -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"> &nbsp;</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"> &nbsp;</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>

View File

@ -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 %>
&nbsp;
<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 %>

View File

@ -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) %>

View File

@ -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 %>

View File

@ -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">&nbsp;
<strong>发布人:&nbsp; <%= link_to (display_id ? homework.user.realname : homework.user ), user_path(homework.user)%></strong>
<strong>发布人:&nbsp; <%= 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">&nbsp;
<% if display_id %>
<% if is_cur_course_user? @bid %>
<strong><%= l(:label_bidding_user_studentcode) %>&nbsp; &nbsp;<%= homework.user.user_extensions.student_id%></strong>
<% end %>
</td>

View File

@ -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>

View File

@ -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("");
}
})
}

View File

@ -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'>

View File

@ -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 %>

View File

@ -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) %>

View File

@ -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 %>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 %>&nbsp;<%= 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>

View File

@ -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'>

View File

@ -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) %>

View File

@ -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 %>

View File

@ -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)
},

View File

@ -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>

View File

@ -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>

View File

@ -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)) -%>

View File

@ -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 %>
&nbsp;
<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 -%>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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.

View File

@ -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: 查看如何提交代码:

View File

@ -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

View File

@ -0,0 +1,5 @@
class AddColoumToHomeworkJournalsForMessage < ActiveRecord::Migration
def change
add_column :journals_for_messages, :is_comprehensive_evaluation, :integer
end
end

View File

@ -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|

View File

@ -0,0 +1,4 @@
language: ruby
rvm:
- 2.1.0
- 2.0.0

View File

@ -0,0 +1 @@
--markup markdown --no-private

View File

@ -0,0 +1,3 @@
# Changelog
See https://github.com/charliesome/better_errors/releases

10
lib/better_errors/Gemfile Normal file
View File

@ -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"

View File

@ -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.

103
lib/better_errors/README.md Normal file
View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 %>

View File

@ -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>&gt;&gt;</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 -->

View File

@ -0,0 +1,3 @@
module BetterErrors
VERSION = "1.1.0"
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,20 @@
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
thirteen
fourteen
fifteen
sixteen
seventeen
eighteen
nineteen
twenty

View File

@ -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

View File

@ -0,0 +1,5 @@
$: << File.expand_path("../../lib", __FILE__)
ENV["EDITOR"] = nil
require "better_errors"

View File

@ -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

View File

@ -0,0 +1,9 @@
language: ruby
rvm:
- 1.9.3
- 2.0.0
- 2.1.1
bundler_args: ""
services:
- redis
- memcached

View File

@ -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)

View File

@ -0,0 +1,3 @@
source 'http://rubygems.org'
gemspec

View File

@ -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.

View File

@ -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

View File

@ -0,0 +1,2 @@
Autotest.add_discovery { "rspec2" }

View File

@ -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;
}

View File

@ -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 += '&nbsp;';
}
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,"&amp;").replace(M,"&lt;").replace(N,"&gt;")}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,"&amp;").replace(M,"&lt;").replace(N,"&gt;").replace(X,"&quot;"),'"')}}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&#160;");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?"&#160;\r\n":h===7?"&#160;<br>\r":"&#160;\r":"&#160;<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=/&lt;/g,fa=/&gt;/g,ga=/&apos;/g,ha=/&quot;/g,ja=/&amp;/g,ia=/&nbsp;/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"])
;
}

View File

@ -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;
}
}
}
}

View File

@ -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 &lt; ${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} &mdash; ${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

View File

@ -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("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;");
}
});
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