Skip to content

Commit c63c8f1

Browse files
committed
Initial version. 0.1.0 Release
1 parent 9f625f9 commit c63c8f1

26 files changed

+13281
-2
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*DS_STORE

README.md

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,13 @@
1-
allthethings
2-
============
1+
TODO
2+
====
3+
4+
- fix textfield style
5+
- tests
6+
- Create a better readme!
7+
- Add data fetching capabilities
8+
- Turn into its own gem
9+
- Come up with a default style
10+
- Create example widgets
11+
- investigate if Dir.pwd is the best approach to get the local directory
12+
- Create githubpages
13+
- Open source!

bin/att

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env ruby
2+
3+
require 'thor'
4+
require 'net/http'
5+
require 'json'
6+
7+
class MockScheduler
8+
def method_missing(*args)
9+
yield
10+
end
11+
end
12+
13+
def send_event(id, data)
14+
req = Net::HTTP::Post.new("/widgets/#{id}")
15+
req["content-type"] = "application/json"
16+
req.body = JSON.unparse(data.merge(:auth_token => AllTheThings::CLI.auth_token))
17+
res = Net::HTTP.new('localhost', 3000).start { |http| http.request(req) }
18+
puts "Data Sent to #{id}: #{data}"
19+
end
20+
21+
SCHEDULER = MockScheduler.new
22+
23+
module AllTheThings
24+
25+
class CLI < Thor
26+
include Thor::Actions
27+
28+
class << self
29+
attr_accessor :auth_token
30+
end
31+
32+
attr_accessor :name
33+
34+
def self.source_root
35+
File.expand_path('../../templates', __FILE__)
36+
end
37+
38+
desc "install PROJECT_NAME", "Sets up ALL THE THINGS needed for your dashboard project structure."
39+
def install(name)
40+
@name = Thor::Util.snake_case(name)
41+
directory :project, @name
42+
end
43+
44+
desc "new_widget WIDGET_NAME", "Creates a new widget with all the fixins'"
45+
def new_widget(name)
46+
@name = Thor::Util.snake_case(name)
47+
directory :widget, File.join('widgets', @name)
48+
end
49+
50+
desc "start", "Starts the server in style!"
51+
def start(*args)
52+
args = args.join(" ")
53+
system("bundle exec thin -R config.ru start #{args}")
54+
end
55+
56+
desc "job JOB_NAME AUTH_TOKEN(optional)", "Runs the specified job."
57+
def job(name, auth_token = "")
58+
self.class.auth_token = auth_token
59+
f = File.join(Dir.pwd, "jobs", "#{name}.rb")
60+
require f
61+
end
62+
63+
end
64+
end
65+
66+
AllTheThings::CLI.start

lib/allthethings.rb

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
require 'sinatra'
2+
require 'sinatra/content_for'
3+
require 'rufus/scheduler'
4+
require 'coffee-script'
5+
require 'sass'
6+
require 'json'
7+
8+
Dir[File.join(Dir.pwd, 'lib/**/*.rb')].each {|file| require file }
9+
10+
SCHEDULER = Rufus::Scheduler.start_new
11+
12+
set server: 'thin', connections: [], history: {}
13+
helpers Sinatra::ContentFor
14+
15+
def configure(&block)
16+
set :public_folder, Dir.pwd + '/public'
17+
set :views, Dir.pwd + '/dashboards'
18+
set :default_dashboard, nil
19+
instance_eval(&block)
20+
end
21+
22+
get '/events', provides: 'text/event-stream' do
23+
stream :keep_open do |out|
24+
settings.connections << out
25+
out << latest_events
26+
out.callback { settings.connections.delete(out) }
27+
end
28+
end
29+
30+
get '/' do
31+
begin
32+
redirect "/" + (settings.default_dashboard || first_dashboard).to_s
33+
rescue NoMethodError => e
34+
raise Exception.new("There are no dashboards in your dashboard directory.")
35+
end
36+
end
37+
38+
get '/:dashboard' do
39+
erb params[:dashboard].to_sym
40+
end
41+
42+
get '/views/:widget?.html' do
43+
widget = params[:widget]
44+
send_file File.join(Dir.pwd, "widgets/#{widget}/#{widget}.html")
45+
end
46+
47+
post '/widgets/:id' do
48+
request.body.rewind
49+
body = JSON.parse(request.body.read)
50+
auth_token = body.delete("auth_token")
51+
if auth_token == settings.auth_token
52+
send_event(params['id'], body)
53+
204 # response without entity body
54+
else
55+
status 401
56+
"Invalid API key\n"
57+
end
58+
end
59+
60+
def framework_javascripts
61+
['jquery.js', 'es5-shim.js', 'batman.js', 'batman.jquery.js', 'application.coffee', 'widget.coffee'].collect do |f|
62+
File.join(File.expand_path("../../vendor/javascripts", __FILE__), f)
63+
end
64+
end
65+
66+
def widget_javascripts
67+
asset_paths("/widgets/**/*.coffee")
68+
end
69+
70+
def javascripts
71+
(framework_javascripts + widget_javascripts).collect do |f|
72+
if File.extname(f) == ".coffee"
73+
begin
74+
CoffeeScript.compile(File.read(f))
75+
rescue ExecJS::ProgramError => e
76+
message = e.message + ": in #{f}"
77+
raise ExecJS::ProgramError.new(message)
78+
end
79+
else
80+
File.read(f)
81+
end
82+
end.join("\n")
83+
end
84+
85+
def stylesheets
86+
asset_paths("/public/**/*.scss", "/widgets/**/*.scss").collect do |f|
87+
Sass.compile File.read(f)
88+
end.join("\n")
89+
end
90+
91+
def asset_paths(*paths)
92+
paths.inject([]) { |arr, path| arr + Dir[File.join(Dir.pwd, path)] }
93+
end
94+
95+
def send_event(id, body)
96+
body["id"] = id
97+
event = format_event(JSON.unparse(body))
98+
settings.history[id] = event
99+
settings.connections.each { |out| out << event }
100+
end
101+
102+
def format_event(body)
103+
"data: #{body}\n\n"
104+
end
105+
106+
def latest_events
107+
settings.history.inject("") do |str, (id, body)|
108+
str << body
109+
end
110+
end
111+
112+
def first_dashboard
113+
files = Dir[settings.views + "/*.erb"].collect { |f| f.match(/(\w*).erb/)[1] }
114+
files -= ['layout']
115+
files.first
116+
end
117+
118+
files = Dir[Dir.pwd + '/jobs/*.rb']
119+
files.each { |job| require(job) }

templates/project/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Here lies instructions on how to set this thing up.

templates/project/config.ru

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
require 'allthethings'
2+
3+
configure do
4+
set :auth_token, 'YOUR_AUTH_TOKEN'
5+
end
6+
7+
run Sinatra::Application
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8"/>
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6+
7+
<title><%= yield_content(:title) %></title>
8+
9+
<script type="text/javascript"><%= javascripts %></script>
10+
<style><%= stylesheets %></style>
11+
12+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
13+
</head>
14+
<body>
15+
16+
<div class="wrapper">
17+
<%= yield %>
18+
</div>
19+
20+
</body>
21+
</html>

templates/project/dashboards/main.erb

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div data-id="sample" data-view="Sample"></div>

templates/project/jobs/sample.rb

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
SCHEDULER.every '5s' do
2+
sayings = [
3+
"That's one trouble with dual identities, Robin. Dual responsibilities.",
4+
"You know your neosauruses well, Robin. Peanut butter sandwiches it is.",
5+
"You're far from mod, Robin. And many hippies are older than you are.",
6+
"We're still over land, Robin, and a seal is an aquatic, marine mammal.",
7+
"True. You owe your life to dental hygiene.",
8+
"This money goes to building better roads. We all must do our part."
9+
]
10+
11+
send_event('sample', { quote: sayings.sample })
12+
end
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.empty_directory
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.empty_directory
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.empty_directory
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.empty_directory
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.empty_directory
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class AllTheThings.Sample extends AllTheThings.Widget
2+
source: 'sample'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<p>w00t! w00t! You've setup your first dashboard!</p>
2+
<p>To celebrate, enjoy some of these fun Batman quotes!</p>
3+
<span data-bind="quote"></span>
4+

templates/project/widgets/sample/sample.scss

Whitespace-only changes.

templates/widget/%name%.coffee.tt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class AllTheThings.<%= Thor::Util.camel_case(name) %> extends AllTheThings.Widget
2+
source: '<%= name %>'

templates/widget/%name%.html

Whitespace-only changes.

templates/widget/%name%.scss

Whitespace-only changes.

vendor/javascripts/application.coffee

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
Batman.Filters.PrettyNumber = (num) ->
2+
num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") unless isNaN(num)
3+
4+
class window.AllTheThings extends Batman.App
5+
@root ->
6+
7+
Batman.Property.EasingSetter =
8+
get: Batman.Property.defaultAccessor.get
9+
set: (k, to) ->
10+
if isNaN(to)
11+
@[k] = to
12+
else
13+
timer = "interval_#{k}"
14+
num = if !isNaN(@[k]) then @[k] else 0
15+
unless @[timer] || num == to
16+
up = to > num
17+
num_interval = Math.abs(num - to) / 90
18+
@[timer] =
19+
setInterval =>
20+
num = if up then Math.ceil(num+num_interval) else Math.floor(num-num_interval)
21+
if (up && num > to) || (!up && num < to)
22+
num = to
23+
clearInterval(@[timer])
24+
@[timer] = null
25+
delete @[timer]
26+
@[k] = num
27+
@set k, to
28+
@[k] = num
29+
30+
AllTheThings.widgets = widgets = {}
31+
AllTheThings.lastEvents = lastEvents = {}
32+
33+
source = new EventSource('/events')
34+
source.addEventListener 'open', (e)->
35+
console.log("Connection opened")
36+
37+
source.addEventListener 'error', (e)->
38+
console.log("Connection error")
39+
if (e.readyState == EventSource.CLOSED)
40+
console.log("Connection closed")
41+
42+
source.addEventListener 'message', (e) =>
43+
data = JSON.parse(e.data)
44+
lastEvents[data.id] = data
45+
if widgets[data.id]?.length > 0
46+
for widget in widgets[data.id]
47+
widget.onData(data)
48+
49+
50+
$(document).ready ->
51+
AllTheThings.run()

0 commit comments

Comments
 (0)