Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Invoke shell scripts via web
user:realm:fb6cb9e166c6c764ff2bdea12175a8aa
production:
sessions:
default:
hosts:
- localhost:27017
database: sinatra-shell
# 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"
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
!!!
%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);
};
#!/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