hackathon_manager 0.6.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +7 -1
  3. data/app/assets/javascripts/hackathon_manager/manage/application.js +4 -6
  4. data/app/assets/javascripts/hackathon_manager/manage/lib/datatables.js +82 -0
  5. data/app/assets/javascripts/hackathon_manager/manage/lib/setupDataTables.js +13 -64
  6. data/app/assets/javascripts/hackathon_manager/vendor/datatables.min.js +290 -0
  7. data/app/assets/stylesheets/hackathon_manager/dashboard.css +109 -0
  8. data/app/assets/stylesheets/hackathon_manager/datatables.min.css +18 -0
  9. data/app/assets/stylesheets/hackathon_manager/manage.sass +64 -47
  10. data/app/controllers/manage/stats_controller.rb +66 -30
  11. data/app/controllers/rsvps_controller.rb +2 -0
  12. data/app/datatables/admin_datatable.rb +3 -3
  13. data/app/datatables/message_datatable.rb +5 -5
  14. data/app/datatables/questionnaire_datatable.rb +6 -5
  15. data/app/datatables/school_datatable.rb +3 -3
  16. data/app/helpers/hackathon_manager_helper.rb +63 -4
  17. data/app/helpers/manage/bootstrap_form_helper.rb +98 -0
  18. data/app/models/bus_list.rb +1 -1
  19. data/app/models/message.rb +3 -3
  20. data/app/models/questionnaire.rb +1 -1
  21. data/app/models/school.rb +10 -4
  22. data/app/models/user.rb +3 -1
  23. data/app/views/application/_triggered_email_summary.html.haml +11 -9
  24. data/app/views/layouts/hackathon_manager/application.html.haml +4 -0
  25. data/app/views/layouts/manage/_flashes.html.haml +4 -0
  26. data/app/views/layouts/manage/_page_title.html.haml +8 -0
  27. data/app/views/layouts/manage/application.html.haml +72 -19
  28. data/app/views/manage/admins/_form.html.haml +4 -5
  29. data/app/views/manage/admins/edit.html.haml +5 -10
  30. data/app/views/manage/admins/index.html.haml +4 -8
  31. data/app/views/manage/admins/new.html.haml +5 -6
  32. data/app/views/manage/admins/show.html.haml +12 -14
  33. data/app/views/manage/bus_lists/_form.html.haml +3 -6
  34. data/app/views/manage/bus_lists/edit.html.haml +3 -7
  35. data/app/views/manage/bus_lists/index.html.haml +16 -23
  36. data/app/views/manage/bus_lists/new.html.haml +2 -5
  37. data/app/views/manage/bus_lists/show.html.haml +78 -66
  38. data/app/views/manage/configs/show.html.haml +31 -27
  39. data/app/views/manage/dashboard/index.html.haml +28 -19
  40. data/app/views/manage/dashboard/map_data.tsv.erb +6 -13
  41. data/app/views/manage/messages/_form.html.haml +3 -3
  42. data/app/views/manage/messages/edit.html.haml +3 -10
  43. data/app/views/manage/messages/index.html.haml +5 -10
  44. data/app/views/manage/messages/new.html.haml +2 -7
  45. data/app/views/manage/messages/show.html.haml +65 -67
  46. data/app/views/manage/questionnaires/_acc_status_badge.html.haml +2 -0
  47. data/app/views/manage/questionnaires/_check_in_badge.html.haml +4 -0
  48. data/app/views/manage/questionnaires/_checkin.html.haml +40 -0
  49. data/app/views/manage/questionnaires/_form.html.haml +21 -21
  50. data/app/views/manage/questionnaires/_history.html.haml +7 -0
  51. data/app/views/manage/questionnaires/_overview.html.haml +126 -0
  52. data/app/views/manage/questionnaires/edit.html.haml +5 -11
  53. data/app/views/manage/questionnaires/index.html.haml +19 -23
  54. data/app/views/manage/questionnaires/new.html.haml +2 -7
  55. data/app/views/manage/questionnaires/show.html.haml +28 -65
  56. data/app/views/manage/schools/_form.html.haml +2 -2
  57. data/app/views/manage/schools/edit.html.haml +5 -11
  58. data/app/views/manage/schools/index.html.haml +4 -8
  59. data/app/views/manage/schools/merge.html.haml +6 -8
  60. data/app/views/manage/schools/new.html.haml +2 -7
  61. data/app/views/manage/schools/show.html.haml +58 -55
  62. data/app/views/manage/stats/index.html.haml +18 -15
  63. data/app/views/questionnaires/_form.html.haml +2 -2
  64. data/config/initializers/simple_form.rb +4 -0
  65. data/config/initializers/simple_form_bootstrap.rb +412 -0
  66. data/config/locales/en.yml +73 -0
  67. data/db/migrate/20180701160855_remove_email_from_questionnaires.rb +5 -0
  68. data/lib/hackathon_manager/version.rb +1 -1
  69. metadata +39 -31
  70. data/app/assets/javascripts/hackathon_manager/vendor/buttons.colVis.min.js +0 -6
  71. data/app/assets/javascripts/hackathon_manager/vendor/buttons.html5.min.js +0 -22
  72. data/app/assets/javascripts/hackathon_manager/vendor/dataTables.buttons.min.js +0 -35
  73. data/app/assets/javascripts/hackathon_manager/vendor/jquery.dataTables.min.js +0 -164
  74. data/app/assets/javascripts/hackathon_manager/vendor/pdfmake.min.js +0 -22
  75. data/app/assets/stylesheets/hackathon_manager/datatables/buttons.dataTables.min.css +0 -1
  76. data/app/assets/stylesheets/hackathon_manager/datatables/jquery.dataTables.min.css +0 -1
@@ -16,9 +16,8 @@ class SchoolDatatable < AjaxDatatablesRails::Base
16
16
  def data
17
17
  records.map do |record|
18
18
  {
19
- link: link_to('<i class="fa fa-search"></i>'.html_safe, manage_school_path(record)),
20
19
  id: record.id,
21
- name: record.name,
20
+ name: link_to(record.name, manage_school_path(record)),
22
21
  city: record.city,
23
22
  state: record.state,
24
23
  questionnaire_count: record.questionnaire_count,
@@ -27,8 +26,9 @@ class SchoolDatatable < AjaxDatatablesRails::Base
27
26
  end
28
27
  end
29
28
 
30
- # rubocop:disable Style/AccessorMethodName
29
+ # rubocop:disable Naming/AccessorMethodName
31
30
  def get_raw_records
32
31
  School.all
33
32
  end
33
+ # rubocop:enable Naming/AccessorMethodName
34
34
  end
@@ -1,7 +1,7 @@
1
1
  module HackathonManagerHelper
2
2
  def title(page_title)
3
3
  content_for(:page_title) { page_title }
4
- content_for(:title) { page_title + " - #{Rails.configuration.hackathon['name']}" }
4
+ content_for(:title) { page_title }
5
5
  page_title
6
6
  end
7
7
 
@@ -26,11 +26,33 @@ module HackathonManagerHelper
26
26
  markdown.render(text).html_safe
27
27
  end
28
28
 
29
+ # Same as link_to, but adds a special active class whenever the link matches
30
+ # the current page.
31
+ # Only https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/url_helper.rb
29
32
  def active_link_to(name = nil, options = nil, html_options = nil, &block)
30
- if current_page?(options)
31
- html_options[:class] = html_options[:class] + ' ' + html_options[:active_class]
33
+ # this is from Rails source - ignore rubocop
34
+ # rubocop:disable Style/ParallelAssignment
35
+ html_options, options, name = options, name, block if block_given?
36
+ options ||= {}
37
+ # rubocop:enable Style/ParallelAssignment
38
+
39
+ html_options = convert_options_to_data_attributes(options, html_options)
40
+
41
+ url = url_for(options)
42
+ html_options["href".freeze] ||= url
43
+
44
+ # Begin custom
45
+ active_children = html_options.delete('active_children')
46
+ active_children = true if active_children.nil?
47
+ current_url = request.env['PATH_INFO']
48
+ if current_page?(url) || (active_children && current_url.include?(url))
49
+ active_class = html_options.delete('active_class') || 'active'
50
+ existing_class = html_options['class'] || ''
51
+ html_options['class'] = existing_class + ' ' + active_class
32
52
  end
33
- link_to(name, options, html_options, &block)
53
+ # End custom
54
+
55
+ content_tag("a".freeze, name || url, html_options, &block)
34
56
  end
35
57
 
36
58
  # https://github.com/rails/sprockets-rails/issues/298#issuecomment-168927471
@@ -45,4 +67,41 @@ module HackathonManagerHelper
45
67
  def collection_or_text(model_value, collection)
46
68
  model_value.blank? || collection.include?(model_value) ? collection : nil
47
69
  end
70
+
71
+ def acc_status_class(acc_status)
72
+ case acc_status
73
+ when "denied"
74
+ "danger"
75
+ when "accepted"
76
+ "success"
77
+ when "waitlist"
78
+ "info"
79
+ when "late_waitlist"
80
+ "secondary"
81
+ when "pending"
82
+ "secondary"
83
+ when "rsvp_denied"
84
+ "danger"
85
+ when "rsvp_confirmed"
86
+ "success"
87
+ end
88
+ end
89
+
90
+ def display_datetime(datetime, opts = {})
91
+ formatted = ""
92
+ if Time.now - datetime < 5.hours
93
+ formatted << "#{time_ago_in_words(datetime, include_seconds: true)} ago"
94
+ else
95
+ format = datetime.year == Time.now.year ? "%b %-d at %I:%M %P" : "%b %-d, %Y at %I:%M %P"
96
+ formatted << "on " if opts[:in_sentence]
97
+ formatted << datetime.strftime(format)
98
+ end
99
+ "<span title=\"#{datetime}\">#{formatted}</span>".html_safe
100
+ end
101
+
102
+ def google_maps_link(*args)
103
+ query = args.reject(&:blank?).join('+')
104
+ query = CGI.escape(query)
105
+ "https://www.google.com/maps/search/?api=1&query=#{query}"
106
+ end
48
107
  end
@@ -0,0 +1,98 @@
1
+ module Manage
2
+ module BootstrapFormHelper
3
+ def bs_vertical_simple_form(path, options, &block)
4
+ bootstrap_options = {
5
+ wrapper: :bootstrap_vertical_form,
6
+ wrapper_mappings: {
7
+ boolean: :bootstrap_vertical_boolean,
8
+ check_boxes: :bootstrap_vertical_collection,
9
+ date: :bootstrap_vertical_multi_select,
10
+ datetime: :bootstrap_vertical_multi_select,
11
+ file: :bootstrap_vertical_file,
12
+ radio_buttons: :bootstrap_vertical_collection,
13
+ range: :bootstrap_vertical_range,
14
+ time: :bootstrap_vertical_multi_select
15
+ }
16
+ }
17
+ options = options.deep_merge(bootstrap_options)
18
+ patch_simple_form_config do
19
+ simple_form_for(path, options, &block)
20
+ end
21
+ end
22
+
23
+ def bs_horizontal_simple_form_for(path, options, &block)
24
+ bootstrap_options = {
25
+ wrapper: :bootstrap_horizontal_form,
26
+ wrapper_mappings: {
27
+ boolean: :bootstrap_horizontal_boolean,
28
+ check_boxes: :bootstrap_horizontal_collection,
29
+ date: :bootstrap_horizontal_multi_select,
30
+ datetime: :bootstrap_horizontal_multi_select,
31
+ file: :bootstrap_horizontal_file,
32
+ radio_buttons: :bootstrap_horizontal_collection,
33
+ range: :bootstrap_horizontal_range,
34
+ time: :bootstrap_horizontal_multi_select
35
+ }
36
+ }
37
+ options = options.deep_merge(bootstrap_options)
38
+ patch_simple_form_config do
39
+ simple_form_for(path, options, &block)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def set_config(key, value)
46
+ @saved_values ||= {}
47
+ @saved_values[key] = SimpleForm.send(key)
48
+ SimpleForm.send("#{key}=", value)
49
+ end
50
+
51
+ def restore_config
52
+ @saved_values.keys.each do |key|
53
+ value = @saved_values.delete(key)
54
+ SimpleForm.send("#{key}=", value)
55
+ end
56
+ end
57
+
58
+ def patch_simple_form_config
59
+ # Values copied from the top of
60
+ # https://github.com/rafaelfranca/simple_form-bootstrap/blob/master/config/initializers/simple_form_bootstrap.rb
61
+
62
+ # Default class for buttons
63
+ set_config(:button_class, 'btn')
64
+
65
+ # Define the default class of the input wrapper of the boolean input.
66
+ set_config(:boolean_label_class, 'form-check-label')
67
+
68
+ # How the label text should be generated altogether with the required text.
69
+ set_config(:label_text, ->(label, required, _explicit_label) { "#{label} #{required}" })
70
+
71
+ # Define the way to render check boxes / radio buttons with labels.
72
+ set_config(:boolean_style, :inline)
73
+
74
+ # You can wrap each item in a collection of radio/check boxes with a tag
75
+ set_config(:item_wrapper_tag, :div)
76
+
77
+ # Defines if the default input wrapper class should be included in radio
78
+ # collection wrappers.
79
+ set_config(:include_default_input_wrapper_class, false)
80
+
81
+ # CSS class to add for error notification helper.
82
+ set_config(:error_notification_class, 'alert alert-danger')
83
+
84
+ # Method used to tidy up errors. Specify any Rails Array method.
85
+ # :first lists the first message for each field.
86
+ # :to_sentence to list all errors for each field.
87
+ set_config(:error_method, :to_sentence)
88
+
89
+ # add validation classes to `input_field`
90
+ set_config(:input_field_error_class, 'is-invalid')
91
+ # set_config(:input_field_valid_class, 'is-valid')
92
+
93
+ yield
94
+ ensure
95
+ restore_config
96
+ end
97
+ end
98
+ end
@@ -1,5 +1,5 @@
1
1
  class BusList < ApplicationRecord
2
- validates_presence_of :name
2
+ validates_presence_of :name, :capacity
3
3
  validates_uniqueness_of :name
4
4
 
5
5
  has_many :schools
@@ -18,7 +18,7 @@ class Message < ApplicationRecord
18
18
  "rsvp-denied" => "RSVP Denied Attendees",
19
19
  "checked-in" => "Checked-In Attendees",
20
20
  "non-checked-in" => "Non-Checked-In, Accepted & RSVP'd Applications",
21
- "non-checked-in-excluding" => "Non-Checked-In Applications, Excluding Accepted & RSVP'd",
21
+ "non-checked-in-excluding" => "Non-Checked-In Applications, Excluding Accepted & RSVP'd"
22
22
  }.freeze
23
23
 
24
24
  POSSIBLE_TRIGGERS = {
@@ -43,13 +43,13 @@ class Message < ApplicationRecord
43
43
  labels = recipients.map do |r|
44
44
  if POSSIBLE_SIMPLE_RECIPIENTS.include?(r)
45
45
  POSSIBLE_SIMPLE_RECIPIENTS[r]
46
- elsif r =~ /(.*)::(\d*)/
46
+ elsif r.match? /(.*)::(\d*)/
47
47
  MessageRecipientQuery.friendly_name(r)
48
48
  else
49
49
  "(unknown)"
50
50
  end
51
51
  end
52
- labels.join(', ')
52
+ labels
53
53
  end
54
54
 
55
55
  def delivered?
@@ -100,7 +100,7 @@ class Questionnaire < ApplicationRecord
100
100
  belongs_to :user
101
101
 
102
102
  def email
103
- user.email
103
+ user&.email
104
104
  end
105
105
 
106
106
  def portfolio_url=(value)
@@ -11,15 +11,21 @@ class School < ApplicationRecord
11
11
  def full_name
12
12
  out = ""
13
13
  out << name
14
- if city.present? || state.present?
14
+ if full_location.present?
15
15
  out << " in "
16
- out << city if city.present?
17
- out << ", " if city.present? && state.present?
18
- out << state if state.present?
16
+ out << full_location
19
17
  end
20
18
  out
21
19
  end
22
20
 
21
+ def full_location
22
+ out = ""
23
+ out << city if city.present?
24
+ out << ", " if city.present? && state.present?
25
+ out << state if state.present?
26
+ out
27
+ end
28
+
23
29
  def bus_list
24
30
  return unless bus_list_id
25
31
  BusList.find(bus_list_id)
@@ -41,7 +41,9 @@ class User < ApplicationRecord
41
41
  end
42
42
 
43
43
  def self.from_omniauth(auth)
44
- where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
44
+ matching_provider = where(provider: auth.provider, uid: auth.uid)
45
+ matching_email = where(email: auth.info.email)
46
+ matching_provider.or(matching_email).first_or_create do |user|
45
47
  user.uid = auth.uid
46
48
  user.email = auth.info.email
47
49
  user.password = Devise.friendly_token[0, 20]
@@ -1,11 +1,13 @@
1
- %h3.no-margin Triggered Email Overview
2
- %p
3
- Recipient updates that match a given event will be sent the corresponding email(s) automatically.
4
- %br
5
- For example, when a user moves from the "Accepted" to "RSVP Confirmed" state, the user will be sent an email for the "RSVP Confirmed" event.
6
- %p Hard-coded emails are those managed in the source code repository, and cannot be disabled.
1
+ %h3.pb-2#triggered-email-overview Triggered Email Overview
7
2
 
8
- %table.table
3
+ %table.table.table-striped
4
+ %caption
5
+ %ul
6
+ %li.mb-1
7
+ Recipient updates that match a given event will be sent the corresponding email(s) automatically.
8
+ %br
9
+ For example, when a user moves from the "Accepted" to "RSVP Confirmed" state, the user will be sent an email for the "RSVP Confirmed" event.
10
+ %li Hard-coded emails are those managed in the source code repository, and cannot be disabled.
9
11
  %thead
10
12
  %tr
11
13
  %th Trigger Event
@@ -17,10 +19,10 @@
17
19
  %td= Message::POSSIBLE_TRIGGERS[trigger]
18
20
  - messages = Message.where(trigger: trigger).all
19
21
  %td
20
- = ['questionnaire.pending', 'questionnaire.accepted', 'questionnaire.denied', 'questionnaire.rsvp_confirmed'].include?(trigger) ? '<strong>Yes</strong>'.html_safe : 'No'
22
+ = ['questionnaire.pending', 'questionnaire.accepted', 'questionnaire.denied', 'questionnaire.rsvp_confirmed'].include?(trigger) ? '<strong class="text-success">Yes</strong>'.html_safe : 'No'
21
23
  %td
22
24
  - if messages.present?
23
- %ul.no-margin
25
+ %ul.list-unstyled.mb-0
24
26
  - messages.each do |message|
25
27
  %li= link_to(message.name, manage_message_path(message))
26
28
  - else
@@ -4,6 +4,10 @@
4
4
  %title= yield(:title) || Rails.configuration.hackathon['default_page_title']
5
5
  %meta{ charset: "utf-8" }
6
6
  %meta{ name:"viewport", content: "width=device-width, initial-scale=1" }
7
+
8
+ - if Rails.configuration.hackathon['favicon_asset'].present?
9
+ %link{ href: image_url(Rails.configuration.hackathon['favicon_asset']), rel: "shortcut icon" }
10
+
7
11
  = stylesheet_link_tag "hackathon_manager/core", media: "all"
8
12
  = csrf_meta_tags
9
13
 
@@ -0,0 +1,4 @@
1
+ - if flash[:notice].present?
2
+ .alert.alert-info.mt-3
3
+ %span.fa.fa-info-circle.icon-space-r-half
4
+ = flash[:notice].html_safe
@@ -0,0 +1,8 @@
1
+ .d-flex.justify-content-between.flex-wrap.flex-md-nowrap.align-items-center.pt-3.pb-2.mb-3.border-bottom
2
+ %h1.h2
3
+ = title title
4
+ - if defined?(subtitle) && subtitle.present?
5
+ %small.text-muted= subtitle
6
+
7
+ - unless current_user.admin_limited_access?
8
+ = yield
@@ -1,28 +1,81 @@
1
1
  !!! 5
2
2
  %html
3
3
  %head
4
- %title= yield(:title) || "Manage - #{Rails.configuration.hackathon['name']}"
4
+ %title= yield(:title).presence || "#{Rails.configuration.hackathon['name']} Manager"
5
+ %meta{ content: "width=device-width, initial-scale=1, shrink-to-fit=no", name: "viewport" }/
6
+
7
+ - if Rails.configuration.hackathon['favicon_asset'].present?
8
+ %link{ href: image_url(Rails.configuration.hackathon['favicon_asset']), rel: "shortcut icon" }
9
+
5
10
  = stylesheet_link_tag "hackathon_manager/manage", :media => "all"
6
11
  = csrf_meta_tags
7
12
 
8
13
  %body
9
- = render "layouts/hackathon_manager/header"
10
14
  = javascript_include_tag "hackathon_manager/manage/application"
11
- #main
12
- #manageNav
13
- %p #{Rails.configuration.hackathon['name']} Manager
14
- = link_to "Dashboard", manage_root_path
15
- = link_to "Questionnaires", manage_questionnaires_path
16
- = link_to "Admins", manage_admins_path
17
- = link_to "Messages", manage_messages_path
18
- = link_to "Bus Lists", manage_bus_lists_path
19
- = link_to "Schools", manage_schools_path
20
- = link_to "Stats", manage_stats_path
21
- - unless current_user.admin_limited_access?
22
- \|
23
- = link_to "Config", manage_config_path
24
- = link_to "Sidekiq", sidekiq_web_path
25
- = link_to "Blazer", blazer_path
26
- = render "layouts/hackathon_manager/flashes"
27
- = yield
15
+
16
+ %nav.navbar.navbar-dark.fixed-top.bg-dark.flex-md-nowrap.p-0.shadow
17
+ %a.navbar-brand.col-sm-3.col-md-2.mr-0{href: "#"} #{Rails.configuration.hackathon['name']} Manager
18
+ %input.form-control.form-control-dark.w-100{"aria-label" => "Search", placeholder: "Search", type: "text"}/
19
+ %ul.navbar-nav.px-3
20
+ %li.nav-item.text-nowrap
21
+ = link_to "Sign out", destroy_user_session_path, method: :delete, class: "nav-link"
22
+
23
+ .container-fluid
24
+ .row
25
+ %nav.col-md-2.d-none.d-md-block.bg-light.sidebar
26
+ .sidebar-sticky
27
+ %ul.nav.flex-column
28
+ %li.nav-item
29
+ = active_link_to manage_root_path, class: "nav-link", active_children: false do
30
+ .fa.fa-area-chart.fa-fw.icon-space-r-half
31
+ Dashboard
32
+ %li.nav-item
33
+ = active_link_to manage_questionnaires_path, class: "nav-link" do
34
+ .fa.fa-inbox.fa-fw.icon-space-r-half
35
+ Questionnaires
36
+ %li.nav-item
37
+ = active_link_to manage_admins_path, class: "nav-link" do
38
+ .fa.fa-users.fa-fw.icon-space-r-half
39
+ Admins
40
+ %li.nav-item
41
+ = active_link_to manage_messages_path, class: "nav-link" do
42
+ .fa.fa-bullhorn.fa-fw.icon-space-r-half
43
+ Messages
44
+ %li.nav-item
45
+ = active_link_to manage_bus_lists_path, class: "nav-link" do
46
+ .fa.fa-bus.fa-fw.icon-space-r-half
47
+ Bus Lists
48
+ %li.nav-item
49
+ = active_link_to manage_schools_path, class: "nav-link" do
50
+ .fa.fa-home.fa-fw.icon-space-r-half
51
+ Schools
52
+ %li.nav-item
53
+ = active_link_to manage_stats_path, class: "nav-link" do
54
+ .fa.fa-table.fa-fw.icon-space-r-half
55
+ Stats
56
+ - unless current_user.admin_limited_access?
57
+ %h6.sidebar-heading.d-flex.justify-content-between.align-items-center.px-3.mt-4.mb-1.text-muted
58
+ %span Advanced
59
+ %ul.nav.flex-column.mb-2
60
+ %li.nav-item
61
+ = active_link_to manage_config_path, class: "nav-link" do
62
+ .fa.fa-wrench.fa-fw.icon-space-r-half
63
+ Config
64
+ .nav-item-description View configuration
65
+ %li.nav-item
66
+ = active_link_to sidekiq_web_path, target: '_blank', class: "nav-link" do
67
+ .fa.fa-tasks.fa-fw.icon-space-r-half
68
+ Sidekiq
69
+ %span.fa.fa-external-link.icon-space-l-half
70
+ .nav-item-description Monitor background jobs
71
+ %li.nav-item
72
+ = active_link_to blazer_path, target: '_blank', class: "nav-link" do
73
+ .fa.fa-terminal.fa-fw.icon-space-r-half
74
+ Blazer
75
+ %span.fa.fa-external-link.icon-space-l-half
76
+ .nav-item-description Run custom SQL queries
77
+ %main.col-md-9.ml-sm-auto.col-lg-10.px-4{role: "main"}
78
+ = render "layouts/manage/flashes"
79
+ = yield
80
+
28
81
  = render "layouts/hackathon_manager/shared_footer"