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
@@ -0,0 +1,62 @@
1
+
2
+
3
+
4
+ # Simple tab showing a line chart with supporting table
5
+ #
6
+
7
+ server:
8
+ name: 'Simple Demo'
9
+ port: 4050
10
+
11
+ data_sources:
12
+ - id: signal
13
+ table: wave
14
+ database:
15
+ id: sample
16
+ url: sqlite://sample.sq3
17
+
18
+ repositories:
19
+ - id: simple
20
+ type: file
21
+ top_dir: .
22
+
23
+ widgets:
24
+ - id: main
25
+ name: Main
26
+ top_level: true
27
+ priority: 900
28
+ type: layout/two_columns/33_66
29
+ left:
30
+ - name: Introduction
31
+ type: text
32
+ content:
33
+ url: file:simple:introduction.md
34
+ right:
35
+ - type: layout/stacked
36
+ info: This widget shows the output measurements of three stacked generators.
37
+ widgets:
38
+ - name: Signal
39
+ type: data/line_chart3
40
+ data_source:
41
+ name: signal
42
+ mapping:
43
+ x_axis:
44
+ property: t
45
+ y_axis:
46
+ property: y
47
+ max: 2.0
48
+ group_by: oml_sender
49
+ axis:
50
+ x:
51
+ legend: Time (sec)
52
+ y:
53
+ legend: V
54
+ ticks:
55
+ format: ".1f"
56
+
57
+ - name: Table
58
+ type: data/table2
59
+ data_source:
60
+ name: signal
61
+
62
+
@@ -0,0 +1,66 @@
1
+
2
+
3
+
4
+ # Simple tab showing a line chart with supporting table
5
+ #
6
+
7
+ server:
8
+ name: 'Simple Demo'
9
+ port: 4050
10
+
11
+ data_sources:
12
+ - id: signal
13
+ table: wave
14
+ dynamic: 2 # Check every 2 seconds
15
+ limit: 5 # Fetch 5 rows each time
16
+ database:
17
+ id: sample
18
+ url: sqlite://sample.sq3
19
+
20
+ repositories:
21
+ - id: simple
22
+ type: file
23
+ top_dir: .
24
+
25
+ widgets:
26
+ - id: main
27
+ name: Main
28
+ top_level: true
29
+ priority: 900
30
+ type: layout/two_columns/33_66
31
+ left:
32
+ - name: Introduction
33
+ type: text
34
+ content:
35
+ url: file:simple:introduction.md
36
+ right:
37
+ - type: layout/stacked
38
+ info: This widget shows the output measurements of three stacked generators.
39
+ widgets:
40
+ - name: Signal
41
+ type: data/line_chart3
42
+ data_source:
43
+ name: signal
44
+ dynamic: 2
45
+ mapping:
46
+ x_axis:
47
+ property: t
48
+ y_axis:
49
+ property: y
50
+ max: 2.0
51
+ group_by: oml_sender
52
+ axis:
53
+ x:
54
+ legend: Time (sec)
55
+ y:
56
+ legend: V
57
+ ticks:
58
+ format: ".1f"
59
+
60
+ - name: Table
61
+ type: data/table2
62
+ data_source:
63
+ name: signal
64
+ dynamic: 2
65
+
66
+
data/lib/irods4r/file.rb CHANGED
@@ -2,15 +2,15 @@
2
2
  require 'irods4r/directory'
3
3
 
4
4
  module IRODS4r
5
-
5
+
6
6
  #class NotFoundException < Exception; end
7
- class NoFileException < Exception; end
8
- class FileExistsException < Exception; end
9
-
7
+ class NoFileException < Exception; end
8
+ class FileExistsException < Exception; end
9
+
10
10
  # This class proxies a file in an iRODS environment
11
11
  #
12
12
  class File
13
-
13
+
14
14
  # Create a file resource 'path'. If 'must_not_exist' is true,
15
15
  # throw exception if resource already exists.
16
16
  #
@@ -20,34 +20,35 @@ module IRODS4r
20
20
  end
21
21
  self.new(path, opts)
22
22
  end
23
-
24
-
23
+
24
+
25
25
  # Return the content of this file
26
26
  def read()
27
- ICommands.read(@path)
27
+ ICommands.read(@path, @ticket)
28
28
  end
29
-
29
+
30
30
  # Write content to this file.
31
31
  #
32
32
  # WARN: This will overwrite any previous content
33
33
  #
34
34
  def write(content)
35
- ICommands.write(@path, content)
35
+ ICommands.write(@path, content, @ticket)
36
36
  end
37
37
 
38
38
  def file?
39
39
  return true
40
40
  end
41
-
41
+
42
42
  def directory?
43
43
  return false
44
44
  end
45
-
45
+
46
46
  attr_reader :path
47
-
47
+
48
48
  private
49
49
  def initialize(path, opts = {})
50
50
  @path = path
51
+ @ticket = opts[:ticket]
51
52
  end
52
53
  end
53
- end
54
+ end
@@ -2,62 +2,62 @@
2
2
  require 'irods4r'
3
3
 
4
4
  module IRODS4r
5
-
5
+
6
6
  # This module interfaces directly with the IRODS system
7
7
  #
8
8
  module ICommands
9
9
  class ICommandException < IRODS4rException; end
10
-
11
- # Return the list of files found at 'path'.
12
- def self.ls(path)
13
- r = `ils #{path}`
10
+
11
+ # Return the list of files found at 'path'.
12
+ def self.ls(path, ticket = nil)
13
+ r = `ils #{"-t #{ticket}" if ticket} #{path}`
14
14
  #raise ICommandException.new($?) unless $?.exitstatus == 0
15
15
  if r.empty?
16
16
  raise NotFoundException.new("Can't find resource '#{path}'")
17
- end
17
+ end
18
18
  r.lines
19
19
  end
20
-
20
+
21
21
  # Return content of resource at 'path'
22
22
  #
23
- def self.read(path)
23
+ def self.read(path, ticket = nil)
24
24
  f = Tempfile.new('irods4r')
25
- `iget -f #{path} #{f.path}`
25
+ `iget -f #{"-t #{ticket}" if ticket} #{path} #{f.path}`
26
26
  raise ICommandException.new($?) unless $?.exitstatus == 0
27
27
  content = f.read
28
28
  f.close
29
29
  f.unlink
30
30
  content
31
31
  end
32
-
32
+
33
33
  # Return content of resource at 'path'
34
34
  #
35
- def self.write(path, content)
35
+ def self.write(path, content, ticket = nil)
36
36
  f = Tempfile.new('irods4r')
37
37
  f.write(content)
38
38
  f.close
39
- `iput -f #{f.path} #{path}`
39
+ `iput -f #{"-t #{ticket}" if ticket} #{f.path} #{path}`
40
40
  raise ICommandException.new($?) unless $?.exitstatus == 0
41
41
  f.unlink
42
42
  end
43
43
 
44
- def self.exist?(path)
45
- `ils #{path}`
44
+ def self.exist?(path, ticket = nil)
45
+ `ils #{"-t #{ticket}" if ticket} #{path}`
46
46
  $?.exitstatus == 0
47
47
  end
48
-
48
+
49
49
  # Copy the resource at 'path' in iRODS to 'file_path'
50
50
  # in the local file system.
51
51
  #
52
- def self.export(path, file_path, create_parent_path = true)
52
+ def self.export(path, file_path, create_parent_path = true, ticket = nil)
53
53
  #puts ">>>> #{path} -> #{file_path}"
54
54
  if create_parent_path
55
55
  require 'fileutils'
56
56
  FileUtils.mkpath ::File.dirname(file_path)
57
57
  end
58
- `iget -f #{path} #{file_path}`
58
+ `iget -f #{"-t #{ticket}" if ticket} #{path} #{file_path}`
59
59
  raise ICommandException.new($?) unless $?.exitstatus == 0
60
60
  end
61
61
  end #module
62
62
  end # module
63
-
63
+
data/lib/irods4r.rb CHANGED
@@ -1,10 +1,10 @@
1
1
 
2
2
 
3
3
  module IRODS4r
4
-
4
+
5
5
  class IRODS4rException < Exception; end
6
6
  class NotFoundException < IRODS4rException; end
7
- class NoDirectoryException < IRODS4rException; end
7
+ class NoDirectoryException < IRODS4rException; end
8
8
 
9
9
  # Return a IRODS4r object for 'irodsPath' if it exists.
10
10
  #
@@ -12,23 +12,23 @@ module IRODS4r
12
12
  # @param [Hash] opts Options to use for establishing context
13
13
  # @return [Directory|File]
14
14
  #
15
- def self.find(irodsPath = ".", opts = {})
16
- r = ICommands.ls(irodsPath)
15
+ def self.find(irodsPath = ".", opts = {}, ticket = nil)
16
+ r = ICommands.ls(irodsPath, ticket)
17
17
  name = r.to_a[0].strip
18
18
  if name.end_with? ':'
19
19
  Directory.new(name[0 ... -1])
20
20
  else
21
21
  File.new(name)
22
- end
22
+ end
23
23
  end
24
-
24
+
25
25
  # Return true if 'path' exists
26
- def self.exists?(path)
27
- ICommands.exist?(path)
26
+ def self.exists?(path, ticket = nil)
27
+ ICommands.exist?(path, ticket)
28
28
  end
29
29
  end
30
30
 
31
31
  require 'irods4r/directory'
32
32
  require 'irods4r/file'
33
33
  require 'irods4r/icommands'
34
-
34
+
@@ -1,16 +1,19 @@
1
1
 
2
2
  require 'omf_common/lobject'
3
+ require 'rack/accept'
3
4
 
4
5
  use ::Rack::ShowExceptions
5
6
  #use ::Rack::Lint
7
+ use Rack::Accept
6
8
 
7
9
  OMF::Web::Runner.instance.life_cycle(:pre_rackup)
8
10
  options = OMF::Web::Runner.instance.options
11
+ auth_opts = options[:authentication] || {required: false}
9
12
 
10
13
  require 'omf-web/rack/session_authenticator'
11
14
  use OMF::Web::Rack::SessionAuthenticator, #:expire_after => 10,
12
- #:login_url => '/tab/login',
13
- :no_session => ['^/resource/', '^/login', '^/logout']
15
+ login_page_url: auth_opts[:required] ? (auth_opts[:login_url] || '/content/login') : nil,
16
+ no_session: ['^/resource/', '^/auth']
14
17
 
15
18
  map "/resource/vendor/" do
16
19
  require 'omf-web/rack/multi_file'
@@ -52,25 +55,47 @@ end
52
55
  # run OMF::Web::Rack::WidgetMapper.new(options)
53
56
  # end
54
57
 
55
- map '/login' do
58
+ map '/auth/login' do
56
59
  handler = Proc.new do |env|
57
- # req = ::Rack::Request.new(env)
58
- # #puts ">>> post?: #{req.post?} - #{req.params.inspect}"
59
- # if req.post?
60
- # email = req.params["email"]
61
- # pw = req.params["password"]
62
- # remember = req.params["remember"] == "on"
63
- # Authenticator.signon(email, pw, remember)
64
- # end
65
- [301, {'Location' => '/tab', "Content-Type" => ""}, ['Next window!']]
60
+ req = ::Rack::Request.new(env)
61
+ accept = env['rack-accept.request']
62
+ begin
63
+ OMF::Web::Rack::SessionAuthenticator.authenticate_with(req)
64
+ rescue OMF::Web::Rack::AuthenticationFailedException => ax
65
+ if accept.media_type?('application/json')
66
+ body = {authenticated: false, message: ax.to_s}
67
+ next [200, {"Content-Type" => "application/json"}, body.to_json]
68
+ else
69
+ url = auth_opts[:login_url] || '/content/login'
70
+ url = "#{url}?error=#{URI.encode ax.to_s}"
71
+ next [307, {'Location' => url, "Content-Type" => ""}, ['Next window!']]
72
+ end
73
+ end
74
+
75
+ accept = env['rack-accept.request']
76
+ redirect_url = "/?#{rand(10e15)}" # avoid some ugly URL caching
77
+ if accept.media_type?('application/json')
78
+ body = {authenticated: true, redirect: redirect_url}
79
+ [200, {"Content-Type" => "application/json"}, body.to_json]
80
+ else
81
+ [307, {'Location' => redirect_url, "Content-Type" => ""}, ['Next window!']]
82
+ end
66
83
  end
67
84
  run handler
68
85
  end
69
86
 
70
- map '/logout' do
87
+ map '/auth/logout' do
71
88
  handler = Proc.new do |env|
72
89
  OMF::Web::Rack::SessionAuthenticator.logout
73
- [301, {'Location' => '/tab', "Content-Type" => ""}, ['Next window!']]
90
+
91
+ accept = env['rack-accept.request']
92
+ redirect_url = "/?#{rand(10e15)}" # avoid some ugly URL caching
93
+ if accept.media_type?('application/json')
94
+ body = {authenticated: false, redirect: redirect_url}
95
+ [200, {"Content-Type" => "application/json"}, body.to_json]
96
+ else
97
+ [307, {'Location' => redirect_url, "Content-Type" => ""}, ['Next window!']]
98
+ end
74
99
  end
75
100
  run handler
76
101
  end
@@ -80,9 +105,9 @@ map "/" do
80
105
  req = ::Rack::Request.new(env)
81
106
  case req.path_info
82
107
  when '/'
83
- [301, {'Location' => '/tab', "Content-Type" => ""}, ['Next window!']]
108
+ [307, {'Location' => '/tab', "Content-Type" => ""}, ['Next window!']]
84
109
  when '/favicon.ico'
85
- [301, {'Location' => '/resource/image/favicon.ico', "Content-Type" => ""}, ['Next window!']]
110
+ [307, {'Location' => '/resource/image/favicon.ico', "Content-Type" => ""}, ['Next window!']]
86
111
  else
87
112
  OMF::Common::Loggable.logger('rack').warn "Can't handle request '#{req.path_info}'"
88
113
  [401, {"Content-Type" => ""}, "Sorry!"]
@@ -12,10 +12,10 @@ module OMF::Web
12
12
  # This class provides an interface to a GIT repository
13
13
  # It retrieves, archives and versions content.
14
14
  #
15
- class GitContentRepository < ContentRepository
16
-
15
+ class GitContentRepository < ContentRepository
16
+
17
17
  # @@git_repositories = {}
18
- #
18
+ #
19
19
  # # Return the repository which is referenced to by elements in 'opts'.
20
20
  # #
21
21
  # #
@@ -25,8 +25,8 @@ module OMF::Web
25
25
  # end
26
26
  # repo
27
27
  # end
28
-
29
- # Register an existing GIT repo to the system. It will be
28
+
29
+ # Register an existing GIT repo to the system. It will be
30
30
  # consulted for all content url's strarting with
31
31
  # 'git:_top_dir_:'. If 'is_primary' is set to true, it will
32
32
  # become the default repo for all newly created content
@@ -43,7 +43,7 @@ module OMF::Web
43
43
  # @@primary_repository = repo
44
44
  # end
45
45
  # end
46
-
46
+
47
47
  # def self.read_content(url, opts)
48
48
  # unless (a = url.split(':')).length == 3
49
49
  # raise "Expected 'git:some_name:some_path', but got '#{url}'"
@@ -56,13 +56,13 @@ module OMF::Web
56
56
  # end
57
57
 
58
58
  attr_reader :name, :top_dir
59
-
59
+
60
60
  def initialize(name, opts)
61
61
  super
62
62
  @repo = Grit::Repo.new(@top_dir)
63
63
  @url_prefix = "git:#{@name}:"
64
64
  end
65
-
65
+
66
66
  #
67
67
  # Create a URL for a file with 'path' in.
68
68
  # If 'strictly_new' is true, returns nil if 'path' already exists.
@@ -72,8 +72,8 @@ module OMF::Web
72
72
  # # TODO: Need to add code to select proper repository
73
73
  # return GitContentRepository.create_url(path, strictly_new)
74
74
  # end
75
-
76
-
75
+
76
+
77
77
  # Load content described by either a hash or a straightforward path
78
78
  # and return a 'ContentProxy' holding it.
79
79
  #
@@ -88,9 +88,9 @@ module OMF::Web
88
88
  url = @url_prefix + path
89
89
  key = Digest::MD5.hexdigest(url)
90
90
  descr = {}
91
- descr[:url] = url
91
+ descr[:url] = url
92
92
  descr[:url_key] = key
93
- descr[:path] = path
93
+ descr[:path] = path
94
94
  descr[:name] = url # Should be something human digestable
95
95
  if (descr[:strictly_new])
96
96
  Dir.chdir(@top_dir) do
@@ -100,31 +100,31 @@ module OMF::Web
100
100
  proxy = ContentProxy.create(descr, self)
101
101
  return proxy
102
102
  end
103
-
103
+
104
104
  def write(content_descr, content, message)
105
105
  path = _get_path(content_descr)
106
106
  Dir.chdir(@top_dir) do
107
- unless File.writable?(path)
107
+ unless File.writable?(path) || File.writable?(File.dirname(path))
108
108
  raise "Cannot write to file '#{path}'"
109
109
  end
110
110
  f = File.open(path, 'w')
111
111
  f.write(content)
112
112
  f.close
113
-
113
+
114
114
  @repo.add(path)
115
115
  # TODO: Should set info about committing user which should be in thread context
116
- @repo.commit_index(message || 'no message')
116
+ @repo.commit_index(message || 'no message')
117
117
  end
118
118
  end
119
-
119
+
120
120
  # Return a URL for a path in this repo
121
- #
121
+ #
122
122
  def get_url_for_path(path)
123
123
  @url_prefix + path
124
124
  end
125
-
126
-
127
-
125
+
126
+
127
+
128
128
  #
129
129
  # Return an array of file names which are in the repository and
130
130
  # match 'search_pattern'
@@ -134,13 +134,13 @@ module OMF::Web
134
134
  tree = @repo.tree
135
135
  res = []
136
136
  fs = _find_files(search_pattern, tree, nil, res)
137
-
137
+
138
138
  if (mt = opts[:mime_type])
139
139
  fs = fs.select { |f| f[:mime_type] == mt }
140
140
  end
141
141
  fs
142
142
  end
143
-
143
+
144
144
  def _find_files(search_pattern, tree, dir_path, res)
145
145
  tree.contents.each do |e|
146
146
  d = e.name
@@ -151,11 +151,12 @@ module OMF::Web
151
151
  else
152
152
  if long_name.match(search_pattern)
153
153
  mt = mime_type_for_file(e.name)
154
- path = @url_prefix + long_name
155
- res << {:path => path, url => url_for_path(path), :name => e.name,
156
- :mime_type => mt,
157
- #:id => Base64.encode64(long_name).gsub("\n", ''),
158
- :size => e.size, :blob => e.id}
154
+ #path = @url_prefix + long_name
155
+ path = long_name
156
+ res << {path: path, url: get_url_for_path(path), name: e.name,
157
+ mime_type: mt,
158
+ #:id => Base64.encode64(long_name).gsub("\n", ''),
159
+ size: e.size, blob: e.id}
159
160
  end
160
161
  # name = e.name
161
162
  # if File.fnmatch(search_pattern, long_name)
@@ -165,7 +166,7 @@ module OMF::Web
165
166
  end
166
167
  res
167
168
  end
168
-
169
+
169
170
  def _get_path(content_descr)
170
171
  if content_descr.is_a? String
171
172
  path = content_descr.to_s
@@ -191,6 +192,6 @@ module OMF::Web
191
192
  end
192
193
  return path
193
194
  end
194
-
195
+
195
196
  end # class
196
- end # module
197
+ end # module