watch_tower 0.0.0.1

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 (115) hide show
  1. data/.gitignore +10 -0
  2. data/.todo +33 -0
  3. data/.travis.yml +14 -0
  4. data/Gemfile +43 -0
  5. data/Guardfile +25 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +38 -0
  8. data/Rakefile +8 -0
  9. data/TODO +17 -0
  10. data/bin/watchtower +10 -0
  11. data/ci/adapters/jruby-mysql.yml +8 -0
  12. data/ci/adapters/jruby-postgresql.yml +6 -0
  13. data/ci/adapters/jruby-sqlite.yml +6 -0
  14. data/ci/adapters/ruby-mysql.yml +8 -0
  15. data/ci/adapters/ruby-postgresql.yml +6 -0
  16. data/ci/adapters/ruby-sqlite.yml +6 -0
  17. data/ci/travis.rb +102 -0
  18. data/lib/watch_tower.rb +60 -0
  19. data/lib/watch_tower/appscript.rb +22 -0
  20. data/lib/watch_tower/cli.rb +15 -0
  21. data/lib/watch_tower/cli/.gitkeep +0 -0
  22. data/lib/watch_tower/cli/install.rb +63 -0
  23. data/lib/watch_tower/cli/open.rb +24 -0
  24. data/lib/watch_tower/cli/start.rb +140 -0
  25. data/lib/watch_tower/config.rb +38 -0
  26. data/lib/watch_tower/core_ext.rb +4 -0
  27. data/lib/watch_tower/core_ext/.gitkeep +0 -0
  28. data/lib/watch_tower/editor.rb +17 -0
  29. data/lib/watch_tower/editor/.gitkeep +0 -0
  30. data/lib/watch_tower/editor/base_appscript.rb +34 -0
  31. data/lib/watch_tower/editor/base_ps.rb +6 -0
  32. data/lib/watch_tower/editor/textmate.rb +17 -0
  33. data/lib/watch_tower/editor/xcode.rb +22 -0
  34. data/lib/watch_tower/errors.rb +25 -0
  35. data/lib/watch_tower/eye.rb +79 -0
  36. data/lib/watch_tower/project.rb +14 -0
  37. data/lib/watch_tower/project/.gitkeep +0 -0
  38. data/lib/watch_tower/project/any_based.rb +22 -0
  39. data/lib/watch_tower/project/git_based.rb +86 -0
  40. data/lib/watch_tower/project/init.rb +38 -0
  41. data/lib/watch_tower/project/path_based.rb +144 -0
  42. data/lib/watch_tower/server.rb +62 -0
  43. data/lib/watch_tower/server/.gitkeep +0 -0
  44. data/lib/watch_tower/server/app.rb +37 -0
  45. data/lib/watch_tower/server/assets/images/WatchTower.jpg +0 -0
  46. data/lib/watch_tower/server/assets/images/percentage.png +0 -0
  47. data/lib/watch_tower/server/assets/javascripts/application.js +3 -0
  48. data/lib/watch_tower/server/assets/javascripts/percentage.coffee +8 -0
  49. data/lib/watch_tower/server/assets/stylesheets/application.css +7 -0
  50. data/lib/watch_tower/server/assets/stylesheets/global.sass +71 -0
  51. data/lib/watch_tower/server/assets/stylesheets/project.sass +59 -0
  52. data/lib/watch_tower/server/configurations.rb +10 -0
  53. data/lib/watch_tower/server/configurations/asset.rb +43 -0
  54. data/lib/watch_tower/server/database.rb +105 -0
  55. data/lib/watch_tower/server/db/migrate/001_create_projects.rb +15 -0
  56. data/lib/watch_tower/server/db/migrate/002_create_files.rb +16 -0
  57. data/lib/watch_tower/server/db/migrate/003_create_time_entries.rb +12 -0
  58. data/lib/watch_tower/server/db/migrate/004_create_durations.rb +14 -0
  59. data/lib/watch_tower/server/db/migrate/005_add_hash_to_time_entries.rb +6 -0
  60. data/lib/watch_tower/server/db/migrate/006_add_hash_to_files.rb +6 -0
  61. data/lib/watch_tower/server/decorator.rb +21 -0
  62. data/lib/watch_tower/server/decorator/application_decorator.rb +91 -0
  63. data/lib/watch_tower/server/decorator/file_decorator.rb +38 -0
  64. data/lib/watch_tower/server/decorator/project_decorator.rb +51 -0
  65. data/lib/watch_tower/server/helpers.rb +13 -0
  66. data/lib/watch_tower/server/helpers/asset.rb +29 -0
  67. data/lib/watch_tower/server/helpers/improved_partials.rb +41 -0
  68. data/lib/watch_tower/server/models/duration.rb +11 -0
  69. data/lib/watch_tower/server/models/file.rb +31 -0
  70. data/lib/watch_tower/server/models/project.rb +17 -0
  71. data/lib/watch_tower/server/models/time_entry.rb +64 -0
  72. data/lib/watch_tower/server/public/assets/WatchTower-4d6de11e1bd34165ad91ac46fb711bf3.jpg +0 -0
  73. data/lib/watch_tower/server/public/assets/application-7829b53b5ece1a16d22dc3d00f329023.css +107 -0
  74. data/lib/watch_tower/server/public/assets/application-e0e6b7731aade460f680331e65cf0682.js +9359 -0
  75. data/lib/watch_tower/server/public/assets/percentage-d8589e21a5fc85d32a445f531ff8ab95.png +0 -0
  76. data/lib/watch_tower/server/vendor/assets/javascripts/jquery-ui.js +11729 -0
  77. data/lib/watch_tower/server/vendor/assets/javascripts/jquery.js +8981 -0
  78. data/lib/watch_tower/server/vendor/assets/javascripts/jquery_ujs.js +363 -0
  79. data/lib/watch_tower/server/views/.gitkeep +0 -0
  80. data/lib/watch_tower/server/views/_file.haml +9 -0
  81. data/lib/watch_tower/server/views/_project.haml +13 -0
  82. data/lib/watch_tower/server/views/index.haml +7 -0
  83. data/lib/watch_tower/server/views/layout.haml +32 -0
  84. data/lib/watch_tower/server/views/project.haml +12 -0
  85. data/lib/watch_tower/templates/config.yml +146 -0
  86. data/lib/watch_tower/templates/watchtower.plist +23 -0
  87. data/lib/watch_tower/version.rb +8 -0
  88. data/spec/factories.rb +45 -0
  89. data/spec/spec_helper.rb +26 -0
  90. data/spec/support/active_record.rb +44 -0
  91. data/spec/support/factory_girl.rb +6 -0
  92. data/spec/support/launchy.rb +3 -0
  93. data/spec/support/sinatra.rb +10 -0
  94. data/spec/support/timecop.rb +7 -0
  95. data/spec/watch_tower/appscript_spec.rb +6 -0
  96. data/spec/watch_tower/cli/install_spec.rb +16 -0
  97. data/spec/watch_tower/cli/open_spec.rb +14 -0
  98. data/spec/watch_tower/cli/start_spec.rb +17 -0
  99. data/spec/watch_tower/cli_spec.rb +15 -0
  100. data/spec/watch_tower/config_spec.rb +25 -0
  101. data/spec/watch_tower/editor/textmate_spec.rb +43 -0
  102. data/spec/watch_tower/editor/xcode_spec.rb +43 -0
  103. data/spec/watch_tower/editor_spec.rb +19 -0
  104. data/spec/watch_tower/eye_spec.rb +130 -0
  105. data/spec/watch_tower/project/git_based_spec.rb +131 -0
  106. data/spec/watch_tower/project/path_based_spec.rb +111 -0
  107. data/spec/watch_tower/project_spec.rb +82 -0
  108. data/spec/watch_tower/server/app_spec.rb +186 -0
  109. data/spec/watch_tower/server/decorator/project_decorator_spec.rb +60 -0
  110. data/spec/watch_tower/server/models/file_spec.rb +284 -0
  111. data/spec/watch_tower/server/models/project_spec.rb +165 -0
  112. data/spec/watch_tower/server/models/time_entry_spec.rb +37 -0
  113. data/spec/watch_tower/server_spec.rb +4 -0
  114. data/watch_tower.gemspec +80 -0
  115. metadata +450 -0
@@ -0,0 +1,21 @@
1
+ require 'draper'
2
+
3
+ module WatchTower
4
+ module Server
5
+ module Decorator
6
+ extend ::ActiveSupport::Autoload
7
+
8
+ autoload :ApplicationDecorator
9
+ autoload :ProjectDecorator
10
+ autoload :FileDecorator
11
+ end
12
+ end
13
+ end
14
+
15
+ # Monkey Patch Draper
16
+ Draper::Base.class_eval <<-END, __FILE__, __LINE__ + 1
17
+ def self.decorates(input)
18
+ self.model_class = "::WatchTower::Server::\#{input.to_s.camelize}".constantize
19
+ model_class.send :include, Draper::ModelSupport
20
+ end
21
+ END
@@ -0,0 +1,91 @@
1
+ module WatchTower
2
+ module Server
3
+ module Decorator
4
+ class ApplicationDecorator < Draper::Base
5
+
6
+ # Returns a human formatted time
7
+ #
8
+ # @return [String] The elapsed time formatted
9
+ def elapsed
10
+ if model.respond_to? :elapsed_time
11
+ humanize_time elapsed_time
12
+ else
13
+ ""
14
+ end
15
+ end
16
+
17
+ protected
18
+ def pluralize(num, word)
19
+ if num > 1
20
+ "#{num} #{word.pluralize}"
21
+ else
22
+ "#{num} #{word}"
23
+ end
24
+ end
25
+
26
+ # Humanize time
27
+ #
28
+ # @param [Integer] The number of seconds
29
+ # @return [String]
30
+ def humanize_time(time)
31
+ case
32
+ when time >= 1.day
33
+ humanize_day(time)
34
+ when time >= 1.hour
35
+ humanize_hour(time)
36
+ when time >= 1.minute
37
+ humanize_minute(time)
38
+ else
39
+ pluralize time, "second"
40
+ end
41
+ end
42
+
43
+ [:day, :hour, :minute].each do |t|
44
+ class_eval <<-END, __FILE__, __LINE__ + 1
45
+ protected
46
+ def humanize_#{t}(time)
47
+ seconds = 1.#{t}
48
+ num = (time / seconds).to_i
49
+ rest = time % seconds
50
+
51
+ time_str = pluralize num, "#{t}"
52
+
53
+ unless rest == 0
54
+ "\#{time_str}#{t == :minute ? ' and' : ','} \#{humanize_time(rest)}"
55
+ else
56
+ time_str
57
+ end
58
+ end
59
+ END
60
+ end
61
+
62
+ # Lazy Helpers
63
+ # PRO: Call Rails helpers without the h. proxy
64
+ # ex: number_to_currency(model.price)
65
+ # CON: Add a bazillion methods into your decorator's namespace
66
+ # and probably sacrifice performance/memory
67
+ #
68
+ # Enable them by uncommenting this line:
69
+ # lazy_helpers
70
+
71
+ # Shared Decorations
72
+ # Consider defining shared methods common to all your models.
73
+ #
74
+ # Example: standardize the formatting of timestamps
75
+ #
76
+ # def formatted_timestamp(time)
77
+ # h.content_tag :span, time.strftime("%a %m/%d/%y"),
78
+ # :class => 'timestamp'
79
+ # end
80
+ #
81
+ # def created_at
82
+ # formatted_timestamp(model.created_at)
83
+ # end
84
+ #
85
+ # def updated_at
86
+ # formatted_timestamp(model.updated_at)
87
+ # end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,38 @@
1
+ module WatchTower
2
+ module Server
3
+ module Decorator
4
+ class FileDecorator < ApplicationDecorator
5
+ decorates :file
6
+
7
+ # Accessing Helpers
8
+ # You can access any helper via a proxy
9
+ #
10
+ # Normal Usage: helpers.number_to_currency(2)
11
+ # Abbreviated : h.number_to_currency(2)
12
+ #
13
+ # Or, optionally enable "lazy helpers" by calling this method:
14
+ # lazy_helpers
15
+ # Then use the helpers with no proxy:
16
+ # number_to_currency(2)
17
+
18
+ # Defining an Interface
19
+ # Control access to the wrapped subject's methods using one of the following:
20
+ #
21
+ # To allow only the listed methods (whitelist):
22
+ # allows :method1, :method2
23
+ #
24
+ # To allow everything except the listed methods (blacklist):
25
+ # denies :method1, :method2
26
+
27
+ # Presentation Methods
28
+ # Define your own instance methods, even overriding accessors
29
+ # generated by ActiveRecord:
30
+ #
31
+ # def created_at
32
+ # h.content_tag :span, time.strftime("%a %m/%d/%y"),
33
+ # :class => 'timestamp'
34
+ # end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ module WatchTower
2
+ module Server
3
+ module Decorator
4
+ class ProjectDecorator < ApplicationDecorator
5
+ decorates :project
6
+
7
+ # Return an image representing the percentage of this project
8
+ #
9
+ # @return [String] image_tag
10
+ def percentage
11
+ max_elapsed = Project.order('elapsed_time DESC').first.elapsed_time
12
+
13
+ percentage = (elapsed_time * 100 / max_elapsed).to_i
14
+
15
+ <<-EHTML
16
+ <img src="#{asset_path('percentage.png')}" data-width="#{percentage}" />
17
+ EHTML
18
+ end
19
+
20
+ # Accessing Helpers
21
+ # You can access any helper via a proxy
22
+ #
23
+ # Normal Usage: helpers.number_to_currency(2)
24
+ # Abbreviated : h.number_to_currency(2)
25
+ #
26
+ # Or, optionally enable "lazy helpers" by calling this method:
27
+ # lazy_helpers
28
+ # Then use the helpers with no proxy:
29
+ # number_to_currency(2)
30
+
31
+ # Defining an Interface
32
+ # Control access to the wrapped subject's methods using one of the following:
33
+ #
34
+ # To allow only the listed methods (whitelist):
35
+ # allows :method1, :method2
36
+ #
37
+ # To allow everything except the listed methods (blacklist):
38
+ # denies :method1, :method2
39
+
40
+ # Presentation Methods
41
+ # Define your own instance methods, even overriding accessors
42
+ # generated by ActiveRecord:
43
+ #
44
+ # def created_at
45
+ # h.content_tag :span, time.strftime("%a %m/%d/%y"),
46
+ # :class => 'timestamp'
47
+ # end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,13 @@
1
+ require 'sinatra-snap'
2
+
3
+ module WatchTower
4
+ module Server
5
+ module Helpers
6
+ extend ::ActiveSupport::Autoload
7
+
8
+ # Sinatra helpers
9
+ autoload :ImprovedPartials
10
+ autoload :Asset
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module WatchTower
2
+ module Server
3
+ module Helpers
4
+ module Asset
5
+
6
+ def self.included(base)
7
+ base.send :include, InstanceMethods
8
+ end
9
+
10
+ module InstanceMethods
11
+
12
+ # Define partial as a helper
13
+ helpers do
14
+ # Get the asset path of a given source
15
+ #
16
+ # Code taken from
17
+ # https://github.com/stevehodgkiss/sinatra-asset-pipeline/blob/master/app.rb#L11
18
+ #
19
+ # @param [String] The source file
20
+ # @return [String] The path to the asset
21
+ def asset_path(source)
22
+ "/assets/" + settings.sprockets.find_asset(source).digest_path
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ module WatchTower
2
+ module Server
3
+ module Helpers
4
+ module ImprovedPartials
5
+
6
+ def self.included(base)
7
+ base.send :include, InstanceMethods
8
+ end
9
+
10
+ module InstanceMethods
11
+
12
+ # Define partial as a helper
13
+ helpers do
14
+ # Render a partial with support for collections
15
+ #
16
+ # stolen from http://github.com/cschneid/irclogger/blob/master/lib/partials.rb
17
+ # and made a lot more robust by Sam Elliott <sam@lenary.co.uk>
18
+ # https://gist.github.com/119874
19
+ #
20
+ # @param [Symbol] The template to render
21
+ # @param [Hash] Options
22
+ def partial(template, *args)
23
+ template_array = template.to_s.split('/')
24
+ template = template_array[0..-2].join('/') + "/_#{template_array[-1]}"
25
+ options = args.last.is_a?(Hash) ? args.pop : {}
26
+ options.merge!(:layout => false)
27
+ if collection = options.delete(:collection) then
28
+ collection.inject([]) do |buffer, member|
29
+ buffer << haml(:"#{template}", options.merge(:layout =>
30
+ false, :locals => {template_array[-1].to_sym => member}))
31
+ end.join("\n")
32
+ else
33
+ haml(:"#{template}", options)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,11 @@
1
+ module WatchTower
2
+ module Server
3
+ class Duration < ::ActiveRecord::Base
4
+ validates :file_id, presence: true
5
+ validates :date, presence: true
6
+ validates :duration, presence: true
7
+
8
+ belongs_to :file, counter_cache: true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module WatchTower
2
+ module Server
3
+ class File < ::ActiveRecord::Base
4
+ # Scopes
5
+ default_scope order('files.elapsed_time DESC')
6
+ scope :worked_on, -> { where('files.elapsed_time > ?', 0) }
7
+
8
+ # Validations
9
+ validates :project_id, presence: true
10
+ validates :path, presence: true
11
+ validates_uniqueness_of :path, sope: :project_id
12
+
13
+ # Associations
14
+ belongs_to :project, counter_cache: true
15
+ has_many :time_entries, dependent: :destroy
16
+ has_many :durations, dependent: :destroy
17
+
18
+ # Return the percent of this file
19
+ def percent
20
+ (elapsed_time * 100) / project.files.sum_elapsed_time
21
+ end
22
+
23
+ # Returns the sum of all elapsed time
24
+ #
25
+ # @return [Integer]
26
+ def self.sum_elapsed_time
27
+ sum(:elapsed_time)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module WatchTower
2
+ module Server
3
+ class Project < ::ActiveRecord::Base
4
+ # Scopes
5
+ default_scope order('projects.elapsed_time DESC')
6
+ scope :worked_on, -> { where('projects.elapsed_time > ?', 0) }
7
+
8
+ # Validations
9
+ validates :name, presence: true
10
+ validates :path, presence: true
11
+
12
+ # Associations
13
+ has_many :files, dependent: :destroy
14
+ has_many :time_entries, through: :files
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,64 @@
1
+ module WatchTower
2
+ module Server
3
+ class TimeEntry < ::ActiveRecord::Base
4
+
5
+ # Default pause time, in case nothing was given in the config file
6
+ DEFAULT_PAUSE_TIME = 30.minutes
7
+
8
+ # Validations
9
+ validates :file_id, presence: true
10
+ validates :mtime, presence: true
11
+ validates_uniqueness_of :mtime, scope: :file_id
12
+ validates :file_hash, presence: true
13
+
14
+
15
+ # Associations
16
+ belongs_to :file, counter_cache: true
17
+
18
+ # Callbacks
19
+ after_create :calculate_elapsed_time
20
+
21
+ protected
22
+ # Calculate the elapsed time between this time entry and the last one
23
+ # then update the durations table with the time difference, either by
24
+ # updating the duration for this day or by creating a new one for the
25
+ # next day
26
+ def calculate_elapsed_time
27
+ # Gather information about this and last time entry for this file
28
+ this_time_entry = self
29
+ last_time_entry = file.time_entries.where('id < ?', this_time_entry.id).order('id DESC').first
30
+ # Check the hash first
31
+ return if this_time_entry.file_hash == last_time_entry.try(:file_hash)
32
+ # Update the file's hash
33
+ file.file_hash = this_time_entry.file_hash
34
+ # Parse the date of the mtime
35
+ this_time_entry_date = self.mtime.to_date
36
+ last_time_entry_date = last_time_entry.mtime.to_date rescue nil
37
+ # Act upon the date
38
+ if this_time_entry_date == last_time_entry_date
39
+ # Calculate the time
40
+ time_entry_elapsed = this_time_entry.mtime - last_time_entry.mtime rescue 0
41
+ unless time_entry_elapsed > pause_time
42
+ # Update the file elapsed time
43
+ file.elapsed_time += time_entry_elapsed
44
+ # Update the project's elapsed time
45
+ file.project.elapsed_time += time_entry_elapsed
46
+ # Add this time to the durations table
47
+ d = file.durations.find_or_create_by_date(this_time_entry_date)
48
+ d.duration += time_entry_elapsed
49
+ d.save
50
+ end
51
+ end
52
+
53
+ # Save the file and project
54
+ file.save
55
+ file.project.save
56
+ end
57
+
58
+ # Get the
59
+ def pause_time
60
+ eval(Config[:pause_time]) rescue DEFAULT_PAUSE_TIME
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,107 @@
1
+ /*
2
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
3
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
5
+ */
6
+ h1 {
7
+ font-size: 2em; }
8
+
9
+ h2 {
10
+ font-size: 1em; }
11
+
12
+ #logo a {
13
+ color: black;
14
+ text-decoration: none;
15
+ display: block;
16
+ background: url("WatchTower-4d6de11e1bd34165ad91ac46fb711bf3.jpg") no-repeat;
17
+ width: 390px;
18
+ height: 94px; }
19
+ #logo a:visited, #logo a:hover {
20
+ text-decoration: none; }
21
+ #logo h1 {
22
+ float: left;
23
+ margin-top: 30px;
24
+ margin-left: 180px; }
25
+
26
+ .clearfix {
27
+ clear: both; }
28
+
29
+ a {
30
+ color: +#b36c6c;
31
+ text-decoration: none; }
32
+ a:hover, a:visited {
33
+ text-decoration: none; }
34
+
35
+ body {
36
+ background: #f7f7f7;
37
+ color: black;
38
+ font-family: Arial, Helvetica, sans-serif; }
39
+ body #wrapper {
40
+ width: 960px;
41
+ margin: 0 auto; }
42
+ body #wrapper #main {
43
+ border: 1px solid #cdcdcd;
44
+ -webkit-border-radius: 5px;
45
+ -moz-border-radius: 5px;
46
+ border-radius: 5px;
47
+ padding: 10px;
48
+ min-height: 200px;
49
+ background: white; }
50
+ body #wrapper #footer {
51
+ width: 960px;
52
+ text-align: center;
53
+ background: #d6afaf;
54
+ color: black;
55
+ padding: 5px 0;
56
+ font-size: 12px; }
57
+ body #wrapper #footer a {
58
+ color: black;
59
+ text-decoration: none; }
60
+ body #wrapper #footer a:visited, body #wrapper #footer a:hover {
61
+ text-decoration: none; }
62
+ #projects header .name {
63
+ float: left;
64
+ width: 200px;
65
+ font-size: 20px;
66
+ text-decoration: underline; }
67
+ #projects header .percentage {
68
+ width: 310px;
69
+ float: left;
70
+ font-size: 20px;
71
+ text-decoration: underline; }
72
+ #projects header .percentage .percentage_img {
73
+ height: 20px; }
74
+ #projects header .elapsed {
75
+ float: left;
76
+ font-size: 20px;
77
+ text-decoration: underline; }
78
+ #projects .project .name {
79
+ float: left;
80
+ width: 200px; }
81
+ #projects .project .percentage_img_container {
82
+ width: 310px;
83
+ float: left; }
84
+ #projects .project .percentage_img_container .percentage_img {
85
+ height: 20px; }
86
+ #projects .project .elapsed {
87
+ float: left; }
88
+
89
+ #project #files header .path {
90
+ font-size: 20px;
91
+ text-decoration: underline;
92
+ float: left;
93
+ width: 600px; }
94
+ #project #files header .elapsed {
95
+ font-size: 20px;
96
+ text-decoration: underline;
97
+ float: left; }
98
+ #project .path {
99
+ float: left;
100
+ width: 600px; }
101
+ #project .elapsed {
102
+ float: left; }
103
+ #project .percentage_img_container {
104
+ width: 310px;
105
+ float: left; }
106
+ #project .percentage_img_container .percentage_img {
107
+ height: 20px; }