Last active
          October 29, 2015 21:08 
        
      - 
      
- 
        Save jbgutierrez/584377ff26a33a73d0e9 to your computer and use it in GitHub Desktop. 
    Invoke shell scripts via web
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | user:realm:fb6cb9e166c6c764ff2bdea12175a8aa | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | production: | |
| sessions: | |
| default: | |
| hosts: | |
| - localhost:27017 | |
| database: sinatra-shell | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | # A sample Gemfile | |
| source "https://rubygems.org" | |
| gem "thin", '<2.0.0' | |
| gem "sinatra" | |
| gem "sinatra-contrib" | |
| gem "sinatra-websocket" | |
| gem "haml" | |
| gem "activesupport" | |
| gem "mongoid" | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | GEM | |
| remote: https://rubygems.org/ | |
| specs: | |
| activemodel (4.2.4) | |
| activesupport (= 4.2.4) | |
| builder (~> 3.1) | |
| activesupport (4.2.4) | |
| i18n (~> 0.7) | |
| json (~> 1.7, >= 1.7.7) | |
| minitest (~> 5.1) | |
| thread_safe (~> 0.3, >= 0.3.4) | |
| tzinfo (~> 1.1) | |
| addressable (2.3.8) | |
| backports (3.6.4) | |
| bson (3.1.2) | |
| builder (3.2.2) | |
| connection_pool (2.2.0) | |
| daemons (1.2.3) | |
| em-websocket (0.3.8) | |
| addressable (>= 2.1.1) | |
| eventmachine (>= 0.12.9) | |
| eventmachine (1.0.8) | |
| haml (4.0.6) | |
| tilt | |
| i18n (0.7.0) | |
| json (1.8.3) | |
| minitest (5.8.0) | |
| mongoid (4.0.2) | |
| activemodel (~> 4.0) | |
| moped (~> 2.0.0) | |
| origin (~> 2.1) | |
| tzinfo (>= 0.3.37) | |
| moped (2.0.6) | |
| bson (~> 3.0) | |
| connection_pool (~> 2.0) | |
| optionable (~> 0.2.0) | |
| multi_json (1.11.2) | |
| optionable (0.2.0) | |
| origin (2.1.1) | |
| rack (1.6.4) | |
| rack-protection (1.5.3) | |
| rack | |
| rack-test (0.6.3) | |
| rack (>= 1.0) | |
| sinatra (1.4.6) | |
| rack (~> 1.4) | |
| rack-protection (~> 1.4) | |
| tilt (>= 1.3, < 3) | |
| sinatra-contrib (1.4.2) | |
| backports (>= 2.0) | |
| multi_json | |
| rack-protection | |
| rack-test | |
| sinatra (~> 1.4.0) | |
| tilt (~> 1.3) | |
| sinatra-websocket (0.3.1) | |
| em-websocket (~> 0.3.6) | |
| eventmachine | |
| thin (>= 1.3.1, < 2.0.0) | |
| thin (1.6.3) | |
| daemons (~> 1.0, >= 1.0.9) | |
| eventmachine (~> 1.0) | |
| rack (~> 1.0) | |
| thread_safe (0.3.5) | |
| tilt (1.4.1) | |
| tzinfo (1.2.2) | |
| thread_safe (~> 0.1) | |
| PLATFORMS | |
| ruby | |
| DEPENDENCIES | |
| activesupport | |
| haml | |
| mongoid | |
| sinatra | |
| sinatra-contrib | |
| sinatra-websocket | |
| thin (< 2.0.0) | |
| BUNDLED WITH | |
| 1.10.0.rc | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | !!! | |
| %html.no-js{:lang => "es"} | |
| %head | |
| %meta{:charset => "utf-8"}/ | |
| %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}/ | |
| %title sinatra-shell | |
| %meta{:content => "width=device-width, initial-scale=1", :name => "viewport"}/ | |
| %link{:href => "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.css", :rel => "stylesheet"}/ | |
| :css | |
| #content { | |
| margin: 0 auto; | |
| float: none; | |
| } | |
| .commands { | |
| padding: 40px; | |
| } | |
| th { | |
| text-align: center; | |
| } | |
| tr { | |
| cursor: pointer; | |
| } | |
| %body | |
| .container.text-center | |
| #content.row.col-xs-9 | |
| %form.form-inline.commands{method: :post} | |
| - cmds.each do |cmd, _| | |
| %input.btn.btn-default.btn-lg{name: "cmd", type: "submit", value: cmd} | |
| %table.table.table-hover | |
| %tr | |
| %th | |
| %th cmd | |
| %th status | |
| %th duration | |
| %th date | |
| %th user | |
| - if jobs.length > 0 | |
| - jobs.each_with_index do |job, idx| | |
| %tr.job{:id => "job-#{job.id}", :"data-job" => job.to_json } | |
| %td= jobs.length - idx | |
| %td= job.cmd | |
| %td.status= job.status | |
| %td.total= job.total | |
| %td= job.created_at | |
| %td= job.user | |
| - else | |
| %tr | |
| %td{colspan: 6, style: 'font-style: italic;'} None | |
| .modal.fade | |
| .modal-dialog | |
| .modal-content | |
| .modal-header | |
| %button.close{"aria-label" => "Close", "data-dismiss" => "modal", :type => "button"} | |
| %span{"aria-hidden" => "true"} × | |
| %h4.modal-title Output | |
| .modal-body | |
| %pre | |
| .modal-footer | |
| %button.btn.btn-default{"data-dismiss" => "modal", :type => "button"} Close | |
| %script{:src => "//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"} | |
| %script{:src => "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.js"} | |
| :javascript | |
| var classes = { | |
| running: "text-warning", | |
| scheduled: "text-active", | |
| 0: "text-success", | |
| error: "text-danger" | |
| }; | |
| function setClass(){ | |
| var $job = $(this); | |
| for (var name in classes) { | |
| $job.removeClass(classes[name]); | |
| } | |
| var job = $job.data('job'); | |
| if (job.status != "") { | |
| $job.addClass(classes[String(job.status)] || classes.error); | |
| } | |
| } | |
| $('.job').each(setClass); | |
| $('.job').on('click', function() { | |
| var $modal = $('.modal'), | |
| $el = $(this); | |
| $modal.find('.modal-body pre').html($el.data('output')); | |
| $modal.modal(); | |
| }); | |
| var ws = new WebSocket('ws://' + window.location.host); | |
| ws.onmessage = function(message) { | |
| var job = JSON.parse(message.data); | |
| var $job = $("#job-" + job._id.$oid); | |
| $job.data('job', job); | |
| $job.find('.status').html(job.status); | |
| $job.find('.total').html(job.total); | |
| setClass.apply($job); | |
| }; | 
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | #!/usr/bin/env ruby | |
| # coding: UTF-8 | |
| require 'bundler/setup' | |
| require "sinatra/base" | |
| require 'sinatra/contrib' | |
| require 'sinatra-websocket' | |
| require 'thread' | |
| require "haml" | |
| require "json" | |
| require 'mongoid' | |
| require "benchmark" | |
| require "active_support" | |
| require "csv" | |
| Mongoid.load! 'database.yml', :production | |
| class Job | |
| include Mongoid::Document | |
| include Mongoid::Attributes::Dynamic | |
| include Mongoid::Timestamps | |
| def run | |
| this = self | |
| times = Benchmark.measure { this.output = `#{this.cmd}` } | |
| self.status = $?.exitstatus.to_s | |
| self.total = times.real.to_s | |
| save! | |
| end | |
| end | |
| $sockets = [] | |
| $jobs = [] | |
| Thread.new do | |
| while true do | |
| sleep 2 | |
| next unless job = $jobs.pop | |
| job.status = "running" | |
| job.save | |
| $sockets.each{|s| s.send job.to_json } | |
| job.run | |
| $sockets.each{|s| s.send job.to_json } | |
| end | |
| end | |
| CMDS = { | |
| long: 'sleep 3', | |
| error: 'sync-files static/common', | |
| tree: 'tree' | |
| } | |
| PWD = File.dirname(__FILE__) | |
| class Server < Sinatra::Application | |
| register Sinatra::Contrib::All | |
| set :public_folder, PWD | |
| set :views, PWD | |
| set :haml, format: :html5 | |
| set :port, ENV['PORT'] || 4567 | |
| set :server, 'thin' | |
| def self.new(*) | |
| users = {} | |
| CSV.foreach('.htdigest', col_sep: ':') { |row| users[row[0]] = row[2] } | |
| options = { | |
| realm: 'realm', | |
| opaque: '', | |
| passwords_hashed: true | |
| } | |
| Rack::Auth::Digest::MD5.new(super, options) { |username| users[username] } | |
| end | |
| get '/' do | |
| if !request.websocket? | |
| haml :index, locals: { | |
| jobs: Job.where(:created_at.gt => 1.week.ago).order_by(created_at: 'desc'), | |
| cmds: CMDS.keys | |
| } | |
| else | |
| request.websocket do |ws| | |
| ws.onopen { $sockets << ws } | |
| ws.onclose { $sockets.delete ws } | |
| end | |
| end | |
| end | |
| post '/' do | |
| if cmd = CMDS[params[:cmd].to_sym] | |
| job = Job.create cmd: cmd, output: 'no output', total: 0, status: 'scheduled', user: request.env['REMOTE_USER'] | |
| $jobs << job | |
| redirect '/' | |
| else | |
| status 403 | |
| "Comando no permitido" | |
| end | |
| end | |
| run! | |
| end | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment