workerholic 0.0.21 → 0.0.22
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.
- checksums.yaml +4 -4
- data/README.md +226 -2
- data/app_test/job_test.rb +1 -1
- data/app_test/run.rb +1 -1
- data/bin/workerholic +1 -1
- data/demo_app/.gitignore +19 -0
- data/demo_app/Gemfile +40 -0
- data/demo_app/README.md +24 -0
- data/demo_app/Rakefile +6 -0
- data/demo_app/app/assets/config/manifest.js +3 -0
- data/demo_app/app/assets/images/.keep +0 -0
- data/demo_app/app/assets/javascripts/application.js +14 -0
- data/demo_app/app/assets/javascripts/cable.js +13 -0
- data/demo_app/app/assets/javascripts/channels/.keep +0 -0
- data/demo_app/app/assets/stylesheets/application.css +15 -0
- data/demo_app/app/assets/stylesheets/main.scss +140 -0
- data/demo_app/app/channels/application_cable/channel.rb +4 -0
- data/demo_app/app/channels/application_cable/connection.rb +4 -0
- data/demo_app/app/controllers/application_controller.rb +3 -0
- data/demo_app/app/controllers/concerns/.keep +0 -0
- data/demo_app/app/controllers/jobs_controller.rb +38 -0
- data/demo_app/app/helpers/application_helper.rb +2 -0
- data/demo_app/app/jobs/application_job.rb +2 -0
- data/demo_app/app/jobs/cpu_bound_job.rb +14 -0
- data/demo_app/app/jobs/io_bound_job.rb +8 -0
- data/demo_app/app/jobs/non_blocking_job.rb +8 -0
- data/demo_app/app/mailers/application_mailer.rb +4 -0
- data/demo_app/app/models/application_record.rb +3 -0
- data/demo_app/app/models/concerns/.keep +0 -0
- data/demo_app/app/views/jobs/_actions_form.html.slim +15 -0
- data/demo_app/app/views/jobs/_footer.html.slim +5 -0
- data/demo_app/app/views/jobs/_header.html.slim +3 -0
- data/demo_app/app/views/jobs/index.html.slim +8 -0
- data/demo_app/app/views/layouts/application.html.slim +15 -0
- data/demo_app/app/views/layouts/mailer.html.erb +13 -0
- data/demo_app/app/views/layouts/mailer.text.erb +1 -0
- data/demo_app/bin/bundle +3 -0
- data/demo_app/bin/rails +9 -0
- data/demo_app/bin/rake +9 -0
- data/demo_app/bin/setup +38 -0
- data/demo_app/bin/spring +17 -0
- data/demo_app/bin/update +29 -0
- data/demo_app/bin/yarn +11 -0
- data/demo_app/config.ru +5 -0
- data/demo_app/config/application.rb +19 -0
- data/demo_app/config/boot.rb +3 -0
- data/demo_app/config/cable.yml +10 -0
- data/demo_app/config/database.yml +85 -0
- data/demo_app/config/environment.rb +5 -0
- data/demo_app/config/environments/development.rb +54 -0
- data/demo_app/config/environments/production.rb +91 -0
- data/demo_app/config/environments/test.rb +42 -0
- data/demo_app/config/initializers/application_controller_renderer.rb +6 -0
- data/demo_app/config/initializers/assets.rb +14 -0
- data/demo_app/config/initializers/backtrace_silencers.rb +7 -0
- data/demo_app/config/initializers/cookies_serializer.rb +5 -0
- data/demo_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/demo_app/config/initializers/inflections.rb +16 -0
- data/demo_app/config/initializers/mime_types.rb +4 -0
- data/demo_app/config/initializers/wrap_parameters.rb +14 -0
- data/demo_app/config/locales/en.yml +33 -0
- data/demo_app/config/puma.rb +56 -0
- data/demo_app/config/routes.rb +6 -0
- data/demo_app/config/secrets.yml +32 -0
- data/demo_app/config/spring.rb +6 -0
- data/demo_app/db/schema.rb +18 -0
- data/demo_app/db/seeds.rb +7 -0
- data/demo_app/lib/assets/.keep +0 -0
- data/demo_app/lib/tasks/.keep +0 -0
- data/demo_app/log/.keep +0 -0
- data/demo_app/package.json +5 -0
- data/demo_app/public/404.html +67 -0
- data/demo_app/public/422.html +67 -0
- data/demo_app/public/500.html +66 -0
- data/demo_app/public/apple-touch-icon-precomposed.png +0 -0
- data/demo_app/public/apple-touch-icon.png +0 -0
- data/demo_app/public/favicon.ico +0 -0
- data/demo_app/public/robots.txt +1 -0
- data/demo_app/tmp/.keep +0 -0
- data/demo_app/vendor/.keep +0 -0
- data/lib/workerholic.rb +1 -1
- data/lib/workerholic/cli.rb +1 -1
- data/lib/workerholic/job_processor.rb +2 -2
- data/lib/workerholic/job_serializer.rb +9 -9
- data/lib/workerholic/queue.rb +1 -1
- data/lib/workerholic/version.rb +1 -1
- data/lib/workerholic/web/application.rb +1 -1
- data/logos.png +0 -0
- data/spec/job_serializer_spec.rb +15 -5
- data/spec/spec_helper.rb +1 -1
- data/spec/statistics_storage_spec.rb +0 -2
- metadata +78 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fdbe7b74f8270c226d2601c566f6a0417a2bc1b5
|
4
|
+
data.tar.gz: b9e159ae9b745c367f79ac92b11711e8650946a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f500485b1718548a16660432deb738ccd9eaa3d8ce29dc407c839d3c4201f8ac13babbf3f7c5d2c7158fbd6880d60a50ab0e19cbd29b058e3e8f210dcc07a8a
|
7
|
+
data.tar.gz: 14360fab31e56ee4f37d4a5db775f7ad8bdfdb47f26e55ff09aa6549618df5207506021878afcf7a9b68254114879fd1e82193db488f055fc727ec07dd2785be
|
data/README.md
CHANGED
@@ -1,2 +1,226 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# Workerholic: background job processor
|
2
|
+
|
3
|
+
## Summary
|
4
|
+
|
5
|
+
Workerholic is a multi-threaded, multi-process background job processing manager. It is an experimental project and as such is not meant to replace other stable engines like Sidekiq or Resque. In fact, Workerholic is inspired in large part by these projects.
|
6
|
+
|
7
|
+
In general, background job managers are great for asynchronous processing of time-consuming tasks like calling third party APIs and performing long calculations. What we are building aims to cover all those use cases.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
### Overview
|
11
|
+
#### Here's a brief overview of Workerholic's data flow:
|
12
|
+
- enqueue a job
|
13
|
+
- job is serialized and added to queue in Redis
|
14
|
+
- workers pick jobs from Redis queues, deserialize and process them
|
15
|
+
- on completion (or failure), relevant statistics are added to Redis
|
16
|
+
- web-ui monitors application metrics and outputs the result
|
17
|
+
|
18
|
+
### Job retry
|
19
|
+
Workerholic will retry every unsuccessfully performed job up to 5 times before placing it into a failed jobs queue
|
20
|
+
|
21
|
+
### Multiple queues
|
22
|
+
It is possible to specify your own queues for each job your application needs to perform. This way every job has its own namespace and can be easily distinguished between multiple jobs.
|
23
|
+
|
24
|
+
### Job scheduler
|
25
|
+
You can schedule a job to be performed at certain time
|
26
|
+
|
27
|
+
### Job persistence in Redis
|
28
|
+
Every job (both active and completed) is stored in a Redis database. This way you will not lose any jobs even if your application crashes
|
29
|
+
|
30
|
+
### Graceful shutdown
|
31
|
+
Workerholic will finish all currently processing jobs before shutting down
|
32
|
+
|
33
|
+
### Web UI with statistics
|
34
|
+
Detailed statistics for processed jobs, queues sizes, overall performance and memory usage
|
35
|
+
|
36
|
+
### Workers provisioning
|
37
|
+
Each worker is treated as a separate entity with its state changing dynamically based on the current number of jobs to perform
|
38
|
+
|
39
|
+
### Auto-balancing
|
40
|
+
By default, each job queue will get a fair number of workers assigned to it. With provided optional argument, Workerholic can dynamically reassign workers to different queues depending on the number of jobs in each queue
|
41
|
+
|
42
|
+
### Multi-process execution
|
43
|
+
Workerholic can be executed in multiple processes to utilize different CPU cores (MRI)
|
44
|
+
|
45
|
+
## Installation
|
46
|
+
### Installing the gem
|
47
|
+
Install the gem with the following command:
|
48
|
+
|
49
|
+
$ gem install workerholic
|
50
|
+
|
51
|
+
Or, add the following line to your application's Gemfile:
|
52
|
+
|
53
|
+
gem 'workerholic', '~> 0.1'
|
54
|
+
|
55
|
+
And then make sure to execute the following command:
|
56
|
+
|
57
|
+
$ bundle install
|
58
|
+
|
59
|
+
### Starting Redis
|
60
|
+
In order to start Redis, execute the following:
|
61
|
+
|
62
|
+
redis-server
|
63
|
+
|
64
|
+
By default, Workerholic will try to connect to Redis via `localhost:6379`.
|
65
|
+
|
66
|
+
For a production environment, make sure to set a `REDIS_URL` environment variable to the address of your redis server.
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
### Including Workerholic
|
70
|
+
|
71
|
+
In order to perform your jobs asynchronously you will need to include Workerholic in your job classes:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
class MyJob
|
75
|
+
include Workerholic::Job
|
76
|
+
|
77
|
+
def perform(args)
|
78
|
+
# job logic goes here
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
It is important to follow this pattern:
|
84
|
+
- include `Workerholic::Job` in your job class
|
85
|
+
- define a `perform` method holding your job's logic
|
86
|
+
|
87
|
+
### Job Options
|
88
|
+
#### Specifying a Queue
|
89
|
+
|
90
|
+
Workerholic allows you to specify a name for the queue that your job will be enqueued in:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class MyJob
|
94
|
+
include Workerholic::Job
|
95
|
+
|
96
|
+
job_options queue_name: 'my_queue'
|
97
|
+
|
98
|
+
def perform(args)
|
99
|
+
# job logic goes here
|
100
|
+
end
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
### Performing a Job Asynchronously
|
105
|
+
|
106
|
+
You can perform a job asynchronously:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
my_job = MyJob.new
|
110
|
+
my_job.perform_async(arg1, arg2)
|
111
|
+
```
|
112
|
+
|
113
|
+
This will ensure that your job is performed in the background, asynchronously, as soon as possible.
|
114
|
+
|
115
|
+
### Scheduling the Job
|
116
|
+
|
117
|
+
You can schedule a job to be executed at a later time:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
my_job = MyJob.new
|
121
|
+
my_job.peform_delayed(100, arg1, arg2)
|
122
|
+
```
|
123
|
+
|
124
|
+
The above example ensures that `my_job` will be performed in 100 seconds.
|
125
|
+
|
126
|
+
## Configuration
|
127
|
+
### Loading Application's Files
|
128
|
+
#### For a Rails Application
|
129
|
+
|
130
|
+
When using Workerholic with a Rails application, as long as you make sure to start Workerholic from the root directory of your Rails application, it will automatically detect your Rails application and eager load your application's files.
|
131
|
+
|
132
|
+
#### For Another Application
|
133
|
+
|
134
|
+
For Workerholic to execute the jobs you enqueue properly it needs to have access to the job classes.
|
135
|
+
|
136
|
+
Make sure to require all classes/dependencies needed with a single file:
|
137
|
+
|
138
|
+
workerholic -r my_app/all_dependencies_needed.rb
|
139
|
+
|
140
|
+
### Setting Number of Workers
|
141
|
+
|
142
|
+
When starting Workerholic you can specify the number of workers you want running and performing jobs:
|
143
|
+
|
144
|
+
workerholic -w 25
|
145
|
+
|
146
|
+
### Setting Number of Redis Connections
|
147
|
+
|
148
|
+
Internally, Workerholic uses a connection pool. By default, Workerholic will create a connection pool with a number of `workers_count + 5` Redis connections.
|
149
|
+
|
150
|
+
In a production environment, you might be limited by the number of concurrent connections to Redis you are allowed to have. Make sure to check what the limit is and you can then start Workerholic by specifying a number of connections:
|
151
|
+
|
152
|
+
workerholic -c 10
|
153
|
+
|
154
|
+
### Setting Number of Processes to Boot
|
155
|
+
|
156
|
+
Workerholic allows you to start multiple processes in parallel by forking children processes from the main process. This can be achieved with the following option:
|
157
|
+
|
158
|
+
workerholic -p 3
|
159
|
+
|
160
|
+
This will allow you to run 3 processes in parallel, with each process having its own workers and connection pool to Redis.
|
161
|
+
|
162
|
+
### Enabling Workers Auto-Balancing Option
|
163
|
+
|
164
|
+
By default, Workerholic will evenly provision workers between job queues.
|
165
|
+
Use the following option if you want to have workers provisioned based on the load for each queue:
|
166
|
+
|
167
|
+
workerholic -a
|
168
|
+
|
169
|
+
This will ensure that each queue will be provisioned with a number of workers based on its relative load compared to the aggregated load for all job queues.
|
170
|
+
|
171
|
+
## Integration
|
172
|
+
### ActiveJob
|
173
|
+
|
174
|
+
Workerholic integrates with ActiveJob. Add the following line to your `application.rb` file:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
class Application < Rails::Application
|
178
|
+
# ...
|
179
|
+
|
180
|
+
config.active_job.queue_adapter = :workerholic
|
181
|
+
|
182
|
+
# ...
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
After that line is added, you can use `ActiveJob`'s API to execute your jobs asynchronously:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
class MyJob < ApplicationJob
|
190
|
+
queue_as: 'my_queue'
|
191
|
+
|
192
|
+
def perform(args)
|
193
|
+
# job logic goes here
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
MyJob.perform_later(args)
|
198
|
+
```
|
199
|
+
|
200
|
+
### Web UI
|
201
|
+
|
202
|
+
Workerholic comes with a Web UI tracking various statistics about the jobs that are being performed.
|
203
|
+
|
204
|
+
#### For a Rails Application
|
205
|
+
|
206
|
+
For a Rails application you will need to mount the Web UI on a specific route. In your `routes.rb` file make sure to add the following:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
require 'workerholic/web/application'
|
210
|
+
|
211
|
+
Rails.application.routes.draw do
|
212
|
+
# ...
|
213
|
+
|
214
|
+
mount WorkerholicWeb => '/workerholic'
|
215
|
+
|
216
|
+
# ...
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
#### For Another Application
|
221
|
+
|
222
|
+
If you are using another kind of application, you can start the Web UI using the following command:
|
223
|
+
|
224
|
+
workerholic-web
|
225
|
+
|
226
|
+
You can then view the the app at: `localhost:4567`
|
data/app_test/job_test.rb
CHANGED
data/app_test/run.rb
CHANGED
data/bin/workerholic
CHANGED
data/demo_app/.gitignore
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile '~/.gitignore_global'
|
6
|
+
|
7
|
+
# Ignore bundler config.
|
8
|
+
/.bundle
|
9
|
+
|
10
|
+
# Ignore all logfiles and tempfiles.
|
11
|
+
/log/*
|
12
|
+
/tmp/*
|
13
|
+
!/log/.keep
|
14
|
+
!/tmp/.keep
|
15
|
+
|
16
|
+
/node_modules
|
17
|
+
/yarn-error.log
|
18
|
+
|
19
|
+
.byebug_history
|
data/demo_app/Gemfile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
git_source(:github) do |repo_name|
|
4
|
+
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
|
5
|
+
"https://github.com/#{repo_name}.git"
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
gem 'rails', '~> 5.1.3'
|
10
|
+
gem 'pg', '~> 0.18'
|
11
|
+
gem 'puma', '~> 3.7'
|
12
|
+
gem 'sass-rails', '~> 5.0'
|
13
|
+
gem 'uglifier', '>= 1.3.0'
|
14
|
+
gem 'coffee-rails', '~> 4.2'
|
15
|
+
#gem 'turbolinks', '~> 5'
|
16
|
+
gem 'jbuilder', '~> 2.5'
|
17
|
+
gem 'slim-rails'
|
18
|
+
gem 'http'
|
19
|
+
gem 'workerholic'
|
20
|
+
|
21
|
+
group :development, :test do
|
22
|
+
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
|
23
|
+
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
|
24
|
+
# Adds support for Capybara system testing and selenium driver
|
25
|
+
gem 'capybara', '~> 2.13'
|
26
|
+
gem 'selenium-webdriver'
|
27
|
+
end
|
28
|
+
|
29
|
+
group :development do
|
30
|
+
# Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
|
31
|
+
gem 'web-console', '>= 3.3.0'
|
32
|
+
gem 'pry-byebug'
|
33
|
+
gem 'listen', '>= 3.0.5', '< 3.2'
|
34
|
+
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
|
35
|
+
# gem 'spring'
|
36
|
+
# gem 'spring-watcher-listen', '~> 2.0.0'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
40
|
+
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
data/demo_app/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
data/demo_app/Rakefile
ADDED
File without changes
|
@@ -0,0 +1,14 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
|
5
|
+
// vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require rails-ujs
|
14
|
+
//= require_tree .
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// Action Cable provides the framework to deal with WebSockets in Rails.
|
2
|
+
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
|
3
|
+
//
|
4
|
+
//= require action_cable
|
5
|
+
//= require_self
|
6
|
+
//= require_tree ./channels
|
7
|
+
|
8
|
+
(function() {
|
9
|
+
this.App || (this.App = {});
|
10
|
+
|
11
|
+
App.cable = ActionCable.createConsumer();
|
12
|
+
|
13
|
+
}).call(this);
|
File without changes
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
|
6
|
+
* vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,140 @@
|
|
1
|
+
html {
|
2
|
+
font-family: Helvetica, sans-serif;
|
3
|
+
box-sizing: border-box;
|
4
|
+
height: 100%;
|
5
|
+
width: 80%;
|
6
|
+
margin: 0 auto;
|
7
|
+
}
|
8
|
+
|
9
|
+
header {
|
10
|
+
width: 70%;
|
11
|
+
margin: 0 auto;
|
12
|
+
text-align: center;
|
13
|
+
|
14
|
+
#app-header {
|
15
|
+
background-color: rgba(243, 243, 243, 0.62);
|
16
|
+
border-radius: 4px;
|
17
|
+
padding: 10px;
|
18
|
+
box-shadow: 1px 1px 1px 0px rgba(0, 0, 0, 0.15);
|
19
|
+
}
|
20
|
+
|
21
|
+
h1 {
|
22
|
+
color: rgba(122, 135, 173, 0.84);
|
23
|
+
cursor: default;
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
main {
|
28
|
+
form.work-form {
|
29
|
+
margin: 50px 0;
|
30
|
+
text-align: left;
|
31
|
+
position: absolute;
|
32
|
+
left: 50%;
|
33
|
+
transform: translateX(-30%);
|
34
|
+
width: 50%;
|
35
|
+
|
36
|
+
fieldset {
|
37
|
+
padding: 0;
|
38
|
+
border: none;
|
39
|
+
}
|
40
|
+
|
41
|
+
fieldset.actions {
|
42
|
+
margin: 50px 0;
|
43
|
+
text-align: center;
|
44
|
+
width: 60%;
|
45
|
+
|
46
|
+
input[type='submit'], button[type='reset'] {
|
47
|
+
width: 100px;
|
48
|
+
height: 28px;
|
49
|
+
-webkit-appearance: none;
|
50
|
+
font-size: 14px;
|
51
|
+
background: #e06e51;
|
52
|
+
border: none;
|
53
|
+
border-radius: 4px;
|
54
|
+
box-shadow: 1px 1px 0.5px 0.5px rgba(0, 0, 0, 0.36);
|
55
|
+
color: rgba(235, 235, 235, 0.90);
|
56
|
+
opacity: .85;
|
57
|
+
cursor: pointer;
|
58
|
+
transition: opacity 200ms ease-in-out, color 200ms ease-in-out;
|
59
|
+
|
60
|
+
&:hover {
|
61
|
+
opacity: 1;
|
62
|
+
color: rgba(235, 235, 235, 1);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
button[type='reset'] {
|
67
|
+
margin-left: 20px;
|
68
|
+
background: #353535;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
label {
|
73
|
+
display: inline-block;
|
74
|
+
margin: 10px 20px 10px 0;
|
75
|
+
width: 30%;
|
76
|
+
color: rgba(0, 0, 0, 0.67);
|
77
|
+
}
|
78
|
+
|
79
|
+
input[type='number'] {
|
80
|
+
display: inline-block;
|
81
|
+
height: 18px;
|
82
|
+
width: 150px;
|
83
|
+
padding: 4px;
|
84
|
+
outline: none;
|
85
|
+
border: 1px solid #000;
|
86
|
+
transition: border-color 200ms ease-in-out;
|
87
|
+
font-size: 14px;
|
88
|
+
border-radius: 2px;
|
89
|
+
|
90
|
+
&:focus {
|
91
|
+
border-color: #e06e51;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
footer {
|
98
|
+
text-align: center;
|
99
|
+
|
100
|
+
#app-footer {
|
101
|
+
border-top: 1px solid rgba(0, 0, 0, 0.09);
|
102
|
+
position: absolute;
|
103
|
+
transform: translateX(-50%);
|
104
|
+
bottom: 10px;
|
105
|
+
left: 50%;
|
106
|
+
|
107
|
+
p {
|
108
|
+
color: #b3b3b3;
|
109
|
+
|
110
|
+
a {
|
111
|
+
color: rgba(0, 0, 0, 0.55);
|
112
|
+
transition: color 200ms ease-in-out;
|
113
|
+
|
114
|
+
&:hover {
|
115
|
+
color: #000;
|
116
|
+
}
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
.message {
|
123
|
+
text-align: center;
|
124
|
+
font-size: 14px;
|
125
|
+
margin: 4px auto;
|
126
|
+
color: rgba(0, 0, 0, 0.65);
|
127
|
+
width: 70%;
|
128
|
+
|
129
|
+
.success {
|
130
|
+
border-radius: 4px;
|
131
|
+
background: #c3e8c3;
|
132
|
+
padding: 6px 0;
|
133
|
+
}
|
134
|
+
|
135
|
+
.error {
|
136
|
+
border-radius: 4px;
|
137
|
+
background: #e8aeae;
|
138
|
+
padding: 6px 0;
|
139
|
+
}
|
140
|
+
}
|