proxes 0.9.8 → 0.9.9
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.
- checksums.yaml +4 -4
- data/lib/ditty/components/proxes.rb +89 -0
- data/lib/proxes/controllers/permissions.rb +41 -0
- data/lib/proxes/controllers/search.rb +55 -0
- data/lib/proxes/controllers/status.rb +115 -0
- data/lib/proxes/forwarder.rb +49 -0
- data/lib/proxes/helpers/indices.rb +33 -0
- data/lib/proxes/loggers/elasticsearch.rb +10 -0
- data/lib/proxes/middleware/error_handling.rb +64 -0
- data/lib/proxes/middleware/metrics.rb +25 -0
- data/lib/proxes/middleware/security.rb +59 -0
- data/lib/proxes/models/permission.rb +55 -0
- data/lib/proxes/policies/permission_policy.rb +37 -0
- data/lib/proxes/policies/request/bulk_policy.rb +24 -0
- data/lib/proxes/policies/request/cat_policy.rb +12 -0
- data/lib/proxes/policies/request/create_policy.rb +15 -0
- data/lib/proxes/policies/request/index_policy.rb +19 -0
- data/lib/proxes/policies/request/root_policy.rb +13 -0
- data/lib/proxes/policies/request/search_policy.rb +14 -0
- data/lib/proxes/policies/request/snapshot_policy.rb +15 -0
- data/lib/proxes/policies/request/stats_policy.rb +12 -0
- data/lib/proxes/policies/request_policy.rb +62 -0
- data/lib/proxes/policies/search_policy.rb +29 -0
- data/lib/proxes/policies/status_policy.rb +21 -0
- data/lib/proxes/request.rb +84 -0
- data/lib/proxes/request/bulk.rb +40 -0
- data/lib/proxes/request/cat.rb +32 -0
- data/lib/proxes/request/create.rb +33 -0
- data/lib/proxes/request/index.rb +33 -0
- data/lib/proxes/request/root.rb +11 -0
- data/lib/proxes/request/search.rb +37 -0
- data/lib/proxes/request/snapshot.rb +17 -0
- data/lib/proxes/request/stats.rb +35 -0
- data/lib/proxes/services/es.rb +34 -0
- data/lib/proxes/services/listener.rb +29 -0
- data/lib/proxes/services/search.rb +45 -0
- data/lib/proxes/version.rb +5 -0
- data/migrate/20170209_permissions.rb +13 -0
- data/migrate/20170416_user_specific_permissions.rb +9 -0
- data/public/browserconfig.xml +9 -0
- data/public/manifest.json +25 -0
- data/views/index.haml +1 -0
- data/views/layout.haml +60 -0
- metadata +44 -2
| @@ -0,0 +1,40 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'proxes/request'
         | 
| 4 | 
            +
            require 'proxes/policies/request/bulk_policy'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module ProxES
         | 
| 7 | 
            +
              class Request
         | 
| 8 | 
            +
                class Bulk < Request
         | 
| 9 | 
            +
                  attr_reader :index, :type
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  REGEX = /"(index|delete|create|update)".*"_index"\s*:\s*"(.*?)"/
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def bulk_indices
         | 
| 14 | 
            +
                    @bulk_indices ||= begin
         | 
| 15 | 
            +
                      body.read.scan(REGEX).tap { |_r| body.rewind }
         | 
| 16 | 
            +
                    end.map { |e| e[1] }.uniq
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def index=(idx)
         | 
| 20 | 
            +
                    @index = idx
         | 
| 21 | 
            +
                    self.path_info = '/' + [index, type, endpoint].compact
         | 
| 22 | 
            +
                                                                  .map { |v| v.is_a?(Array) ? v.join(',') : v }
         | 
| 23 | 
            +
                                                                  .select { |v| !v.nil? && v != '' }.join('/')
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def endpoint
         | 
| 27 | 
            +
                    '_bulk'
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def parse
         | 
| 31 | 
            +
                    @index ||= check_part(path_parts[0]) unless path_parts[0] == endpoint
         | 
| 32 | 
            +
                    @type  ||= check_part(path_parts[1]) unless path_parts[1] == endpoint
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def indices?
         | 
| 36 | 
            +
                    !@index.nil?
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'proxes/request'
         | 
| 4 | 
            +
            require 'proxes/policies/request/cat_policy'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module ProxES
         | 
| 7 | 
            +
              class Request
         | 
| 8 | 
            +
                class Cat < Request
         | 
| 9 | 
            +
                  attr_reader :index, :type
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def index=(idx)
         | 
| 12 | 
            +
                    @index = idx
         | 
| 13 | 
            +
                    self.path_info = '/' + [endpoint, type, index].compact
         | 
| 14 | 
            +
                                                                  .map { |v| v.is_a?(Array) ? v.join(',') : v }
         | 
| 15 | 
            +
                                                                  .select { |v| !v.nil? && v != '' }.join('/')
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def endpoint
         | 
| 19 | 
            +
                    '_cat'
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def parse
         | 
| 23 | 
            +
                    @type  ||= check_part(path_parts[1])
         | 
| 24 | 
            +
                    @index ||= check_part(path_parts[2])
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def indices?
         | 
| 28 | 
            +
                    %w[shards indices segments count recovery].include? type.first
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'proxes/request'
         | 
| 4 | 
            +
            require 'proxes/policies/request/create_policy'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module ProxES
         | 
| 7 | 
            +
              class Request
         | 
| 8 | 
            +
                class Create < Request
         | 
| 9 | 
            +
                  attr_reader :index, :type, :id
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def index=(idx)
         | 
| 12 | 
            +
                    @index = idx
         | 
| 13 | 
            +
                    self.path_info = '/' + [index, type, id, endpoint].compact
         | 
| 14 | 
            +
                                                                      .map { |v| v.is_a?(Array) ? v.join(',') : v }
         | 
| 15 | 
            +
                                                                      .select { |v| !v.nil? && v != '' }.join('/')
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def endpoint
         | 
| 19 | 
            +
                    '_create'
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def parse
         | 
| 23 | 
            +
                    @index ||= check_part(path_parts[0])
         | 
| 24 | 
            +
                    @type ||= check_part(path_parts[1])
         | 
| 25 | 
            +
                    @id ||= check_part(path_parts[2])
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def indices?
         | 
| 29 | 
            +
                    true
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'proxes/request'
         | 
| 4 | 
            +
            require 'proxes/policies/request/index_policy'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module ProxES
         | 
| 7 | 
            +
              class Request
         | 
| 8 | 
            +
                class Index < Request
         | 
| 9 | 
            +
                  attr_reader :index, :type, :id
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def index=(idx)
         | 
| 12 | 
            +
                    @index = idx
         | 
| 13 | 
            +
                    self.path_info = '/' + [index, type, id].compact
         | 
| 14 | 
            +
                                                            .map { |v| v.is_a?(Array) ? v.join(',') : v }
         | 
| 15 | 
            +
                                                            .select { |v| !v.nil? && v != '' }.join('/')
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def endpoint
         | 
| 19 | 
            +
                    nil
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def parse
         | 
| 23 | 
            +
                    @index ||= check_part(path_parts[0])
         | 
| 24 | 
            +
                    @type ||= check_part(path_parts[1])
         | 
| 25 | 
            +
                    @id ||= check_part(path_parts[2])
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def indices?
         | 
| 29 | 
            +
                    true
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'proxes/request'
         | 
| 4 | 
            +
            require 'proxes/policies/request/search_policy'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module ProxES
         | 
| 7 | 
            +
              class Request
         | 
| 8 | 
            +
                class Search < Request
         | 
| 9 | 
            +
                  attr_reader :index, :type
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def index=(idx)
         | 
| 12 | 
            +
                    @index = idx
         | 
| 13 | 
            +
                    self.path_info = '/' + [index, type, id, endpoint].compact
         | 
| 14 | 
            +
                                                                      .map { |v| v.is_a?(Array) ? v.join(',') : v }
         | 
| 15 | 
            +
                                                                      .select { |v| !v.nil? && v != '' }.join('/')
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def endpoint
         | 
| 19 | 
            +
                    '_search'
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def parse
         | 
| 23 | 
            +
                    @index ||= check_part(path_parts[0]) unless path_parts[0] == endpoint
         | 
| 24 | 
            +
                    @type  ||= check_part(path_parts[1]) unless path_parts[1] == endpoint
         | 
| 25 | 
            +
                    @id    ||= check_part(path_parts[2]) unless path_parts[2] == endpoint
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def id
         | 
| 29 | 
            +
                    @id == [] ? nil : @id
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def indices?
         | 
| 33 | 
            +
                    type != ['scroll']
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'proxes/request'
         | 
| 4 | 
            +
            require 'proxes/policies/request/snapshot_policy'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module ProxES
         | 
| 7 | 
            +
              class Request
         | 
| 8 | 
            +
                class Snapshot < Request
         | 
| 9 | 
            +
                  attr_reader :repository
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def parse
         | 
| 12 | 
            +
                    @repository ||= check_part(path_parts[1])
         | 
| 13 | 
            +
                    @repository = [] if repository.nil?
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'proxes/request'
         | 
| 4 | 
            +
            require 'proxes/policies/request/stats_policy'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module ProxES
         | 
| 7 | 
            +
              class Request
         | 
| 8 | 
            +
                class Stats < Request
         | 
| 9 | 
            +
                  attr_reader :index
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def index=(idx)
         | 
| 12 | 
            +
                    @index = idx
         | 
| 13 | 
            +
                    self.path_info = '/' + [index, endpoint].compact
         | 
| 14 | 
            +
                                                            .map { |v| v.is_a?(Array) ? v.join(',') : v }
         | 
| 15 | 
            +
                                                            .select { |v| !v.nil? && v != '' }.join('/')
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def endpoint
         | 
| 19 | 
            +
                    '_stats'
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def parse
         | 
| 23 | 
            +
                    @index ||= check_part(path_parts[0])
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def stats
         | 
| 27 | 
            +
                    @stats ||= check_part(path_parts[2])
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def indices?
         | 
| 31 | 
            +
                    true
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'openssl'
         | 
| 4 | 
            +
            require 'elasticsearch'
         | 
| 5 | 
            +
            require 'ditty/services/logger'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module ProxES
         | 
| 8 | 
            +
              module Services
         | 
| 9 | 
            +
                module ES
         | 
| 10 | 
            +
                  def client
         | 
| 11 | 
            +
                    @client ||= Elasticsearch::Client.new(
         | 
| 12 | 
            +
                      url: ENV['ELASTICSEARCH_URL'],
         | 
| 13 | 
            +
                      transport_options: {
         | 
| 14 | 
            +
                        ssl: {
         | 
| 15 | 
            +
                          verify: ENV['SSL_VERIFY_NONE'].to_i != 1,
         | 
| 16 | 
            +
                          cert_store: ssl_store
         | 
| 17 | 
            +
                        }
         | 
| 18 | 
            +
                      },
         | 
| 19 | 
            +
                      logger: Ditty::Services::Logger.instance
         | 
| 20 | 
            +
                    )
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def ssl_store
         | 
| 24 | 
            +
                    store = OpenSSL::X509::Store.new
         | 
| 25 | 
            +
                    store.set_default_paths
         | 
| 26 | 
            +
                    store
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def conn
         | 
| 30 | 
            +
                    client.transport.connections.get_connection.connection
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'wisper'
         | 
| 4 | 
            +
            require 'ditty/models/audit_log'
         | 
| 5 | 
            +
            require 'ditty/services/logger'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module ProxES
         | 
| 8 | 
            +
              class Listener
         | 
| 9 | 
            +
                def es_request_failed(request, response)
         | 
| 10 | 
            +
                  Ditty::AuditLog.create(
         | 
| 11 | 
            +
                    action: :es_request_failed,
         | 
| 12 | 
            +
                    user: request.user,
         | 
| 13 | 
            +
                    details: "#{request.detail} > #{response[0]}"
         | 
| 14 | 
            +
                  )
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def es_request_denied(request, exception = nil)
         | 
| 18 | 
            +
                  detail = request.detail
         | 
| 19 | 
            +
                  detail = "#{detail} - #{exception.class}" if exception
         | 
| 20 | 
            +
                  Ditty::AuditLog.create(
         | 
| 21 | 
            +
                    action: :es_request_denied,
         | 
| 22 | 
            +
                    user: request.user,
         | 
| 23 | 
            +
                    details: detail
         | 
| 24 | 
            +
                  )
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            Wisper.subscribe(ProxES::Listener.new) unless ENV['RACK_ENV'] == 'test'
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            require 'proxes/services/es'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # TODO: This needs to be filtered.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module ProxES
         | 
| 6 | 
            +
              module Services
         | 
| 7 | 
            +
                module Search
         | 
| 8 | 
            +
                  class << self
         | 
| 9 | 
            +
                    include ES
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    def indices
         | 
| 12 | 
            +
                      client.indices.get_mapping(index: '_all').keys
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    def fields(index: '_all', names_only: false)
         | 
| 16 | 
            +
                      fields = {}
         | 
| 17 | 
            +
                      client.indices.get_mapping(index: index).each do |_idx, index_map|
         | 
| 18 | 
            +
                        index_map['mappings'].each do |_type, type_map|
         | 
| 19 | 
            +
                          next if type_map['properties'].nil?
         | 
| 20 | 
            +
                          type_map['properties'].each do |name, details|
         | 
| 21 | 
            +
                            if details['type'] != 'keyword' && details['fields'] && (names_only == false)
         | 
| 22 | 
            +
                              keyword = details['fields'].find do |v|
         | 
| 23 | 
            +
                                v[1]['type'] == 'keyword'
         | 
| 24 | 
            +
                              end
         | 
| 25 | 
            +
                              fields["#{name}.#{keyword[0]}"] ||= keyword[1]['type'] if keyword
         | 
| 26 | 
            +
                            end
         | 
| 27 | 
            +
                            fields[name] ||= details['type'] unless details['type'].nil?
         | 
| 28 | 
            +
                          end
         | 
| 29 | 
            +
                        end.to_h
         | 
| 30 | 
            +
                      end
         | 
| 31 | 
            +
                      fields
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    def values(field, index = '_all', size = 25)
         | 
| 35 | 
            +
                      result = client.search index: index, body: { size: 0, aggs: { values: { terms: { field: field, size: size } } } }
         | 
| 36 | 
            +
                      result['aggregations']['values']['buckets'].map { |e| e['key'] }
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    def search(qs, options = {})
         | 
| 40 | 
            +
                      client.search options.merge(q: qs) # , explain: true
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            {
         | 
| 2 | 
            +
              "short_name": "ProxES",
         | 
| 3 | 
            +
              "name": "ProxES",
         | 
| 4 | 
            +
              "icons": [
         | 
| 5 | 
            +
                {
         | 
| 6 | 
            +
                  "src": "_proxes/images/launcher-icon-1x.png",
         | 
| 7 | 
            +
                  "type": "image/png",
         | 
| 8 | 
            +
                  "sizes": "48x48"
         | 
| 9 | 
            +
                },
         | 
| 10 | 
            +
                {
         | 
| 11 | 
            +
                  "src": "_proxes/images/launcher-icon-2x.png",
         | 
| 12 | 
            +
                  "type": "image/png",
         | 
| 13 | 
            +
                  "sizes": "96x96"
         | 
| 14 | 
            +
                },
         | 
| 15 | 
            +
                {
         | 
| 16 | 
            +
                  "src": "_proxes/images/launcher-icon-4x.png",
         | 
| 17 | 
            +
                  "type": "image/png",
         | 
| 18 | 
            +
                  "sizes": "192x192"
         | 
| 19 | 
            +
                }
         | 
| 20 | 
            +
              ],
         | 
| 21 | 
            +
              "start_url": "_proxes/auth/login",
         | 
| 22 | 
            +
              "theme_color": "#bebebe",
         | 
| 23 | 
            +
              "background_color": "#bebebe",
         | 
| 24 | 
            +
              "display": "fullscreen"
         | 
| 25 | 
            +
            }
         | 
    
        data/views/index.haml
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            #react-dashboard{ 'data-elasticsearch-url' => '..'}
         | 
    
        data/views/layout.haml
    ADDED
    
    | @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            !!! 5
         | 
| 2 | 
            +
            %html{ lang: 'en' }
         | 
| 3 | 
            +
              %head
         | 
| 4 | 
            +
                %meta{ charset: 'utf-8' }
         | 
| 5 | 
            +
                %meta{ 'http-equiv' => 'X-UA-Compatible', 'content' => 'IE=edge,chrome=1' }
         | 
| 6 | 
            +
                %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
         | 
| 7 | 
            +
                %meta{ name: 'theme-color', content: '#ffffff' }
         | 
| 8 | 
            +
                %link{ rel: 'manifest', href: '/_proxes/manifest.json' }
         | 
| 9 | 
            +
                %link{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '/_proxes/images/favicon-32x32.png' }
         | 
| 10 | 
            +
                %link{ rel: 'icon', type: 'image/png', sizes: '16x16', href: '/_proxes/images/favicon-16x16.png' }
         | 
| 11 | 
            +
                %link{ rel: 'apple-touch-icon', sizes: '76x76', href: '/_proxes/images/apple-icon.png' }
         | 
| 12 | 
            +
                %link{ rel: 'mask-icon', href: '/_proxes/images/safari-pinned-tab.svg', color: '#5bbad5' }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                %title
         | 
| 15 | 
            +
                  ProxES
         | 
| 16 | 
            +
                  - if defined? title
         | 
| 17 | 
            +
                    = "- #{title}"
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                %meta{ name: 'description', content: '' }
         | 
| 20 | 
            +
                %meta{ name: 'author', content: '' }
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                / Le styles
         | 
| 23 | 
            +
                %link{ rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', media: 'screen' }
         | 
| 24 | 
            +
                %link{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/startbootstrap-sb-admin-2/3.3.7+1/css/sb-admin-2.min.css', media: 'screen' }
         | 
| 25 | 
            +
                %link{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/metisMenu/2.5.2/metisMenu.min.css', media: 'screen' }
         | 
| 26 | 
            +
                %link{ rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css', media: 'screen' }
         | 
| 27 | 
            +
                /[if lt IE 9] <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
         | 
| 28 | 
            +
                /[if lt IE 9] <script src="https://cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js"></script>
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js' }
         | 
| 31 | 
            +
                %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/startbootstrap-sb-admin-2/3.3.7+1/js/sb-admin-2.min.js' }
         | 
| 32 | 
            +
                %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/metisMenu/2.5.2/metisMenu.min.js' }
         | 
| 33 | 
            +
              %body
         | 
| 34 | 
            +
                #wrapper
         | 
| 35 | 
            +
                  = haml :'partials/navbar', locals: { title: (defined?(title) ? title : 'ProxES') }
         | 
| 36 | 
            +
                  #page-wrapper{ style: 'opacity: 0.8' }
         | 
| 37 | 
            +
                    .row
         | 
| 38 | 
            +
                      .col-md-12
         | 
| 39 | 
            +
                        -if defined? title
         | 
| 40 | 
            +
                          %h1.page-header= title
         | 
| 41 | 
            +
                        = haml :'partials/notifications'
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    = yield
         | 
| 44 | 
            +
                    %footer.footer.text-muted.text-center
         | 
| 45 | 
            +
                      %hr
         | 
| 46 | 
            +
                      .copyright
         | 
| 47 | 
            +
                        :plain
         | 
| 48 | 
            +
                          © <script>document.write(new Date().getFullYear())</script>, DataTools.io
         | 
| 49 | 
            +
             | 
| 50 | 
            +
             | 
| 51 | 
            +
                / Placed at the end of the document so the pages load faster
         | 
| 52 | 
            +
                %script{ type: 'text/javascript', src: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' }
         | 
| 53 | 
            +
                %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.min.js' }
         | 
| 54 | 
            +
                %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.min.js' }
         | 
| 55 | 
            +
                %script{ type: 'text/javascript', src: '/_proxes/js/bundle.js' }
         | 
| 56 | 
            +
                :javascript
         | 
| 57 | 
            +
                    $(function() {
         | 
| 58 | 
            +
                        $('.sidebar-nav').metisMenu();
         | 
| 59 | 
            +
                    });
         | 
| 60 | 
            +
             |