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.
- data/.gitignore +10 -0
- data/.todo +33 -0
- data/.travis.yml +14 -0
- data/Gemfile +43 -0
- data/Guardfile +25 -0
- data/MIT-LICENSE +20 -0
- data/README.md +38 -0
- data/Rakefile +8 -0
- data/TODO +17 -0
- data/bin/watchtower +10 -0
- data/ci/adapters/jruby-mysql.yml +8 -0
- data/ci/adapters/jruby-postgresql.yml +6 -0
- data/ci/adapters/jruby-sqlite.yml +6 -0
- data/ci/adapters/ruby-mysql.yml +8 -0
- data/ci/adapters/ruby-postgresql.yml +6 -0
- data/ci/adapters/ruby-sqlite.yml +6 -0
- data/ci/travis.rb +102 -0
- data/lib/watch_tower.rb +60 -0
- data/lib/watch_tower/appscript.rb +22 -0
- data/lib/watch_tower/cli.rb +15 -0
- data/lib/watch_tower/cli/.gitkeep +0 -0
- data/lib/watch_tower/cli/install.rb +63 -0
- data/lib/watch_tower/cli/open.rb +24 -0
- data/lib/watch_tower/cli/start.rb +140 -0
- data/lib/watch_tower/config.rb +38 -0
- data/lib/watch_tower/core_ext.rb +4 -0
- data/lib/watch_tower/core_ext/.gitkeep +0 -0
- data/lib/watch_tower/editor.rb +17 -0
- data/lib/watch_tower/editor/.gitkeep +0 -0
- data/lib/watch_tower/editor/base_appscript.rb +34 -0
- data/lib/watch_tower/editor/base_ps.rb +6 -0
- data/lib/watch_tower/editor/textmate.rb +17 -0
- data/lib/watch_tower/editor/xcode.rb +22 -0
- data/lib/watch_tower/errors.rb +25 -0
- data/lib/watch_tower/eye.rb +79 -0
- data/lib/watch_tower/project.rb +14 -0
- data/lib/watch_tower/project/.gitkeep +0 -0
- data/lib/watch_tower/project/any_based.rb +22 -0
- data/lib/watch_tower/project/git_based.rb +86 -0
- data/lib/watch_tower/project/init.rb +38 -0
- data/lib/watch_tower/project/path_based.rb +144 -0
- data/lib/watch_tower/server.rb +62 -0
- data/lib/watch_tower/server/.gitkeep +0 -0
- data/lib/watch_tower/server/app.rb +37 -0
- data/lib/watch_tower/server/assets/images/WatchTower.jpg +0 -0
- data/lib/watch_tower/server/assets/images/percentage.png +0 -0
- data/lib/watch_tower/server/assets/javascripts/application.js +3 -0
- data/lib/watch_tower/server/assets/javascripts/percentage.coffee +8 -0
- data/lib/watch_tower/server/assets/stylesheets/application.css +7 -0
- data/lib/watch_tower/server/assets/stylesheets/global.sass +71 -0
- data/lib/watch_tower/server/assets/stylesheets/project.sass +59 -0
- data/lib/watch_tower/server/configurations.rb +10 -0
- data/lib/watch_tower/server/configurations/asset.rb +43 -0
- data/lib/watch_tower/server/database.rb +105 -0
- data/lib/watch_tower/server/db/migrate/001_create_projects.rb +15 -0
- data/lib/watch_tower/server/db/migrate/002_create_files.rb +16 -0
- data/lib/watch_tower/server/db/migrate/003_create_time_entries.rb +12 -0
- data/lib/watch_tower/server/db/migrate/004_create_durations.rb +14 -0
- data/lib/watch_tower/server/db/migrate/005_add_hash_to_time_entries.rb +6 -0
- data/lib/watch_tower/server/db/migrate/006_add_hash_to_files.rb +6 -0
- data/lib/watch_tower/server/decorator.rb +21 -0
- data/lib/watch_tower/server/decorator/application_decorator.rb +91 -0
- data/lib/watch_tower/server/decorator/file_decorator.rb +38 -0
- data/lib/watch_tower/server/decorator/project_decorator.rb +51 -0
- data/lib/watch_tower/server/helpers.rb +13 -0
- data/lib/watch_tower/server/helpers/asset.rb +29 -0
- data/lib/watch_tower/server/helpers/improved_partials.rb +41 -0
- data/lib/watch_tower/server/models/duration.rb +11 -0
- data/lib/watch_tower/server/models/file.rb +31 -0
- data/lib/watch_tower/server/models/project.rb +17 -0
- data/lib/watch_tower/server/models/time_entry.rb +64 -0
- data/lib/watch_tower/server/public/assets/WatchTower-4d6de11e1bd34165ad91ac46fb711bf3.jpg +0 -0
- data/lib/watch_tower/server/public/assets/application-7829b53b5ece1a16d22dc3d00f329023.css +107 -0
- data/lib/watch_tower/server/public/assets/application-e0e6b7731aade460f680331e65cf0682.js +9359 -0
- data/lib/watch_tower/server/public/assets/percentage-d8589e21a5fc85d32a445f531ff8ab95.png +0 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery-ui.js +11729 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery.js +8981 -0
- data/lib/watch_tower/server/vendor/assets/javascripts/jquery_ujs.js +363 -0
- data/lib/watch_tower/server/views/.gitkeep +0 -0
- data/lib/watch_tower/server/views/_file.haml +9 -0
- data/lib/watch_tower/server/views/_project.haml +13 -0
- data/lib/watch_tower/server/views/index.haml +7 -0
- data/lib/watch_tower/server/views/layout.haml +32 -0
- data/lib/watch_tower/server/views/project.haml +12 -0
- data/lib/watch_tower/templates/config.yml +146 -0
- data/lib/watch_tower/templates/watchtower.plist +23 -0
- data/lib/watch_tower/version.rb +8 -0
- data/spec/factories.rb +45 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/active_record.rb +44 -0
- data/spec/support/factory_girl.rb +6 -0
- data/spec/support/launchy.rb +3 -0
- data/spec/support/sinatra.rb +10 -0
- data/spec/support/timecop.rb +7 -0
- data/spec/watch_tower/appscript_spec.rb +6 -0
- data/spec/watch_tower/cli/install_spec.rb +16 -0
- data/spec/watch_tower/cli/open_spec.rb +14 -0
- data/spec/watch_tower/cli/start_spec.rb +17 -0
- data/spec/watch_tower/cli_spec.rb +15 -0
- data/spec/watch_tower/config_spec.rb +25 -0
- data/spec/watch_tower/editor/textmate_spec.rb +43 -0
- data/spec/watch_tower/editor/xcode_spec.rb +43 -0
- data/spec/watch_tower/editor_spec.rb +19 -0
- data/spec/watch_tower/eye_spec.rb +130 -0
- data/spec/watch_tower/project/git_based_spec.rb +131 -0
- data/spec/watch_tower/project/path_based_spec.rb +111 -0
- data/spec/watch_tower/project_spec.rb +82 -0
- data/spec/watch_tower/server/app_spec.rb +186 -0
- data/spec/watch_tower/server/decorator/project_decorator_spec.rb +60 -0
- data/spec/watch_tower/server/models/file_spec.rb +284 -0
- data/spec/watch_tower/server/models/project_spec.rb +165 -0
- data/spec/watch_tower/server/models/time_entry_spec.rb +37 -0
- data/spec/watch_tower/server_spec.rb +4 -0
- data/watch_tower.gemspec +80 -0
- 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,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,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
|
|
Binary file
|
|
@@ -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; }
|