From c63c8f1698e8c059e286fb49ba0264e4336a6e33 Mon Sep 17 00:00:00 2001 From: Daniel Beauchamp Date: Tue, 24 Jul 2012 15:25:22 -0400 Subject: Initial version. 0.1.0 Release --- lib/allthethings.rb | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 lib/allthethings.rb (limited to 'lib') diff --git a/lib/allthethings.rb b/lib/allthethings.rb new file mode 100644 index 0000000..cf4a62a --- /dev/null +++ b/lib/allthethings.rb @@ -0,0 +1,119 @@ +require 'sinatra' +require 'sinatra/content_for' +require 'rufus/scheduler' +require 'coffee-script' +require 'sass' +require 'json' + +Dir[File.join(Dir.pwd, 'lib/**/*.rb')].each {|file| require file } + +SCHEDULER = Rufus::Scheduler.start_new + +set server: 'thin', connections: [], history: {} +helpers Sinatra::ContentFor + +def configure(&block) + set :public_folder, Dir.pwd + '/public' + set :views, Dir.pwd + '/dashboards' + set :default_dashboard, nil + instance_eval(&block) +end + +get '/events', provides: 'text/event-stream' do + stream :keep_open do |out| + settings.connections << out + out << latest_events + out.callback { settings.connections.delete(out) } + end +end + +get '/' do + begin + redirect "/" + (settings.default_dashboard || first_dashboard).to_s + rescue NoMethodError => e + raise Exception.new("There are no dashboards in your dashboard directory.") + end +end + +get '/:dashboard' do + erb params[:dashboard].to_sym +end + +get '/views/:widget?.html' do + widget = params[:widget] + send_file File.join(Dir.pwd, "widgets/#{widget}/#{widget}.html") +end + +post '/widgets/:id' do + request.body.rewind + body = JSON.parse(request.body.read) + auth_token = body.delete("auth_token") + if auth_token == settings.auth_token + send_event(params['id'], body) + 204 # response without entity body + else + status 401 + "Invalid API key\n" + end +end + +def framework_javascripts + ['jquery.js', 'es5-shim.js', 'batman.js', 'batman.jquery.js', 'application.coffee', 'widget.coffee'].collect do |f| + File.join(File.expand_path("../../vendor/javascripts", __FILE__), f) + end +end + +def widget_javascripts + asset_paths("/widgets/**/*.coffee") +end + +def javascripts + (framework_javascripts + widget_javascripts).collect do |f| + if File.extname(f) == ".coffee" + begin + CoffeeScript.compile(File.read(f)) + rescue ExecJS::ProgramError => e + message = e.message + ": in #{f}" + raise ExecJS::ProgramError.new(message) + end + else + File.read(f) + end + end.join("\n") +end + +def stylesheets + asset_paths("/public/**/*.scss", "/widgets/**/*.scss").collect do |f| + Sass.compile File.read(f) + end.join("\n") +end + +def asset_paths(*paths) + paths.inject([]) { |arr, path| arr + Dir[File.join(Dir.pwd, path)] } +end + +def send_event(id, body) + body["id"] = id + event = format_event(JSON.unparse(body)) + settings.history[id] = event + settings.connections.each { |out| out << event } +end + +def format_event(body) + "data: #{body}\n\n" +end + +def latest_events + settings.history.inject("") do |str, (id, body)| + str << body + end +end + +def first_dashboard + files = Dir[settings.views + "/*.erb"].collect { |f| f.match(/(\w*).erb/)[1] } + files -= ['layout'] + files.first +end + +files = Dir[Dir.pwd + '/jobs/*.rb'] +files.each { |job| require(job) } \ No newline at end of file -- cgit v1.2.3 From 1dcf732b801f9fa513901fda27388a36eddb44ab Mon Sep 17 00:00:00 2001 From: Daniel Beauchamp Date: Tue, 31 Jul 2012 01:42:38 -0400 Subject: Added support for authentication. --- lib/allthethings.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'lib') diff --git a/lib/allthethings.rb b/lib/allthethings.rb index cf4a62a..904a991 100644 --- a/lib/allthethings.rb +++ b/lib/allthethings.rb @@ -19,7 +19,14 @@ def configure(&block) instance_eval(&block) end +helpers do + def protected! + # override with auth logic + end +end + get '/events', provides: 'text/event-stream' do + protected! stream :keep_open do |out| settings.connections << out out << latest_events @@ -36,15 +43,18 @@ get '/' do end get '/:dashboard' do + protected! erb params[:dashboard].to_sym end get '/views/:widget?.html' do + protected! widget = params[:widget] send_file File.join(Dir.pwd, "widgets/#{widget}/#{widget}.html") end post '/widgets/:id' do + protected! request.body.rewind body = JSON.parse(request.body.read) auth_token = body.delete("auth_token") -- cgit v1.2.3 From cdd8ff258582f5eba7e3941a5a18007e7aabbbfa Mon Sep 17 00:00:00 2001 From: Daniel Beauchamp Date: Thu, 2 Aug 2012 13:38:19 -0400 Subject: Better generators, sample widgets, and more! --- lib/allthethings.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/allthethings.rb b/lib/allthethings.rb index 904a991..8fbef07 100644 --- a/lib/allthethings.rb +++ b/lib/allthethings.rb @@ -54,7 +54,6 @@ get '/views/:widget?.html' do end post '/widgets/:id' do - protected! request.body.rewind body = JSON.parse(request.body.read) auth_token = body.delete("auth_token") @@ -125,5 +124,6 @@ def first_dashboard files.first end -files = Dir[Dir.pwd + '/jobs/*.rb'] +job_path = ENV["JOB_PATH"] || 'jobs' +files = Dir[Dir.pwd + "/#{job_path}/*.rb"] files.each { |job| require(job) } \ No newline at end of file -- cgit v1.2.3 From 780fe49f715c2fced88e958b02541bf8e7dca934 Mon Sep 17 00:00:00 2001 From: Daniel Beauchamp Date: Wed, 8 Aug 2012 18:02:56 -0400 Subject: Rename project to 'Dashing', and do some other cleanups --- lib/allthethings.rb | 129 ---------------------------------------------------- lib/dashing.rb | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 129 deletions(-) delete mode 100644 lib/allthethings.rb create mode 100644 lib/dashing.rb (limited to 'lib') diff --git a/lib/allthethings.rb b/lib/allthethings.rb deleted file mode 100644 index 8fbef07..0000000 --- a/lib/allthethings.rb +++ /dev/null @@ -1,129 +0,0 @@ -require 'sinatra' -require 'sinatra/content_for' -require 'rufus/scheduler' -require 'coffee-script' -require 'sass' -require 'json' - -Dir[File.join(Dir.pwd, 'lib/**/*.rb')].each {|file| require file } - -SCHEDULER = Rufus::Scheduler.start_new - -set server: 'thin', connections: [], history: {} -helpers Sinatra::ContentFor - -def configure(&block) - set :public_folder, Dir.pwd + '/public' - set :views, Dir.pwd + '/dashboards' - set :default_dashboard, nil - instance_eval(&block) -end - -helpers do - def protected! - # override with auth logic - end -end - -get '/events', provides: 'text/event-stream' do - protected! - stream :keep_open do |out| - settings.connections << out - out << latest_events - out.callback { settings.connections.delete(out) } - end -end - -get '/' do - begin - redirect "/" + (settings.default_dashboard || first_dashboard).to_s - rescue NoMethodError => e - raise Exception.new("There are no dashboards in your dashboard directory.") - end -end - -get '/:dashboard' do - protected! - erb params[:dashboard].to_sym -end - -get '/views/:widget?.html' do - protected! - widget = params[:widget] - send_file File.join(Dir.pwd, "widgets/#{widget}/#{widget}.html") -end - -post '/widgets/:id' do - request.body.rewind - body = JSON.parse(request.body.read) - auth_token = body.delete("auth_token") - if auth_token == settings.auth_token - send_event(params['id'], body) - 204 # response without entity body - else - status 401 - "Invalid API key\n" - end -end - -def framework_javascripts - ['jquery.js', 'es5-shim.js', 'batman.js', 'batman.jquery.js', 'application.coffee', 'widget.coffee'].collect do |f| - File.join(File.expand_path("../../vendor/javascripts", __FILE__), f) - end -end - -def widget_javascripts - asset_paths("/widgets/**/*.coffee") -end - -def javascripts - (framework_javascripts + widget_javascripts).collect do |f| - if File.extname(f) == ".coffee" - begin - CoffeeScript.compile(File.read(f)) - rescue ExecJS::ProgramError => e - message = e.message + ": in #{f}" - raise ExecJS::ProgramError.new(message) - end - else - File.read(f) - end - end.join("\n") -end - -def stylesheets - asset_paths("/public/**/*.scss", "/widgets/**/*.scss").collect do |f| - Sass.compile File.read(f) - end.join("\n") -end - -def asset_paths(*paths) - paths.inject([]) { |arr, path| arr + Dir[File.join(Dir.pwd, path)] } -end - -def send_event(id, body) - body["id"] = id - event = format_event(JSON.unparse(body)) - settings.history[id] = event - settings.connections.each { |out| out << event } -end - -def format_event(body) - "data: #{body}\n\n" -end - -def latest_events - settings.history.inject("") do |str, (id, body)| - str << body - end -end - -def first_dashboard - files = Dir[settings.views + "/*.erb"].collect { |f| f.match(/(\w*).erb/)[1] } - files -= ['layout'] - files.first -end - -job_path = ENV["JOB_PATH"] || 'jobs' -files = Dir[Dir.pwd + "/#{job_path}/*.rb"] -files.each { |job| require(job) } \ No newline at end of file diff --git a/lib/dashing.rb b/lib/dashing.rb new file mode 100644 index 0000000..8b2af2f --- /dev/null +++ b/lib/dashing.rb @@ -0,0 +1,126 @@ +require 'sinatra' +require 'sinatra/content_for' +require 'rufus/scheduler' +require 'coffee-script' +require 'sass' +require 'json' + +SCHEDULER = Rufus::Scheduler.start_new + +set server: 'thin', connections: [], history: {} +set :public_folder, File.join(Dir.pwd, 'public') +set :views, File.join(Dir.pwd, 'dashboards') +set :default_dashboard, nil +set :auth_token, nil + +helpers Sinatra::ContentFor +helpers do + def protected! + # override with auth logic + end +end + +get '/events', provides: 'text/event-stream' do + protected! + stream :keep_open do |out| + settings.connections << out + out << latest_events + out.callback { settings.connections.delete(out) } + end +end + +get '/' do + begin + redirect "/" + (settings.default_dashboard || first_dashboard).to_s + rescue NoMethodError => e + raise Exception.new("There are no dashboards in your dashboard directory.") + end +end + +get '/:dashboard' do + protected! + erb params[:dashboard].to_sym +end + +get '/views/:widget?.html' do + protected! + widget = params[:widget] + send_file File.join(Dir.pwd, 'widgets', widget, "#{widget}.html") +end + +post '/widgets/:id' do + request.body.rewind + body = JSON.parse(request.body.read) + auth_token = body.delete("auth_token") + if !settings.auth_token || settings.auth_token == auth_token + send_event(params['id'], body) + 204 # response without entity body + else + status 401 + "Invalid API key\n" + end +end + +def framework_javascripts + ['jquery.js', 'es5-shim.js', 'batman.js', 'batman.jquery.js', 'application.coffee', 'widget.coffee'].collect do |f| + File.join(File.expand_path('../../vendor/javascripts', __FILE__), f) + end +end + +def widget_javascripts + asset_paths("/widgets/**/*.coffee") +end + +def javascripts + (framework_javascripts + widget_javascripts).collect do |f| + if File.extname(f) == ".coffee" + begin + CoffeeScript.compile(File.read(f)) + rescue ExecJS::ProgramError => e + message = e.message + ": in #{f}" + raise ExecJS::ProgramError.new(message) + end + else + File.read(f) + end + end.join("\n") +end + +def stylesheets + asset_paths("/public/**/*.scss", "/widgets/**/*.scss").collect do |f| + Sass.compile File.read(f) + end.join("\n") +end + +def asset_paths(*paths) + paths.inject([]) { |arr, path| arr + Dir[File.join(Dir.pwd, path)] } +end + +def send_event(id, body) + body["id"] = id + event = format_event(JSON.unparse(body)) + settings.history[id] = event + settings.connections.each { |out| out << event } +end + +def format_event(body) + "data: #{body}\n\n" +end + +def latest_events + settings.history.inject("") do |str, (id, body)| + str << body + end +end + +def first_dashboard + files = Dir[File.join(settings.views, '*.erb')].collect { |f| f.match(/(\w*).erb/)[1] } + files -= ['layout'] + files.first +end + +Dir[File.join(Dir.pwd, 'lib', '**', '*.rb')].each {|file| require file } + +job_path = ENV["JOB_PATH"] || 'jobs' +files = Dir[File.join(Dir.pwd, job_path, '/*.rb')] +files.each { |job| require(job) } \ No newline at end of file -- cgit v1.2.3 From 16f51fd9dc454b26d9866b080caa17ffe2f8ce27 Mon Sep 17 00:00:00 2001 From: Daniel Beauchamp Date: Mon, 13 Aug 2012 23:36:38 -0400 Subject: Updated to use sprockets! Ah, much cleaner. --- lib/dashing.rb | 57 ++++++++++++++++++++------------------------------------- 1 file changed, 20 insertions(+), 37 deletions(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 8b2af2f..ee7cd1f 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -1,4 +1,5 @@ require 'sinatra' +require 'sprockets' require 'sinatra/content_for' require 'rufus/scheduler' require 'coffee-script' @@ -7,9 +8,18 @@ require 'json' SCHEDULER = Rufus::Scheduler.start_new +set :root, Dir.pwd + +set :sprockets, Sprockets::Environment.new(settings.root) +set :assets_prefix, '/assets' +set :digest_assets, false +['assets/javascripts', 'assets/stylesheets', 'assets/fonts', 'assets/images', 'widgets', File.expand_path('../../javascripts', __FILE__)]. each do |path| + settings.sprockets.append_path path +end + set server: 'thin', connections: [], history: {} -set :public_folder, File.join(Dir.pwd, 'public') -set :views, File.join(Dir.pwd, 'dashboards') +set :public_folder, File.join(settings.root, 'public') +set :views, File.join(settings.root, 'dashboards') set :default_dashboard, nil set :auth_token, nil @@ -45,7 +55,7 @@ end get '/views/:widget?.html' do protected! widget = params[:widget] - send_file File.join(Dir.pwd, 'widgets', widget, "#{widget}.html") + send_file File.join(settings.root, 'widgets', widget, "#{widget}.html") end post '/widgets/:id' do @@ -61,39 +71,12 @@ post '/widgets/:id' do end end -def framework_javascripts - ['jquery.js', 'es5-shim.js', 'batman.js', 'batman.jquery.js', 'application.coffee', 'widget.coffee'].collect do |f| - File.join(File.expand_path('../../vendor/javascripts', __FILE__), f) - end -end - -def widget_javascripts - asset_paths("/widgets/**/*.coffee") -end - -def javascripts - (framework_javascripts + widget_javascripts).collect do |f| - if File.extname(f) == ".coffee" - begin - CoffeeScript.compile(File.read(f)) - rescue ExecJS::ProgramError => e - message = e.message + ": in #{f}" - raise ExecJS::ProgramError.new(message) - end - else - File.read(f) - end - end.join("\n") -end - -def stylesheets - asset_paths("/public/**/*.scss", "/widgets/**/*.scss").collect do |f| - Sass.compile File.read(f) - end.join("\n") +def development? + ENV['RACK_ENV'] == 'development' end -def asset_paths(*paths) - paths.inject([]) { |arr, path| arr + Dir[File.join(Dir.pwd, path)] } +def production? + ENV['RACK_ENV'] == 'production' end def send_event(id, body) @@ -119,8 +102,8 @@ def first_dashboard files.first end -Dir[File.join(Dir.pwd, 'lib', '**', '*.rb')].each {|file| require file } +Dir[File.join(settings.root, 'lib', '**', '*.rb')].each {|file| require file } job_path = ENV["JOB_PATH"] || 'jobs' -files = Dir[File.join(Dir.pwd, job_path, '/*.rb')] -files.each { |job| require(job) } \ No newline at end of file +files = Dir[File.join(settings.root, job_path, '/*.rb')] +files.each { |job| require(job) } -- cgit v1.2.3 From 372ee8475ef9b685e8cd3b8dea2e0475d5a4bd6b Mon Sep 17 00:00:00 2001 From: Daniel Beauchamp Date: Fri, 24 Aug 2012 17:58:43 -0400 Subject: Keep track of updatedAt server side, instead of on widgets. --- lib/dashing.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index ee7cd1f..beb7eca 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -81,6 +81,7 @@ end def send_event(id, body) body["id"] = id + body["updatedAt"] = Time.now event = format_event(JSON.unparse(body)) settings.history[id] = event settings.connections.each { |out| out << event } -- cgit v1.2.3 From 4bf82f6303465bb74e9e6d2747d3053358ef83b7 Mon Sep 17 00:00:00 2001 From: Daniel Beauchamp Date: Tue, 28 Aug 2012 16:03:22 -0400 Subject: Added in a fix that forces your json codec to initialize before jobs are run. --- lib/dashing.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index beb7eca..7d066cc 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -82,7 +82,7 @@ end def send_event(id, body) body["id"] = id body["updatedAt"] = Time.now - event = format_event(JSON.unparse(body)) + event = format_event(body.to_json) settings.history[id] = event settings.connections.each { |out| out << event } end @@ -104,6 +104,7 @@ def first_dashboard end Dir[File.join(settings.root, 'lib', '**', '*.rb')].each {|file| require file } +{}.to_json # Forces your json codec to initialize (in the event that it is lazily loaded). Does this before job threads start. job_path = ENV["JOB_PATH"] || 'jobs' files = Dir[File.join(settings.root, job_path, '/*.rb')] -- cgit v1.2.3 From c39be899b629378c0a4e1dd4beab528ebcbffa6e Mon Sep 17 00:00:00 2001 From: Scott Gerring Date: Sat, 3 Nov 2012 13:18:40 +0800 Subject: Display 'last updated' using user's locale. Closes #10 --- lib/dashing.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 7d066cc..37dc54d 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -81,7 +81,7 @@ end def send_event(id, body) body["id"] = id - body["updatedAt"] = Time.now + body["updatedAt"] = Time.now.to_i event = format_event(body.to_json) settings.history[id] = event settings.connections.each { |out| out << event } -- cgit v1.2.3 From 17ef11ebf1ba22c63cfee4b668f314d69d9bd684 Mon Sep 17 00:00:00 2001 From: Daniel Beauchamp Date: Thu, 24 Jan 2013 17:14:26 -0500 Subject: Allow overriding the 'updatedAt' field. This is useful if you want to show the freshness of the data instead of when the job was last run. --- lib/dashing.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 37dc54d..04e4cee 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -80,8 +80,8 @@ def production? end def send_event(id, body) - body["id"] = id - body["updatedAt"] = Time.now.to_i + body[:id] = id + body[:updatedAt] ||= Time.now.to_i event = format_event(body.to_json) settings.history[id] = event settings.connections.each { |out| out << event } -- cgit v1.2.3 From 6dcda5d7cd26514cac1b18438af7913105e68f00 Mon Sep 17 00:00:00 2001 From: Daniel Beauchamp Date: Wed, 13 Feb 2013 21:32:34 -0500 Subject: Added the public directory for serving static assets. This allows for stuff like favicons and custom 404/500 pages. --- lib/dashing.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 04e4cee..bae4c29 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -49,7 +49,11 @@ end get '/:dashboard' do protected! - erb params[:dashboard].to_sym + if File.exist? File.join(settings.views, "#{params[:dashboard]}.erb") + erb params[:dashboard].to_sym + else + halt 404 + end end get '/views/:widget?.html' do @@ -71,6 +75,10 @@ post '/widgets/:id' do end end +not_found do + send_file File.join(settings.public_folder, '404.html') +end + def development? ENV['RACK_ENV'] == 'development' end -- cgit v1.2.3 From 98f326eab33ee741c2d95c1861fc5c9233f0b47d Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 26 Mar 2013 13:27:58 -0400 Subject: Send an X-Accel-Redirect in /events to disable nginx buffering. --- lib/dashing.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index bae4c29..949cfee 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -32,6 +32,7 @@ end get '/events', provides: 'text/event-stream' do protected! + response.headers['X-Accel-Buffering'] = 'no' # Disable buffering for nginx stream :keep_open do |out| settings.connections << out out << latest_events @@ -116,4 +117,4 @@ Dir[File.join(settings.root, 'lib', '**', '*.rb')].each {|file| require file } job_path = ENV["JOB_PATH"] || 'jobs' files = Dir[File.join(settings.root, job_path, '/*.rb')] -files.each { |job| require(job) } +files.each { |job| require(job) } -- cgit v1.2.3 From 8ad8097ed22cb557a2b5d946d11158575e82cb94 Mon Sep 17 00:00:00 2001 From: Zachary Salzbank Date: Mon, 29 Apr 2013 20:49:02 -0300 Subject: allow send_event to be called from within modules resolves the error: undefined local variable or method `settings' when send_event is called from in a module --- lib/dashing.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 949cfee..952b614 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -92,8 +92,8 @@ def send_event(id, body) body[:id] = id body[:updatedAt] ||= Time.now.to_i event = format_event(body.to_json) - settings.history[id] = event - settings.connections.each { |out| out << event } + Sinatra::Application.settings.history[id] = event + Sinatra::Application.settings.connections.each { |out| out << event } end def format_event(body) -- cgit v1.2.3 From 8795505a06fe58dfb5f414078587349b7771af62 Mon Sep 17 00:00:00 2001 From: Aaron Peckham Date: Thu, 9 May 2013 23:27:44 -0700 Subject: use any Tilt-supported view engine for dashboards, including Haml --- lib/dashing.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 952b614..391095d 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -50,8 +50,12 @@ end get '/:dashboard' do protected! - if File.exist? File.join(settings.views, "#{params[:dashboard]}.erb") - erb params[:dashboard].to_sym + view_engine = Tilt.mappings.keys.find do |ext| + File.exist? File.join(settings.views, "#{params[:dashboard]}.#{ext}") + end + + if view_engine + render view_engine.to_sym, params[:dashboard].to_sym else halt 404 end @@ -107,7 +111,7 @@ def latest_events end def first_dashboard - files = Dir[File.join(settings.views, '*.erb')].collect { |f| f.match(/(\w*).erb/)[1] } + files = Dir[File.join(settings.views, '*')].collect { |f| File.basename(f, '.*') } files -= ['layout'] files.first end -- cgit v1.2.3 From b1b0b3c18d6ee05e42a043dde8d48058841df35c Mon Sep 17 00:00:00 2001 From: Nathan Broadbent Date: Sat, 18 May 2013 11:48:22 +1200 Subject: Persist history at exit and load on start, so that server restarts don't lose all pushed events --- lib/dashing.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 952b614..63fdc37 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -5,6 +5,7 @@ require 'rufus/scheduler' require 'coffee-script' require 'sass' require 'json' +require 'yaml' SCHEDULER = Rufus::Scheduler.start_new @@ -17,7 +18,21 @@ set :digest_assets, false settings.sprockets.append_path path end -set server: 'thin', connections: [], history: {} +set server: 'thin', connections: [], history_file: 'tmp/history.yml' + +# Persist history in tmp file at exit +at_exit do + File.open(settings.history_file, 'w') do |f| + f.puts settings.history.to_yaml + end +end + +if File.exists?(settings.history_file) + set history: YAML.load_file(settings.history_file) +else + set history: {} +end + set :public_folder, File.join(settings.root, 'public') set :views, File.join(settings.root, 'dashboards') set :default_dashboard, nil -- cgit v1.2.3 From a6eea61f2a7cfbf67175583dd7dcb49a4c09da75 Mon Sep 17 00:00:00 2001 From: Aaron Peckham Date: Mon, 20 May 2013 12:19:35 -0700 Subject: allow widget views to be written in any Tilt-supported template language --- lib/dashing.rb | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 391095d..04921fe 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -50,21 +50,20 @@ end get '/:dashboard' do protected! - view_engine = Tilt.mappings.keys.find do |ext| - File.exist? File.join(settings.views, "#{params[:dashboard]}.#{ext}") + tilt_html_engines.each do |suffix, _| + file = File.join(settings.views, "#{params[:dashboard]}.#{suffix}") + return render(suffix.to_sym, params[:dashboard].to_sym) if File.exist? file end - if view_engine - render view_engine.to_sym, params[:dashboard].to_sym - else - halt 404 - end + halt 404 end get '/views/:widget?.html' do protected! - widget = params[:widget] - send_file File.join(settings.root, 'widgets', widget, "#{widget}.html") + tilt_html_engines.each do |suffix, engines| + file = File.join(settings.root, "widgets", params[:widget], "#{params[:widget]}.#{suffix}") + return engines.first.new(file).render if File.exist? file + end end post '/widgets/:id' do @@ -116,6 +115,13 @@ def first_dashboard files.first end +def tilt_html_engines + Tilt.mappings.select do |_, engines| + default_mime_type = engines.first.default_mime_type + default_mime_type.nil? || default_mime_type == 'text/html' + end +end + Dir[File.join(settings.root, 'lib', '**', '*.rb')].each {|file| require file } {}.to_json # Forces your json codec to initialize (in the event that it is lazily loaded). Does this before job threads start. -- cgit v1.2.3 From f609a409637d9586814d5f37e9a060e89eab8382 Mon Sep 17 00:00:00 2001 From: David Underwood Date: Fri, 21 Jun 2013 11:42:17 -0400 Subject: Changes history file location to avoid failing tests --- lib/dashing.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 24b191c..34e2cb5 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -18,7 +18,7 @@ set :digest_assets, false settings.sprockets.append_path path end -set server: 'thin', connections: [], history_file: 'tmp/history.yml' +set server: 'thin', connections: [], history_file: 'history.yml' # Persist history in tmp file at exit at_exit do -- cgit v1.2.3 From 86aac07d890c6ec87d9486e9580ecc87bc5dab26 Mon Sep 17 00:00:00 2001 From: David Underwood Date: Fri, 28 Jun 2013 12:37:52 -0400 Subject: Yo dawg, I heard you liked jobs so we added recursive loading to your job loader so you can have job subfolders in the job folder that contain jobs --- lib/dashing.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 34e2cb5..dc62395 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -141,5 +141,5 @@ Dir[File.join(settings.root, 'lib', '**', '*.rb')].each {|file| require file } {}.to_json # Forces your json codec to initialize (in the event that it is lazily loaded). Does this before job threads start. job_path = ENV["JOB_PATH"] || 'jobs' -files = Dir[File.join(settings.root, job_path, '/*.rb')] +files = Dir[File.join(settings.root, job_path, '**', '/*.rb')] files.each { |job| require(job) } -- cgit v1.2.3 From 3f58bbd626cd84d1cb24375b4d8fe6df75ce85ba Mon Sep 17 00:00:00 2001 From: Pieter van de Bruggen Date: Sun, 4 Nov 2012 17:10:36 -0800 Subject: Adding application messaging. While widgets are the most common target for events, there is good reason to sometimes target the application as a whole. This patch adds support for such events using the /-prefixed notation. (This convention is safe for use since it represents an extremely uncommon widget ID, and one which data could not be POSTed to.) Such events are NOT subject to replay, and are handled on the client-side by firing corresponding events on the Dashing application class. As a proof of concept (and to fufill a feature request), this patch also introduces a POST /reload endpoint, which when provided with a valid authkey (and optionally a dashboard name) fires the appropriate 'reload' event on all of the loaded dashboards. The 'reload' event handler then reloads the dashboard, unless a different dashboard was specified. --- lib/dashing.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index dc62395..4c1b44b 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -81,9 +81,22 @@ get '/views/:widget?.html' do end end +post '/reload' do + request.body.rewind + body = JSON.parse(request.body.read) + auth_token = body.delete("auth_token") + if !settings.auth_token || settings.auth_token == auth_token + send_event('/reload', body) + 204 # response without entity body + else + status 401 + "Invalid API key\n" + end +end + post '/widgets/:id' do request.body.rewind - body = JSON.parse(request.body.read) + body = JSON.parse(request.body.read) auth_token = body.delete("auth_token") if !settings.auth_token || settings.auth_token == auth_token send_event(params['id'], body) @@ -110,7 +123,7 @@ def send_event(id, body) body[:id] = id body[:updatedAt] ||= Time.now.to_i event = format_event(body.to_json) - Sinatra::Application.settings.history[id] = event + Sinatra::Application.settings.history[id] = event unless id =~ /^\// Sinatra::Application.settings.connections.each { |out| out << event } end -- cgit v1.2.3 From 5f8cbcb7debde027e79d87733b84cb852aa47dad Mon Sep 17 00:00:00 2001 From: Chad Jolly Date: Sun, 14 Jul 2013 18:36:19 -0600 Subject: dashboard events - use SSE event names --- lib/dashing.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 4c1b44b..935a031 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -81,12 +81,13 @@ get '/views/:widget?.html' do end end -post '/reload' do +post '/dashboards/:id' do request.body.rewind body = JSON.parse(request.body.read) + body['dashboard'] ||= params['id'] auth_token = body.delete("auth_token") if !settings.auth_token || settings.auth_token == auth_token - send_event('/reload', body) + send_event(params['id'], body, 'dashboards') 204 # response without entity body else status 401 @@ -119,16 +120,18 @@ def production? ENV['RACK_ENV'] == 'production' end -def send_event(id, body) +def send_event(id, body, target=nil) body[:id] = id body[:updatedAt] ||= Time.now.to_i - event = format_event(body.to_json) - Sinatra::Application.settings.history[id] = event unless id =~ /^\// + event = format_event(body.to_json, target) + Sinatra::Application.settings.history[id] = event unless target == 'dashboards' Sinatra::Application.settings.connections.each { |out| out << event } end -def format_event(body) - "data: #{body}\n\n" +def format_event(body, name=nil) + str = "" + str << "event: #{name}\n" if name + str << "data: #{body}\n\n" end def latest_events -- cgit v1.2.3 From 06ba2ea902bfd73a9c9437e7f44063b21611edb7 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Wed, 31 Jul 2013 10:49:00 +0200 Subject: Authenticate / route as to not leak private dashboard url --- lib/dashing.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 935a031..f5b60ca 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -56,6 +56,7 @@ get '/events', provides: 'text/event-stream' do end get '/' do + protected! begin redirect "/" + (settings.default_dashboard || first_dashboard).to_s rescue NoMethodError => e -- cgit v1.2.3 From 7f2031512e5003d0d20d07bcebf248f76cfcd0a8 Mon Sep 17 00:00:00 2001 From: pushmatrix Date: Fri, 9 Aug 2013 18:23:00 +0200 Subject: The default dashboard is picked simply as the first one alphabetically across all OSes. Closes #97 --- lib/dashing.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index f5b60ca..2c3a810 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -144,7 +144,7 @@ end def first_dashboard files = Dir[File.join(settings.views, '*')].collect { |f| File.basename(f, '.*') } files -= ['layout'] - files.first + files.sort.first end def tilt_html_engines -- cgit v1.2.3 From eb331a2fab101b93bce0a5b87e06927f324c96b8 Mon Sep 17 00:00:00 2001 From: Jordan Wheeler Date: Mon, 9 Sep 2013 15:34:08 -0400 Subject: add firebase gems --- lib/dashing.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 2c3a810..d4a8c11 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -6,6 +6,7 @@ require 'coffee-script' require 'sass' require 'json' require 'yaml' +require 'firebase' SCHEDULER = Rufus::Scheduler.start_new -- cgit v1.2.3 From b81273772effd5300bfd970abffedad3d0362dbf Mon Sep 17 00:00:00 2001 From: pushmatrix Date: Mon, 9 Sep 2013 21:38:53 +0200 Subject: Revert "Merge pull request #226 from jordanwheeler/use-firebase" This reverts commit a4cdeb81c2cba2b7028f342d56abac22f101bf78, reversing changes made to 2671d387e3c9b845431d41ab93d51cf2bd7b2cf8. --- lib/dashing.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index d4a8c11..2c3a810 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -6,7 +6,6 @@ require 'coffee-script' require 'sass' require 'json' require 'yaml' -require 'firebase' SCHEDULER = Rufus::Scheduler.start_new -- cgit v1.2.3 From 7ca9e828423d6e19a9b85fc3dbc8f5d8696e937e Mon Sep 17 00:00:00 2001 From: John Tajima Date: Wed, 30 Oct 2013 19:34:45 -0400 Subject: Define a KEYS constant --- lib/dashing.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 2c3a810..f1b18fc 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -8,6 +8,7 @@ require 'json' require 'yaml' SCHEDULER = Rufus::Scheduler.start_new +KEYS = {} set :root, Dir.pwd -- cgit v1.2.3 From 4177623a7a5e7967514a0d92095e0dcb32896026 Mon Sep 17 00:00:00 2001 From: John Tajima Date: Wed, 30 Oct 2013 19:57:07 -0400 Subject: require config/settings.rb if exists --- lib/dashing.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index f1b18fc..a8e9f33 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -8,7 +8,6 @@ require 'json' require 'yaml' SCHEDULER = Rufus::Scheduler.start_new -KEYS = {} set :root, Dir.pwd @@ -155,6 +154,11 @@ def tilt_html_engines end end +settings_file = File.join(settings.root, 'config/settings.rb') +if (File.exists?(settings_file)) + require settings_file +end + Dir[File.join(settings.root, 'lib', '**', '*.rb')].each {|file| require file } {}.to_json # Forces your json codec to initialize (in the event that it is lazily loaded). Does this before job threads start. -- cgit v1.2.3 From 4a042c5cfef6d29d509af12830d11304578fa6aa Mon Sep 17 00:00:00 2001 From: pseudomuto Date: Wed, 18 Dec 2013 15:43:46 -0500 Subject: adding pessimistic versioning for gem dependencies --- lib/dashing.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index a8e9f33..7c4f25a 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -7,7 +7,7 @@ require 'sass' require 'json' require 'yaml' -SCHEDULER = Rufus::Scheduler.start_new +SCHEDULER = Rufus::Scheduler.new set :root, Dir.pwd -- cgit v1.2.3 From d0eef2dbe9d1178111cd768116a66b32a3a4b2b5 Mon Sep 17 00:00:00 2001 From: pseudomuto Date: Wed, 18 Dec 2013 19:12:43 -0500 Subject: moving cli to lib and updating bin file --- lib/dashing.rb | 3 ++ lib/dashing/cli.rb | 105 ++++++++++++++++++++++++++++++++++++++++++++++ lib/dashing/downloader.rb | 18 ++++++++ 3 files changed, 126 insertions(+) create mode 100644 lib/dashing/cli.rb create mode 100644 lib/dashing/downloader.rb (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 7c4f25a..54579dd 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -7,6 +7,9 @@ require 'sass' require 'json' require 'yaml' +require 'dashing/cli' +require 'dashing/downloader' + SCHEDULER = Rufus::Scheduler.new set :root, Dir.pwd diff --git a/lib/dashing/cli.rb b/lib/dashing/cli.rb new file mode 100644 index 0000000..001a92e --- /dev/null +++ b/lib/dashing/cli.rb @@ -0,0 +1,105 @@ +require 'thor' +require 'open-uri' + +module Dashing + class CLI < Thor + include Thor::Actions + + attr_reader :name + + class << self + attr_accessor :auth_token + + def CLI.hyphenate(str) + return str.downcase if str =~ /^[A-Z-]+$/ + str.gsub('_', '-').gsub(/\B[A-Z]/, '-\&').squeeze('-').downcase + end + end + + no_tasks do + %w(widget dashboard job).each do |type| + define_method "generate_#{type}" do |name| + @name = Thor::Util.snake_case(name) + directory(type.to_sym, "#{type}s") + end + end + end + + desc "new PROJECT_NAME", "Sets up ALL THE THINGS needed for your dashboard project." + def new(name) + @name = Thor::Util.snake_case(name) + directory(:project, @name) + end + + desc "generate (widget/dashboard/job) NAME", "Creates a new widget, dashboard, or job." + def generate(type, name) + public_send("generate_#{type}".to_sym, name) + rescue NoMethodError => e + puts "Invalid generator. Either use widget, dashboard, or job" + end + + desc "install GIST_ID", "Installs a new widget from a gist." + def install(gist_id) + gist = Downloader.get_gist(gist_id) + public_url = "https://gist.github.com/#{gist_id}" + + gist['files'].each do |file, details| + if file =~ /\.(html|coffee|scss)\z/ + widget_name = File.basename(file, '.*') + new_path = File.join(Dir.pwd, 'widgets', widget_name, file) + create_file(new_path, details['content']) + elsif file.end_with?('.rb') + new_path = File.join(Dir.pwd, 'jobs', file) + create_file(new_path, details['content']) + end + end + + print set_color("Don't forget to edit the ", :yellow) + print set_color("Gemfile ", :yellow, :bold) + print set_color("and run ", :yellow) + print set_color("bundle install ", :yellow, :bold) + say set_color("if needed. More information for this widget can be found at #{public_url}", :yellow) + rescue OpenURI::HTTPError => http_error + say set_color("Could not find gist at #{public_url}"), :red + end + + desc "start", "Starts the server in style!" + method_option :job_path, :desc => "Specify the directory where jobs are stored" + def start(*args) + port_option = args.include?('-p') ? '' : ' -p 3030' + args = args.join(' ') + command = "bundle exec thin -R config.ru start#{port_option} #{args}" + command.prepend "export JOB_PATH=#{options[:job_path]}; " if options[:job_path] + run_command(command) + end + + desc "stop", "Stops the thin server" + def stop + command = "bundle exec thin stop" + run_command(command) + end + + desc "job JOB_NAME AUTH_TOKEN(optional)", "Runs the specified job. Make sure to supply your auth token if you have one set." + def job(name, auth_token = "") + Dir[File.join(Dir.pwd, 'lib/**/*.rb')].each {|file| require_file(file) } + self.class.auth_token = auth_token + f = File.join(Dir.pwd, "jobs", "#{name}.rb") + require_file(f) + end + + # map some commands + map 'g' => :generate + map 'i' => :install + map 's' => :start + + private + + def run_command(command) + system(command) + end + + def require_file(file) + require file + end + end +end diff --git a/lib/dashing/downloader.rb b/lib/dashing/downloader.rb new file mode 100644 index 0000000..140e862 --- /dev/null +++ b/lib/dashing/downloader.rb @@ -0,0 +1,18 @@ +require 'net/http' +require 'open-uri' +require 'json' + +module Dashing + module Downloader + extend self + + def get_gist(gist_id) + get_json("https://api.github.com/gists/#{gist_id}") + end + + def get_json(url) + response = open(url).read + JSON.parse(response) + end + end +end -- cgit v1.2.3 From fbc9497dbb8ece970f21bc67164557bba8db2db7 Mon Sep 17 00:00:00 2001 From: pseudomuto Date: Wed, 18 Dec 2013 19:22:09 -0500 Subject: moving app to it's own file under lib/dashing --- lib/dashing.rb | 168 +---------------------------------------------------- lib/dashing/app.rb | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 166 deletions(-) create mode 100644 lib/dashing/app.rb (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 54579dd..855aa36 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -1,170 +1,6 @@ -require 'sinatra' -require 'sprockets' -require 'sinatra/content_for' -require 'rufus/scheduler' -require 'coffee-script' -require 'sass' -require 'json' -require 'yaml' - require 'dashing/cli' require 'dashing/downloader' +require 'dashing/app' -SCHEDULER = Rufus::Scheduler.new - -set :root, Dir.pwd - -set :sprockets, Sprockets::Environment.new(settings.root) -set :assets_prefix, '/assets' -set :digest_assets, false -['assets/javascripts', 'assets/stylesheets', 'assets/fonts', 'assets/images', 'widgets', File.expand_path('../../javascripts', __FILE__)]. each do |path| - settings.sprockets.append_path path -end - -set server: 'thin', connections: [], history_file: 'history.yml' - -# Persist history in tmp file at exit -at_exit do - File.open(settings.history_file, 'w') do |f| - f.puts settings.history.to_yaml - end -end - -if File.exists?(settings.history_file) - set history: YAML.load_file(settings.history_file) -else - set history: {} -end - -set :public_folder, File.join(settings.root, 'public') -set :views, File.join(settings.root, 'dashboards') -set :default_dashboard, nil -set :auth_token, nil - -helpers Sinatra::ContentFor -helpers do - def protected! - # override with auth logic - end -end - -get '/events', provides: 'text/event-stream' do - protected! - response.headers['X-Accel-Buffering'] = 'no' # Disable buffering for nginx - stream :keep_open do |out| - settings.connections << out - out << latest_events - out.callback { settings.connections.delete(out) } - end -end - -get '/' do - protected! - begin - redirect "/" + (settings.default_dashboard || first_dashboard).to_s - rescue NoMethodError => e - raise Exception.new("There are no dashboards in your dashboard directory.") - end -end - -get '/:dashboard' do - protected! - tilt_html_engines.each do |suffix, _| - file = File.join(settings.views, "#{params[:dashboard]}.#{suffix}") - return render(suffix.to_sym, params[:dashboard].to_sym) if File.exist? file - end - - halt 404 -end - -get '/views/:widget?.html' do - protected! - tilt_html_engines.each do |suffix, engines| - file = File.join(settings.root, "widgets", params[:widget], "#{params[:widget]}.#{suffix}") - return engines.first.new(file).render if File.exist? file - end -end - -post '/dashboards/:id' do - request.body.rewind - body = JSON.parse(request.body.read) - body['dashboard'] ||= params['id'] - auth_token = body.delete("auth_token") - if !settings.auth_token || settings.auth_token == auth_token - send_event(params['id'], body, 'dashboards') - 204 # response without entity body - else - status 401 - "Invalid API key\n" - end +module Dashing end - -post '/widgets/:id' do - request.body.rewind - body = JSON.parse(request.body.read) - auth_token = body.delete("auth_token") - if !settings.auth_token || settings.auth_token == auth_token - send_event(params['id'], body) - 204 # response without entity body - else - status 401 - "Invalid API key\n" - end -end - -not_found do - send_file File.join(settings.public_folder, '404.html') -end - -def development? - ENV['RACK_ENV'] == 'development' -end - -def production? - ENV['RACK_ENV'] == 'production' -end - -def send_event(id, body, target=nil) - body[:id] = id - body[:updatedAt] ||= Time.now.to_i - event = format_event(body.to_json, target) - Sinatra::Application.settings.history[id] = event unless target == 'dashboards' - Sinatra::Application.settings.connections.each { |out| out << event } -end - -def format_event(body, name=nil) - str = "" - str << "event: #{name}\n" if name - str << "data: #{body}\n\n" -end - -def latest_events - settings.history.inject("") do |str, (id, body)| - str << body - end -end - -def first_dashboard - files = Dir[File.join(settings.views, '*')].collect { |f| File.basename(f, '.*') } - files -= ['layout'] - files.sort.first -end - -def tilt_html_engines - Tilt.mappings.select do |_, engines| - default_mime_type = engines.first.default_mime_type - default_mime_type.nil? || default_mime_type == 'text/html' - end -end - -settings_file = File.join(settings.root, 'config/settings.rb') -if (File.exists?(settings_file)) - require settings_file -end - -Dir[File.join(settings.root, 'lib', '**', '*.rb')].each {|file| require file } -{}.to_json # Forces your json codec to initialize (in the event that it is lazily loaded). Does this before job threads start. - -job_path = ENV["JOB_PATH"] || 'jobs' -files = Dir[File.join(settings.root, job_path, '**', '/*.rb')] -files.each { |job| require(job) } diff --git a/lib/dashing/app.rb b/lib/dashing/app.rb new file mode 100644 index 0000000..7c4f25a --- /dev/null +++ b/lib/dashing/app.rb @@ -0,0 +1,167 @@ +require 'sinatra' +require 'sprockets' +require 'sinatra/content_for' +require 'rufus/scheduler' +require 'coffee-script' +require 'sass' +require 'json' +require 'yaml' + +SCHEDULER = Rufus::Scheduler.new + +set :root, Dir.pwd + +set :sprockets, Sprockets::Environment.new(settings.root) +set :assets_prefix, '/assets' +set :digest_assets, false +['assets/javascripts', 'assets/stylesheets', 'assets/fonts', 'assets/images', 'widgets', File.expand_path('../../javascripts', __FILE__)]. each do |path| + settings.sprockets.append_path path +end + +set server: 'thin', connections: [], history_file: 'history.yml' + +# Persist history in tmp file at exit +at_exit do + File.open(settings.history_file, 'w') do |f| + f.puts settings.history.to_yaml + end +end + +if File.exists?(settings.history_file) + set history: YAML.load_file(settings.history_file) +else + set history: {} +end + +set :public_folder, File.join(settings.root, 'public') +set :views, File.join(settings.root, 'dashboards') +set :default_dashboard, nil +set :auth_token, nil + +helpers Sinatra::ContentFor +helpers do + def protected! + # override with auth logic + end +end + +get '/events', provides: 'text/event-stream' do + protected! + response.headers['X-Accel-Buffering'] = 'no' # Disable buffering for nginx + stream :keep_open do |out| + settings.connections << out + out << latest_events + out.callback { settings.connections.delete(out) } + end +end + +get '/' do + protected! + begin + redirect "/" + (settings.default_dashboard || first_dashboard).to_s + rescue NoMethodError => e + raise Exception.new("There are no dashboards in your dashboard directory.") + end +end + +get '/:dashboard' do + protected! + tilt_html_engines.each do |suffix, _| + file = File.join(settings.views, "#{params[:dashboard]}.#{suffix}") + return render(suffix.to_sym, params[:dashboard].to_sym) if File.exist? file + end + + halt 404 +end + +get '/views/:widget?.html' do + protected! + tilt_html_engines.each do |suffix, engines| + file = File.join(settings.root, "widgets", params[:widget], "#{params[:widget]}.#{suffix}") + return engines.first.new(file).render if File.exist? file + end +end + +post '/dashboards/:id' do + request.body.rewind + body = JSON.parse(request.body.read) + body['dashboard'] ||= params['id'] + auth_token = body.delete("auth_token") + if !settings.auth_token || settings.auth_token == auth_token + send_event(params['id'], body, 'dashboards') + 204 # response without entity body + else + status 401 + "Invalid API key\n" + end +end + +post '/widgets/:id' do + request.body.rewind + body = JSON.parse(request.body.read) + auth_token = body.delete("auth_token") + if !settings.auth_token || settings.auth_token == auth_token + send_event(params['id'], body) + 204 # response without entity body + else + status 401 + "Invalid API key\n" + end +end + +not_found do + send_file File.join(settings.public_folder, '404.html') +end + +def development? + ENV['RACK_ENV'] == 'development' +end + +def production? + ENV['RACK_ENV'] == 'production' +end + +def send_event(id, body, target=nil) + body[:id] = id + body[:updatedAt] ||= Time.now.to_i + event = format_event(body.to_json, target) + Sinatra::Application.settings.history[id] = event unless target == 'dashboards' + Sinatra::Application.settings.connections.each { |out| out << event } +end + +def format_event(body, name=nil) + str = "" + str << "event: #{name}\n" if name + str << "data: #{body}\n\n" +end + +def latest_events + settings.history.inject("") do |str, (id, body)| + str << body + end +end + +def first_dashboard + files = Dir[File.join(settings.views, '*')].collect { |f| File.basename(f, '.*') } + files -= ['layout'] + files.sort.first +end + +def tilt_html_engines + Tilt.mappings.select do |_, engines| + default_mime_type = engines.first.default_mime_type + default_mime_type.nil? || default_mime_type == 'text/html' + end +end + +settings_file = File.join(settings.root, 'config/settings.rb') +if (File.exists?(settings_file)) + require settings_file +end + +Dir[File.join(settings.root, 'lib', '**', '*.rb')].each {|file| require file } +{}.to_json # Forces your json codec to initialize (in the event that it is lazily loaded). Does this before job threads start. + +job_path = ENV["JOB_PATH"] || 'jobs' +files = Dir[File.join(settings.root, job_path, '**', '/*.rb')] +files.each { |job| require(job) } -- cgit v1.2.3 From e975ce77408f5995213cc1b85f61e806152ba3a2 Mon Sep 17 00:00:00 2001 From: pseudomuto Date: Thu, 19 Dec 2013 12:08:07 -0500 Subject: light refactoring for better test coverage --- lib/dashing/app.rb | 110 ++++++++++++++++++++++++++--------------------------- lib/dashing/cli.rb | 26 +++++++------ 2 files changed, 70 insertions(+), 66 deletions(-) (limited to 'lib') diff --git a/lib/dashing/app.rb b/lib/dashing/app.rb index 7c4f25a..5780b92 100644 --- a/lib/dashing/app.rb +++ b/lib/dashing/app.rb @@ -9,40 +9,59 @@ require 'yaml' SCHEDULER = Rufus::Scheduler.new -set :root, Dir.pwd - -set :sprockets, Sprockets::Environment.new(settings.root) -set :assets_prefix, '/assets' -set :digest_assets, false -['assets/javascripts', 'assets/stylesheets', 'assets/fonts', 'assets/images', 'widgets', File.expand_path('../../javascripts', __FILE__)]. each do |path| - settings.sprockets.append_path path +def development? + ENV['RACK_ENV'] == 'development' end -set server: 'thin', connections: [], history_file: 'history.yml' +def production? + ENV['RACK_ENV'] == 'production' +end -# Persist history in tmp file at exit -at_exit do - File.open(settings.history_file, 'w') do |f| - f.puts settings.history.to_yaml +helpers Sinatra::ContentFor +helpers do + def protected! + # override with auth logic end end +set :root, Dir.pwd +set :sprockets, Sprockets::Environment.new(settings.root) +set :assets_prefix, '/assets' +set :digest_assets, false +set server: 'thin', connections: [], history_file: 'history.yml' +set :public_folder, File.join(settings.root, 'public') +set :views, File.join(settings.root, 'dashboards') +set :default_dashboard, nil +set :auth_token, nil + if File.exists?(settings.history_file) set history: YAML.load_file(settings.history_file) else set history: {} end -set :public_folder, File.join(settings.root, 'public') -set :views, File.join(settings.root, 'dashboards') -set :default_dashboard, nil -set :auth_token, nil +%w(javascripts stylesheets fonts images).each do |path| + settings.sprockets.append_path("assets/#{path}") +end -helpers Sinatra::ContentFor -helpers do - def protected! - # override with auth logic - end +['widgets', File.expand_path('../../javascripts', __FILE__)]. each do |path| + settings.sprockets.append_path(path) +end + +not_found do + send_file File.join(settings.public_folder, '404.html') +end + +at_exit do + File.write(settings.history_file, settings.history.to_yaml) +end + +get '/' do + protected! + dashboard = settings.default_dashboard || first_dashboard + raise Exception.new('There are no dashboards available') if not dashboard + + redirect "/" + dashboard end get '/events', provides: 'text/event-stream' do @@ -55,15 +74,6 @@ get '/events', provides: 'text/event-stream' do end end -get '/' do - protected! - begin - redirect "/" + (settings.default_dashboard || first_dashboard).to_s - rescue NoMethodError => e - raise Exception.new("There are no dashboards in your dashboard directory.") - end -end - get '/:dashboard' do protected! tilt_html_engines.each do |suffix, _| @@ -74,14 +84,6 @@ get '/:dashboard' do halt 404 end -get '/views/:widget?.html' do - protected! - tilt_html_engines.each do |suffix, engines| - file = File.join(settings.root, "widgets", params[:widget], "#{params[:widget]}.#{suffix}") - return engines.first.new(file).render if File.exist? file - end -end - post '/dashboards/:id' do request.body.rewind body = JSON.parse(request.body.read) @@ -109,16 +111,12 @@ post '/widgets/:id' do end end -not_found do - send_file File.join(settings.public_folder, '404.html') -end - -def development? - ENV['RACK_ENV'] == 'development' -end - -def production? - ENV['RACK_ENV'] == 'production' +get '/views/:widget?.html' do + protected! + tilt_html_engines.each do |suffix, engines| + file = File.join(settings.root, "widgets", params[:widget], "#{params[:widget]}.#{suffix}") + return engines.first.new(file).render if File.exist? file + end end def send_event(id, body, target=nil) @@ -154,14 +152,16 @@ def tilt_html_engines end end -settings_file = File.join(settings.root, 'config/settings.rb') -if (File.exists?(settings_file)) - require settings_file +def require_glob(relative_glob) + Dir[File.join(settings.root, relative_glob)].each do |file| + require file + end end -Dir[File.join(settings.root, 'lib', '**', '*.rb')].each {|file| require file } -{}.to_json # Forces your json codec to initialize (in the event that it is lazily loaded). Does this before job threads start. +settings_file = File.join(settings.root, 'config/settings.rb') +require settings_file if File.exists?(settings_file) +{}.to_json # Forces your json codec to initialize (in the event that it is lazily loaded). Does this before job threads start. job_path = ENV["JOB_PATH"] || 'jobs' -files = Dir[File.join(settings.root, job_path, '**', '/*.rb')] -files.each { |job| require(job) } +require_glob(File.join('lib', '**', '*.rb')) +require_glob(File.join(job_path, '**', '*.rb')) diff --git a/lib/dashing/cli.rb b/lib/dashing/cli.rb index 001a92e..27d8f6b 100644 --- a/lib/dashing/cli.rb +++ b/lib/dashing/cli.rb @@ -10,7 +10,7 @@ module Dashing class << self attr_accessor :auth_token - def CLI.hyphenate(str) + def hyphenate(str) return str.downcase if str =~ /^[A-Z-]+$/ str.gsub('_', '-').gsub(/\B[A-Z]/, '-\&').squeeze('-').downcase end @@ -43,16 +43,7 @@ module Dashing gist = Downloader.get_gist(gist_id) public_url = "https://gist.github.com/#{gist_id}" - gist['files'].each do |file, details| - if file =~ /\.(html|coffee|scss)\z/ - widget_name = File.basename(file, '.*') - new_path = File.join(Dir.pwd, 'widgets', widget_name, file) - create_file(new_path, details['content']) - elsif file.end_with?('.rb') - new_path = File.join(Dir.pwd, 'jobs', file) - create_file(new_path, details['content']) - end - end + install_widget_from_gist(gist) print set_color("Don't forget to edit the ", :yellow) print set_color("Gemfile ", :yellow, :bold) @@ -98,6 +89,19 @@ module Dashing system(command) end + def install_widget_from_gist(gist) + gist['files'].each do |file, details| + if file =~ /\.(html|coffee|scss)\z/ + widget_name = File.basename(file, '.*') + new_path = File.join(Dir.pwd, 'widgets', widget_name, file) + create_file(new_path, details['content']) + elsif file.end_with?('.rb') + new_path = File.join(Dir.pwd, 'jobs', file) + create_file(new_path, details['content']) + end + end + end + def require_file(file) require file end -- cgit v1.2.3 From 333f5b2340add43a097abfb421ccce75f2f85ce5 Mon Sep 17 00:00:00 2001 From: pseudomuto Date: Thu, 19 Dec 2013 13:01:17 -0500 Subject: updating javascripts path --- lib/dashing/app.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing/app.rb b/lib/dashing/app.rb index 5780b92..033849c 100644 --- a/lib/dashing/app.rb +++ b/lib/dashing/app.rb @@ -44,7 +44,7 @@ end settings.sprockets.append_path("assets/#{path}") end -['widgets', File.expand_path('../../javascripts', __FILE__)]. each do |path| +['widgets', File.expand_path('../../../javascripts', __FILE__)]. each do |path| settings.sprockets.append_path(path) end -- cgit v1.2.3 From d3ccecb096821b2df3b492dd349e5c650929a591 Mon Sep 17 00:00:00 2001 From: pushmatrix Date: Fri, 30 May 2014 14:28:51 -0400 Subject: Don't start the dashing app when you run the bin file. --- lib/dashing.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing.rb b/lib/dashing.rb index 855aa36..3527034 100644 --- a/lib/dashing.rb +++ b/lib/dashing.rb @@ -3,4 +3,4 @@ require 'dashing/downloader' require 'dashing/app' module Dashing -end +end \ No newline at end of file -- cgit v1.2.3 From a615548bf13ca517c3d991738cf611f2d6accf08 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Thu, 19 Jun 2014 02:24:53 -0400 Subject: Close event connections when the server is gracefully shutting down. --- lib/dashing/app.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'lib') diff --git a/lib/dashing/app.rb b/lib/dashing/app.rb index 033849c..800f4c8 100644 --- a/lib/dashing/app.rb +++ b/lib/dashing/app.rb @@ -6,6 +6,7 @@ require 'coffee-script' require 'sass' require 'json' require 'yaml' +require 'thin' SCHEDULER = Rufus::Scheduler.new @@ -119,6 +120,16 @@ get '/views/:widget?.html' do end end +Thin::Server.class_eval do + def stop_with_connection_closing + Sinatra::Application.settings.connections.each(&:close) + stop_without_connection_closing + end + + alias_method :stop_without_connection_closing, :stop + alias_method :stop, :stop_with_connection_closing +end + def send_event(id, body, target=nil) body[:id] = id body[:updatedAt] ||= Time.now.to_i -- cgit v1.2.3 From 55f90939eae4d6eb64822fd3590f694418396510 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Fri, 20 Jun 2014 21:13:02 -0400 Subject: Duplicate array of connections before closing them on thin server stop. --- lib/dashing/app.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing/app.rb b/lib/dashing/app.rb index 800f4c8..921bf9c 100644 --- a/lib/dashing/app.rb +++ b/lib/dashing/app.rb @@ -122,7 +122,7 @@ end Thin::Server.class_eval do def stop_with_connection_closing - Sinatra::Application.settings.connections.each(&:close) + Sinatra::Application.settings.connections.dup.each(&:close) stop_without_connection_closing end -- cgit v1.2.3 From e42fc5d3b48b1dfe7d7ca9d60342727245842627 Mon Sep 17 00:00:00 2001 From: Fredrik Vihlborg Date: Thu, 28 Aug 2014 18:13:12 +0200 Subject: Possible to skip overwrites when installing widget. --- lib/dashing/cli.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/dashing/cli.rb b/lib/dashing/cli.rb index 27d8f6b..4b93f89 100644 --- a/lib/dashing/cli.rb +++ b/lib/dashing/cli.rb @@ -38,12 +38,12 @@ module Dashing puts "Invalid generator. Either use widget, dashboard, or job" end - desc "install GIST_ID", "Installs a new widget from a gist." - def install(gist_id) + desc "install GIST_ID [--skip]", "Installs a new widget from a gist (skip overwrite)." + def install(gist_id, *args) gist = Downloader.get_gist(gist_id) public_url = "https://gist.github.com/#{gist_id}" - install_widget_from_gist(gist) + install_widget_from_gist(gist, args.include?('--skip')) print set_color("Don't forget to edit the ", :yellow) print set_color("Gemfile ", :yellow, :bold) @@ -89,15 +89,15 @@ module Dashing system(command) end - def install_widget_from_gist(gist) + def install_widget_from_gist(gist, skip_overwrite) gist['files'].each do |file, details| if file =~ /\.(html|coffee|scss)\z/ widget_name = File.basename(file, '.*') new_path = File.join(Dir.pwd, 'widgets', widget_name, file) - create_file(new_path, details['content']) + create_file(new_path, details['content'], :skip => skip_overwrite) elsif file.end_with?('.rb') new_path = File.join(Dir.pwd, 'jobs', file) - create_file(new_path, details['content']) + create_file(new_path, details['content'], :skip => skip_overwrite) end end end -- cgit v1.2.3 From 937fcce2dcca8010dbe8e9e26df954cb1330c1e0 Mon Sep 17 00:00:00 2001 From: Sven Dahlstrand Date: Mon, 27 Apr 2015 00:43:59 +0200 Subject: Pass `status` option to `send_file` and make sure a 404 is returned. --- lib/dashing/app.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/dashing/app.rb b/lib/dashing/app.rb index 921bf9c..0e7f7bb 100644 --- a/lib/dashing/app.rb +++ b/lib/dashing/app.rb @@ -50,7 +50,7 @@ end end not_found do - send_file File.join(settings.public_folder, '404.html') + send_file File.join(settings.public_folder, '404.html'), status: 404 end at_exit do -- cgit v1.2.3 From f8d316e212d315a13f7d09149f1fd05624a20399 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 8 Dec 2015 15:49:55 -0500 Subject: Refactor and fix authentication --- lib/dashing/app.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/dashing/app.rb b/lib/dashing/app.rb index 0e7f7bb..b11352d 100644 --- a/lib/dashing/app.rb +++ b/lib/dashing/app.rb @@ -23,6 +23,11 @@ helpers do def protected! # override with auth logic end + + def authenticated?(token) + return true unless settings.auth_token + token && Rack::Utils.secure_compare(settings.auth_token, token) + end end set :root, Dir.pwd @@ -89,8 +94,7 @@ post '/dashboards/:id' do request.body.rewind body = JSON.parse(request.body.read) body['dashboard'] ||= params['id'] - auth_token = body.delete("auth_token") - if !settings.auth_token || settings.auth_token == auth_token + if authenticated?(body.delete("auth_token")) send_event(params['id'], body, 'dashboards') 204 # response without entity body else @@ -102,8 +106,7 @@ end post '/widgets/:id' do request.body.rewind body = JSON.parse(request.body.read) - auth_token = body.delete("auth_token") - if !settings.auth_token || settings.auth_token == auth_token + if authenticated?(body.delete("auth_token")) send_event(params['id'], body) 204 # response without entity body else -- cgit v1.2.3