omf_web 0.9.9 → 1.0.0

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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +31 -0
  3. data/bin/omf_web_server.rb +157 -0
  4. data/doc/screenshot2.png +0 -0
  5. data/doc/widget_detail.png +0 -0
  6. data/example/demo/data_sources/downloads.rb +2 -1
  7. data/example/simple/README.md +12 -13
  8. data/example/simple/create_waveform.rb +29 -0
  9. data/example/simple/introduction.md +17 -0
  10. data/example/simple/sample.sq3 +0 -0
  11. data/example/simple/sample.sql +1008 -0
  12. data/example/simple/simple.yaml +62 -0
  13. data/example/simple/simple_dynamic.yaml +66 -0
  14. data/lib/irods4r/file.rb +15 -14
  15. data/lib/irods4r/icommands.rb +18 -18
  16. data/lib/irods4r.rb +9 -9
  17. data/lib/omf-web/config.ru +41 -16
  18. data/lib/omf-web/content/git_repository.rb +32 -31
  19. data/lib/omf-web/content/irods_repository.rb +34 -33
  20. data/lib/omf-web/content/repository.rb +48 -44
  21. data/lib/omf-web/data_source_proxy.rb +33 -22
  22. data/lib/omf-web/rack/session_authenticator.rb +48 -12
  23. data/lib/omf-web/rack/tab_mapper.rb +30 -36
  24. data/lib/omf-web/rack/websocket_handler.rb +26 -25
  25. data/lib/omf-web/session_store.rb +16 -13
  26. data/lib/omf-web/theme/abstract_page.rb +26 -22
  27. data/lib/omf-web/theme/bright/page.rb +84 -34
  28. data/lib/omf-web/theme/bright/stacked_renderer.rb +20 -19
  29. data/lib/omf-web/theme.rb +14 -9
  30. data/lib/omf-web/thin/runner.rb +38 -36
  31. data/lib/omf-web/thin/server.rb +255 -0
  32. data/lib/omf-web/version.rb +1 -1
  33. data/lib/omf-web/widget/data_widget.rb +6 -6
  34. data/lib/omf-web/widget/text/maruku/helpers.rb +33 -30
  35. data/lib/omf-web/widget/text/maruku/input/parse_block.rb +117 -117
  36. data/lib/omf-web/widget/text/maruku/output/to_html.rb +155 -154
  37. data/lib/omf-web/widget/text/maruku.rb +17 -16
  38. data/omf_web.gemspec +6 -2
  39. data/sample.sq3 +0 -0
  40. data/share/htdocs/graph/js/gauge.js +524 -0
  41. data/share/htdocs/vendor/VERSION_MAP.yaml +3 -3
  42. data/share/htdocs/vendor/backbone-1.0.0/backbone.js +1571 -0
  43. data/share/htdocs/vendor/d3-3.0/LICENSE.brewer.txt +38 -0
  44. data/share/htdocs/vendor/d3-3.0/colorbrewer.js +1 -0
  45. data/share/htdocs/vendor/d3-3.0/d3.js +8810 -0
  46. data/share/htdocs/vendor/d3-3.0/d3.min.js +5 -0
  47. data/share/htdocs/vendor/geo_json/Readme.txt +71 -0
  48. data/share/htdocs/vendor/geo_json/regions.json +41 -0
  49. data/share/htdocs/vendor/geo_json/switzerland.json +24 -0
  50. data/share/htdocs/vendor/geo_json/world.json +497 -0
  51. data/share/htdocs/vendor/nv_d3/js/nv.d3.js +8801 -4447
  52. data/share/htdocs/vendor/spin/jquery.spin.js +46 -0
  53. data/share/htdocs/vendor/spin/spin.js +349 -0
  54. data/share/htdocs/vendor/spin/spin.min.js +1 -0
  55. data/share/htdocs/vendor/underscore-1.4.4/underscore.js +1227 -0
  56. metadata +63 -48
  57. data/example/simple/data_sources/gimi31.sq3 +0 -0
  58. data/example/simple/data_sources/ping_source.rb +0 -56
  59. data/example/simple/simple_viz_server.rb +0 -39
  60. data/example/simple/widgets/charts_tab.yaml +0 -38
  61. data/share/.DS_Store +0 -0
  62. data/share/htdocs/.DS_Store +0 -0
  63. data/share/htdocs/vendor/backbone-0.5.3/backbone.js +0 -1158
  64. data/share/htdocs/vendor/underscore-1.2.1/underscore.js +0 -958
@@ -1,58 +1,59 @@
1
1
 
2
2
  require 'omf-web/theme/bright/widget_chrome'
3
+ require 'omf-web/theme/bright/layout_renderer'
3
4
 
4
5
  module OMF::Web::Theme
5
-
6
- class StackedRenderer
7
-
6
+
7
+ class StackedRenderer < LayoutRenderer
8
+
8
9
  def initialize(stacked_widget, widgets, active_index, opts)
9
10
  @stacked_widget = stacked_widget
10
11
  @widgets = widgets
11
12
  @active_index = active_index
12
13
  @helper = StackedRendererHelper.new(widgets, active_index, opts)
13
14
  @opts = opts
14
- end
15
-
15
+ end
16
+
16
17
  def to_html()
17
18
  wp = "w#{@helper.object_id}"
18
19
  @opts[:menu] = @widgets.each_with_index.map do |w, i|
19
- wc = w.widget_type.split('/').inject([]) do |a, e|
20
- a << (a.empty? ? e : "#{a[-1]}_#{e}")
20
+ wc = w.widget_type.split('/').inject([]) do |a, e|
21
+ a << (a.empty? ? e : "#{a[-1]}_#{e}")
21
22
  end
22
23
  {
23
- :name => w.name,
24
- :class => wc.join(' '),
25
- :is_active => (@active_index == i),
24
+ :name => w.name,
25
+ :class => wc.join(' '),
26
+ :is_active => (@active_index == i),
26
27
  :id => "#{wp}_l_#{i}",
27
28
  :js_function => 'OML.show_widget',
28
29
  :inner_class => wp,
29
30
  :index => i,
30
31
  :widget_id => w.dom_id
31
32
  }
32
- end
33
+ end
33
34
  WidgetChrome.new(@stacked_widget, @helper, @opts).to_html
34
- end
35
- end
35
+ end
36
+ end
36
37
 
37
38
  class StackedRendererHelper < Erector::Widget
38
-
39
+
39
40
  def initialize(widgets, active_index, opts)
40
41
  super opts
41
42
  @widgets = widgets
42
43
  @active_index = active_index
43
- end
44
+ end
44
45
 
45
46
  def content()
46
47
  #widget @active_widget
47
- widgets = @widgets
48
+ widgets = @widgets
48
49
  prefix = "w#{self.object_id}"
49
50
  @widgets.each_with_index do |w, i|
50
51
  style = i == @active_index ? '' : 'display:none'
51
52
  div :id => "#{prefix}_#{i}", :class => prefix, :style => style do
52
- rawtext w.content.to_html
53
+ rawtext w.content.to_html
53
54
  end
54
- end
55
+ end
55
56
  end
56
-
57
+
57
58
  end
58
59
  end
data/lib/omf-web/theme.rb CHANGED
@@ -2,29 +2,34 @@
2
2
 
3
3
  module OMF::Web::Theme
4
4
  extend OMF::Common::Loggable
5
-
5
+
6
6
  DEFAULT_THEME = 'omf-web/theme/bright'
7
7
  @@search_order = [DEFAULT_THEME] # default theme
8
8
  @@loaded = {}
9
9
  @@additional_renderers = {}
10
-
10
+
11
11
  def self.theme=(theme)
12
12
  if theme
13
13
  unless theme.match '.*/'
14
14
  theme = "omf-web/theme/#{theme}" # add default name space
15
15
  end
16
16
  @@loaded = {}
17
- @@search_order = [theme]
17
+ @@search_order = [theme]
18
18
  Kernel::require "#{theme}/init"
19
19
  end
20
20
  end
21
-
21
+
22
+ def self.include_css(css_file)
23
+ ::Kernel.require 'omf-web/theme/abstract_page'
24
+ OMF::Web::Theme::AbstractPage.add_depends_on(:css, "/resource/#{css_file}")
25
+ end
26
+
22
27
  def self.register_renderer(name, klass, theme = DEFAULT_THEME)
23
28
  tr = @@additional_renderers[theme.to_s] ||= {}
24
29
  tr[name] = klass
25
30
  end
26
-
27
- # Set additional themes to search in the given order for
31
+
32
+ # Set additional themes to search in the given order for
28
33
  # implementations of renderes. Allows for partial override
29
34
  # in new themes.
30
35
  #
@@ -32,8 +37,8 @@ module OMF::Web::Theme
32
37
  @@loaded = {}
33
38
  @@search_order = search_order if search_order
34
39
  end
35
-
36
-
40
+
41
+
37
42
  def self.require(name)
38
43
  name = name.to_sym
39
44
  return if @@loaded[name]
@@ -45,7 +50,7 @@ module OMF::Web::Theme
45
50
  Kernel::require "#{theme}/#{name}"
46
51
  end
47
52
  @@loaded[name] = true
48
- debug "Using renderer '#{theme}/#{name}'"
53
+ debug "Using renderer '#{theme}/#{name}'"
49
54
  return
50
55
  rescue LoadError => le
51
56
  # Move on to the next one
@@ -11,26 +11,27 @@ module Thin
11
11
  true # will be verified later
12
12
  end
13
13
  end
14
- end
14
+ end
15
15
 
16
16
  module OMF::Web
17
17
  class Runner < Thin::Runner
18
18
  include OMF::Common::Loggable
19
-
19
+
20
20
  @@instance = nil
21
-
21
+
22
22
  def self.instance
23
23
  @@instance
24
24
  end
25
-
25
+
26
26
  attr_reader :options
27
-
27
+
28
28
  def initialize(argv, opts = {})
29
29
  raise "SINGLETON" if @@instance
30
-
30
+ @@instance = self
31
+
31
32
  @argv = argv
32
33
  sopts = opts.delete(:ssl) # runner has it's own idea of ssl options
33
-
34
+
34
35
  # Default options values
35
36
  app_name = opts[:app_name] || 'omf_web_app'
36
37
  @options = {
@@ -46,68 +47,70 @@ module OMF::Web
46
47
  :max_persistent_conns => Thin::Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS,
47
48
  :require => [],
48
49
  :wait => Thin::Controllers::Cluster::DEFAULT_WAIT_TIME,
49
-
50
+
50
51
  :rackup => File.dirname(__FILE__) + '/../config.ru',
51
52
  :static_dirs => ["#{File.dirname(__FILE__)}/../../../share/htdocs"],
52
53
  :static_dirs_pre => ["./resources"], # directories to prepend to 'static_dirs'
53
-
54
+
54
55
  :handlers => {} # procs to call at various times of the server's life cycle
55
56
  }.merge(opts)
56
57
  # Search path for resource files is concatination of 'pre' and 'standard' static dirs
57
58
  @options[:static_dirs] = @options[:static_dirs_pre].concat(@options[:static_dirs])
58
-
59
-
60
-
59
+
60
+
61
+
61
62
  print_options = false
62
63
  p = parser
63
64
  p.separator ""
64
- p.separator "OMF options:"
65
- p.on("--theme THEME", "Select web theme") do |t| OMF::Web::Theme.theme = t end
66
-
67
- p.separator ""
68
- p.separator "Testing options:"
69
- p.on("--disable-https", "Run server without SSL") do sopts = nil end
70
- p.on("--print-options", "Print option settings after parsing command lines args") do print_options = true end
71
-
65
+ p.separator "OMF Web options:"
66
+ p.on("--theme THEME", "Select web theme") do |t| OMF::Web::Theme.theme = t end
67
+
72
68
  # Allow application to add it's own parsing options
73
69
  if ph = @options[:handlers][:pre_parse]
74
- ph.call(p)
70
+ ph.arity == 1 ? ph.call(p) : ph.call(p, self)
75
71
  end
76
72
 
73
+ p.separator ""
74
+ p.separator "Testing options:"
75
+ p.on("--disable-https", "Run server without SSL") do sopts = nil end
76
+ p.on("--print-options", "Print option settings after parsing command lines args") do print_options = true end
77
+ p.separator ""
78
+
77
79
  parse!
78
- # WHY IS THIS HERE
79
- # unless life_cycle(:post_parse)
80
- # puts p.to_s
81
- # abort()
82
- # end
80
+
83
81
  if sopts
84
82
  @options[:ssl] = true
85
83
  @options[:ssl_key_file] ||= sopts[:key_file]
86
84
  @options[:ssl_cert_file] ||= sopts[:cert_file]
87
85
  @options[:ssl_verify] ||= sopts[:verify_peer]
88
86
  end
87
+ life_cycle(:post_parse)
89
88
 
90
89
  # Change the name of the root logger so we can apply different logging
91
- # policies depending on environment.
90
+ # policies depending on environment.
92
91
  #
93
92
  OMF::Common::Loggable.set_environment @options[:environment]
94
93
 
94
+ if css = opts[:include_css]
95
+ require 'omf-web/theme'
96
+ OMF::Web::Theme.include_css(css)
97
+ end
98
+
95
99
  if print_options
96
100
  require 'pp'
97
101
  pp @options
98
- end
99
-
100
- @@instance = self
102
+ end
103
+
101
104
  end
102
-
105
+
103
106
  def life_cycle(step, &exception_block)
104
107
  begin
105
108
  if (p = @options[:handlers][step])
106
- p.call()
109
+ p.arity == 0 ? p.call() : p.call(self)
107
110
  end
108
111
  rescue => ex
109
112
  if exception_block
110
- begin
113
+ begin
111
114
  exception_block.call(ex)
112
115
  rescue => ex2
113
116
  error ex2
@@ -118,8 +121,8 @@ module OMF::Web
118
121
  debug "#{ex.backtrace.join("\n")}"
119
122
  end
120
123
  end
121
- end
122
-
124
+ end
125
+
123
126
  def run!
124
127
  if theme = @options[:theme]
125
128
  require 'omf-web/theme'
@@ -131,4 +134,3 @@ module OMF::Web
131
134
  end
132
135
 
133
136
 
134
-
@@ -0,0 +1,255 @@
1
+ require 'json'
2
+ require 'omf_common/lobject'
3
+ require 'omf_web'
4
+
5
+ module OMF::Web
6
+
7
+ # Most of the code to run an OMF Web server from a configuration file
8
+ #
9
+ # USAGE:
10
+ #
11
+ #
12
+ class Server < OMF::Common::LObject
13
+
14
+ def self.start(server_name, description, top_dir, opts = {})
15
+ self.new(server_name, description, top_dir, opts)
16
+ end
17
+
18
+ def initialize(server_name, description, top_dir, opts)
19
+ OMF::Common::Loggable.init_log server_name
20
+
21
+ opts = {
22
+ static_dirs_pre: ["#{top_dir}/htdocs"],
23
+ handlers: {
24
+ pre_parse: lambda do |p, runner|
25
+ p.on("--config CONF_FILE", "File holding description of web site") {|f| runner.options[:omf_config_file] = f}
26
+ p.on("--top-dir DIR", "Directory to start from for relative data paths [directory of config file]") {|td| @top_dir = td }
27
+ end,
28
+ post_parse: lambda { |r| load_environment(r.options) },
29
+ },
30
+ authentication: {
31
+ required: false
32
+ }
33
+ }.merge(opts)
34
+
35
+ @top_dir = top_dir
36
+ @databases = {}
37
+
38
+ OMF::Web.start(opts)
39
+ end
40
+
41
+ def load_environment(opts)
42
+ unless cf = opts[:omf_config_file]
43
+ puts "Missing config file"
44
+ abort
45
+ end
46
+
47
+ unless File.readable? cf
48
+ puts "Can't read config file '#{cf}'"
49
+ abort
50
+ end
51
+
52
+ @top_dir ||= File.dirname(cf)
53
+ cfg = _rec_sym_keys(YAML.load_file(cf))
54
+
55
+ (cfg[:server] || {}).each do |k, v|
56
+ k = k.to_sym
57
+ case k
58
+ when :port
59
+ opts[:port] = v.to_i
60
+ else
61
+ opts[k] = v
62
+ end
63
+ end
64
+ (cfg[:data_sources] || []).each do |ds|
65
+ load_datasource(ds)
66
+ end
67
+ (cfg[:repositories] || []).each do |repo|
68
+ load_repository(repo)
69
+ end
70
+
71
+ unless wa = cfg[:widgets]
72
+ puts "Can't find 'widgets' section in config file '#{cf}' - #{cfg.keys}"
73
+ abort
74
+ end
75
+ wa.each do |w|
76
+ OMF::Web.register_widget w
77
+ end
78
+ end
79
+
80
+ def load_datasource(config)
81
+ unless id = config[:id]
82
+ puts "Missing id in datasource configuration"
83
+ abort
84
+ end
85
+ case type = config[:type] || 'database'
86
+ when 'database'
87
+ load_database(config)
88
+ when 'file'
89
+ load_datasource_file(id, config)
90
+ else
91
+ abort "Unknown datasource type '#{type}'."
92
+ end
93
+ end
94
+
95
+ def load_database(config)
96
+ unless table_name = config[:table]
97
+ puts "Missing 'table' in datasource configuration '#{id}'"
98
+ abort
99
+ end
100
+ unless db_cfg = config[:database]
101
+ puts "Missing database configuration in datasource '#{id}'"
102
+ abort
103
+ end
104
+ db = get_database(db_cfg)
105
+ unless table = db.create_table(table_name)
106
+ puts "Can't find table '#{table_name}' in database '#{db}'"
107
+ abort
108
+ end
109
+ OMF::Web.register_datasource table, name: id
110
+ end
111
+
112
+ def get_database(config)
113
+ require 'omf_oml/table'
114
+ require 'omf_oml/sql_source'
115
+
116
+ if config.is_a? String
117
+ if db = @databases[config]
118
+ return db
119
+ end
120
+ puts "Database '#{config}' not defined - (#{@databases.keys})"
121
+ abort
122
+ end
123
+ unless id = config[:id]
124
+ puts "Missing id in database configuration"
125
+ abort
126
+ end
127
+ # unless id = config[:id]
128
+ # puts "Database '#{config}' not defined - (#{@databases.keys})"
129
+ # abort
130
+ # end
131
+ unless url = config[:url]
132
+ puts "Missing URL for database '#{id}'"
133
+ abort
134
+ end
135
+ if url.start_with?('sqlite://') && ! url.start_with?('sqlite:///')
136
+ # inject top dir
137
+ url.insert('sqlite://'.length, @top_dir + '/')
138
+ end
139
+ puts "URL: #{url}"
140
+ begin
141
+ return @databases[id] = OMF::OML::OmlSqlSource.new(url, :check_interval => 3.0)
142
+ rescue Exception => ex
143
+ puts "Can't connect to database '#{id}' - #{ex}"
144
+ abort
145
+ end
146
+ end
147
+
148
+ # The data to be served as a datasource is contained in a file. We
149
+ # currently support CSV with headers, and JSON which turns into a
150
+ # 1 col by 1 row datasource.
151
+ #
152
+ def load_datasource_file(name, opts)
153
+ unless file = opts[:file]
154
+ puts "Data source file is not defined in '#{opts}'"
155
+ abort
156
+ end
157
+ unless file.start_with? '/'
158
+ file = File.join(@top_dir, file)
159
+ end
160
+ unless File.readable? file
161
+ puts "Can't read file '#{file}'"
162
+ abort
163
+ end
164
+ case content_type = opts[:content_type].to_s
165
+ when 'json'
166
+ ds = JSONDataSource.new(file)
167
+ when 'csv'
168
+ require 'omf_oml/csv_table'
169
+ ds = OMF::OML::OmlCsvTable.create name, file, has_csv_header: true
170
+ else
171
+ puts "Unknown content type '#{content_type}'"
172
+ abort
173
+ end
174
+ OMF::Web.register_datasource ds, name: name
175
+ end
176
+
177
+ def load_repository(config)
178
+ unless id = config[:id]
179
+ puts "Missing id in respository configuration"
180
+ abort
181
+ end
182
+ unless type = config[:type]
183
+ puts "Missing 'type' in respository configuration '#{id}'"
184
+ abort
185
+ end
186
+
187
+ require 'omf-web/content/repository'
188
+ case type
189
+ when 'file'
190
+ unless top_dir = config[:top_dir]
191
+ puts "Missing 'top_dir' in respository configuration '#{id}'"
192
+ abort
193
+ end
194
+ unless top_dir.start_with? '/'
195
+ top_dir = File.join(@top_dir, top_dir)
196
+ end
197
+ OMF::Web::ContentRepository.register_repo(id, type: :file, top_dir: top_dir)
198
+ else
199
+ puts "Unknown repository type '#{type}'. Only supporting 'file'."
200
+ abort
201
+ end
202
+
203
+ end
204
+
205
+ # Recusively Symbolize keys of hash
206
+ #
207
+ def _rec_sym_keys(hash)
208
+ h = {}
209
+ hash.each do |k, v|
210
+ if v.is_a? Hash
211
+ v = _rec_sym_keys(v)
212
+ elsif v.is_a? Array
213
+ v = v.map {|e| e.is_a?(Hash) ? _rec_sym_keys(e) : e }
214
+ end
215
+ h[k.to_sym] = v
216
+ end
217
+ h
218
+ end
219
+
220
+
221
+ # This class simulates a DataSource to transfer a JSON file as a database with one row and column
222
+
223
+
224
+ class JSONDataSource < OMF::Common::LObject
225
+
226
+ def initialize(file)
227
+ raw = File.read(file)
228
+ @content = [[JSON.parse(raw)]]
229
+ end
230
+
231
+ # * rows Returns an array of rows
232
+ # * on_content_changed(lambda{action, rows}) Call provided block with actions :added, :removed
233
+ # * create_sliced_table (optional)
234
+ # * release Not exactly sure when that is being used
235
+ # * schema Schema of row
236
+ # * offset
237
+ def rows
238
+ @content
239
+ end
240
+
241
+ def offset
242
+ 0
243
+ end
244
+
245
+ def schema
246
+ require 'omf_oml/schema'
247
+ OMF::OML::OmlSchema.create([[:content]])
248
+ end
249
+
250
+ def on_content_changed(*args)
251
+ # do nothing
252
+ end
253
+ end
254
+ end # class
255
+ end # module
@@ -1,7 +1,7 @@
1
1
 
2
2
  module OMF
3
3
  module Web
4
- VERSION = '0.9.9'
4
+ VERSION = '1.0.0'
5
5
  # Used for finding the example directory
6
6
  TOP_DIR = File.dirname(File.dirname(File.dirname(__FILE__)))
7
7
  end
@@ -28,8 +28,8 @@ module OMF::Web::Widget
28
28
  opts[:js_url] = "graph/js/#{vizType}.js"
29
29
  opts[:js_class] = "OML.#{vizType}"
30
30
  opts[:base_el] = "\##{dom_id}"
31
- super opts
32
-
31
+ super opts
32
+
33
33
  if (ds = opts.delete(:data_source))
34
34
  # single source
35
35
  data_sources = {:default => ds}
@@ -49,10 +49,10 @@ module OMF::Web::Widget
49
49
  end
50
50
  #puts "DTA_WIDGTE>>> #{opts[:data_sources].inspect}"
51
51
  end
52
-
53
- # This is the DOM id which should be used by the renderer for this widget.
52
+
53
+ # This is the DOM id which should be used by the renderer for this widget.
54
54
  # We need to keep this here as various renderes at various levels may need
55
- # to get a reference to it to allow for such functionalities as
55
+ # to get a reference to it to allow for such functionalities as
56
56
  # hiding, stacking, ...
57
57
  def dom_id
58
58
  "w#{object_id.abs}"
@@ -86,7 +86,7 @@ module OMF::Web::Widget
86
86
  # end
87
87
  # end
88
88
  # end
89
- #
89
+ #
90
90
  # def on_ws_close(ws)
91
91
  # raise "ARE WE STILL NEEDING THIS"
92
92
  # @ws = nil