Merge branch 'staging' of https://github.com/DDDIM/Farmbot-Web-App into staging
commit
483923f50a
3
Procfile
3
Procfile
|
@ -1,4 +1,3 @@
|
|||
background_jobs: bundle exec rake jobs:work
|
||||
log_worker: bin/rails r lib/log_service_runner.rb
|
||||
resource_worker: bin/rails r lib/resource_service_runner.rb
|
||||
rabbit_workers: bin/rails r lib/rabbit_workers.rb
|
||||
web: bundle exec passenger start -p $PORT -e $RAILS_ENV --max-pool-size 3
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# Run Rails & Webpack concurrently
|
||||
rails: rails s -e development -p ${API_PORT:-3000} -b 0.0.0.0
|
||||
log_service: rails r lib/log_service_runner.rb
|
||||
resource_service: rails r lib/resource_service_runner.rb
|
||||
worker: rake jobs:work
|
||||
rails: rails s -e development -p ${API_PORT:-3000} -b 0.0.0.0
|
||||
rabbit_workers: bin/rails r lib/rabbit_workers.rb
|
||||
worker: rake jobs:work
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
# Run Rails & Webpack concurrently
|
||||
rails: rails s -e development -p ${API_PORT:-3000} -b 0.0.0.0
|
||||
log_service: rails r lib/log_service_runner.rb
|
||||
resource_service: rails r lib/resource_service_runner.rb
|
||||
webpack: ./node_modules/.bin/webpack-dev-server --config config/webpack.config.js
|
||||
worker: rake jobs:work
|
||||
rails: rails s -e development -p ${API_PORT:-3000} -b 0.0.0.0
|
||||
rabbit_workers: bin/rails r lib/rabbit_workers.rb
|
||||
webpack: ./node_modules/.bin/webpack-dev-server --config config/webpack.config.js
|
||||
worker: rake jobs:work
|
||||
|
||||
# UNCOMMENT THIS LINE IF YOU ARE DOING MOBILE TESTING:
|
||||
# Get started with `npm install weinre -g`
|
||||
|
|
|
@ -37,6 +37,8 @@ module Api
|
|||
end
|
||||
|
||||
def destroy
|
||||
# TODO: We don't need to do batch requests like this any more.
|
||||
# This should be removed when possible. -RC 1 AUG 2018
|
||||
ids = params[:id].to_s.split(",").map(&:to_i)
|
||||
mutate Points::Destroy.run({point_ids: ids}, device_params)
|
||||
end
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
module Resources
|
||||
class Job < Mutations::Command
|
||||
NOT_FOUND = "Resource not found"
|
||||
NO_CREATE_YET = "You did not put a numeric `id` in the `body`. " +
|
||||
"This would be handled as the creation of a new " +
|
||||
"resource, but we don't support it yet."
|
||||
required do
|
||||
duck :body, methods: [:[], :[]=]
|
||||
duck :resource, duck: [:where, :find_by]
|
||||
|
@ -24,6 +21,8 @@ module Resources
|
|||
when SAVE then do_save
|
||||
else; never
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
add_error :not_found, :not_found, NOT_FOUND
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -42,7 +41,7 @@ module Resources
|
|||
# device_params is ALWAYS last because security.
|
||||
klass::Update.run!(body, model_params, device_params) # Security!
|
||||
else
|
||||
add_error :body, :body, NO_CREATE_YET
|
||||
klass::Create.run!(body, device_params)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
require_relative "../app/lib/service_runner_base.rb"
|
||||
|
||||
begin
|
||||
ServiceRunner.go!(Transport.current.log_channel, LogService)
|
||||
# :nocov:
|
||||
rescue
|
||||
sleep 3
|
||||
retry
|
||||
end
|
||||
# :nocov:
|
|
@ -0,0 +1,50 @@
|
|||
# :nocov:
|
||||
require "thread"
|
||||
require "thwait"
|
||||
|
||||
require_relative "../app/lib/resources.rb"
|
||||
require_relative "../app/lib/resources/job.rb"
|
||||
require_relative "../app/lib/resources/preprocessor.rb"
|
||||
require_relative "../app/lib/resources/service.rb"
|
||||
require_relative "../app/lib/service_runner_base.rb"
|
||||
require_relative "../app/lib/service_runner_base.rb"
|
||||
|
||||
class RabbitWorker
|
||||
WAIT = 3
|
||||
SERVICES = {
|
||||
log_channel: LogService,
|
||||
resource_channel: Resources::Service
|
||||
}
|
||||
|
||||
def run_it!(chan, service)
|
||||
puts " Attempting to connect #{service} to #{chan}"
|
||||
ServiceRunner.go!(Transport.current.send(chan), service)
|
||||
rescue
|
||||
puts "Connecting to broker in #{WAIT} seconds."
|
||||
sleep WAIT
|
||||
retry
|
||||
end
|
||||
|
||||
def thread(channel, service)
|
||||
Thread.new { run_it!(channel, service) }
|
||||
end
|
||||
|
||||
def threads
|
||||
@threads ||= SERVICES.map { |(c,s)| thread(c, s) }
|
||||
end
|
||||
|
||||
def self.go!
|
||||
loop do # TODO: What if only one service
|
||||
ThreadsWait.all_waits(self.new.threads)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
sleep(RabbitWorker::WAIT * 2)
|
||||
|
||||
begin
|
||||
RabbitWorker.go!
|
||||
rescue
|
||||
sleep RabbitWorker::WAIT
|
||||
retry
|
||||
end
|
|
@ -51,7 +51,7 @@
|
|||
"css-loader": "1.0.0",
|
||||
"enzyme": "^3.1.0",
|
||||
"enzyme-adapter-react-16": "^1.1.0",
|
||||
"farmbot": "6.4.2",
|
||||
"farmbot": "6.4.3",
|
||||
"farmbot-toastr": "^1.0.3",
|
||||
"fastclick": "^1.0.6",
|
||||
"file-loader": "1.1.11",
|
||||
|
@ -87,7 +87,7 @@
|
|||
"ts-lint": "^4.5.1",
|
||||
"ts-loader": "4.4.2",
|
||||
"tslint": "5.11.0",
|
||||
"typescript": "2.9.2",
|
||||
"typescript": "3.0.1",
|
||||
"url-loader": "1.0.1",
|
||||
"webpack": "4.16.3",
|
||||
"webpack-uglify-js-plugin": "1.1.9",
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
=> [32m#<FakeTransport:0x000055affcb84618[0m
|
||||
@amqp_topic[32m=[0m
|
||||
[32m#<FakeTransport:0x000055afff2652a0[0m
|
||||
@calls[32m=[0m
|
||||
{[33m:create_channel[0m=>[[]],
|
||||
[33m:topic[0m=>[[[31m[1;31m"[0m[31mamq.topic[1;31m"[0m[31m[0m, {[33m:auto_delete[0m=>[1;36mtrue[0m}]],
|
||||
[33m:publish[0m=>
|
||||
[[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m4649201b-c882-4cdd-9eef-3bb61f4459b4[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:92,[1;35m\"[0m[31mcreated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.266Z[1;35m\"[0m[31m,[1;35m\"[0m[31mupdated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.266Z[1;35m\"[0m[31m,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mElden Goodwin[1;35m\"[0m[31m,[1;35m\"[0m[31memail[1;35m\"[0m[31m:[1;35m\"[0m[31mfaviola@kozey.co[1;35m\"[0m[31m}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_142.sync.User.92[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m4649201b-c882-4cdd-9eef-3bb61f4459b4[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:12,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mOmastarLapras[1;35m\"[0m[31m,[1;35m\"[0m[31mcolor[1;35m\"[0m[31m:null,[1;35m\"[0m[31mdevice_id[1;35m\"[0m[31m:143,[1;35m\"[0m[31min_use[1;35m\"[0m[31m:false,[1;35m\"[0m[31mregimen_items[1;35m\"[0m[31m:[]}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_142.sync.Regimen.12[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m4649201b-c882-4cdd-9eef-3bb61f4459b4[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:11,[1;35m\"[0m[31mstart_time[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-03T20:56:42.275Z[1;35m\"[0m[31m,[1;35m\"[0m[31mend_time[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-05T00:01:00.000Z[1;35m\"[0m[31m,[1;35m\"[0m[31mrepeat[1;35m\"[0m[31m:3,[1;35m\"[0m[31mtime_unit[1;35m\"[0m[31m:[1;35m\"[0m[31mhourly[1;35m\"[0m[31m,[1;35m\"[0m[31mexecutable_id[1;35m\"[0m[31m:12,[1;35m\"[0m[31mexecutable_type[1;35m\"[0m[31m:[1;35m\"[0m[31mRegimen[1;35m\"[0m[31m,[1;35m\"[0m[31mcalendar[1;35m\"[0m[31m:[]}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_142.sync.FarmEvent.11[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m4559a4a8-1878-49df-98c7-b7ba9daccd00[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:144,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mRed Leader[1;35m\"[0m[31m,[1;35m\"[0m[31mtimezone[1;35m\"[0m[31m:[1;35m\"[0m[31mAfrica/Tunis[1;35m\"[0m[31m,[1;35m\"[0m[31mlast_saw_api[1;35m\"[0m[31m:null,[1;35m\"[0m[31mlast_saw_mq[1;35m\"[0m[31m:null,[1;35m\"[0m[31mtz_offset_hrs[1;35m\"[0m[31m:1,[1;35m\"[0m[31mfbos_version[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_until[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_at[1;35m\"[0m[31m:null}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_143.sync.Device.144[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m4559a4a8-1878-49df-98c7-b7ba9daccd00[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:93,[1;35m\"[0m[31mcreated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.300Z[1;35m\"[0m[31m,[1;35m\"[0m[31mupdated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.300Z[1;35m\"[0m[31m,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mPerla Schowalter[1;35m\"[0m[31m,[1;35m\"[0m[31memail[1;35m\"[0m[31m:[1;35m\"[0m[31mmurrayortiz@trantow.com[1;35m\"[0m[31m}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_143.sync.User.93[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31md133ad1f-80d6-4059-9e2b-3f0c366d4454[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:1,[1;35m\"[0m[31murl[1;35m\"[0m[31m:[1;35m\"[0m[31murl1[1;35m\"[0m[31m,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mname1[1;35m\"[0m[31m,[1;35m\"[0m[31mupdated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.313Z[1;35m\"[0m[31m,[1;35m\"[0m[31mcreated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.313Z[1;35m\"[0m[31m}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_144.sync.WebcamFeed.1[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31md133ad1f-80d6-4059-9e2b-3f0c366d4454[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:145,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mGray Leader[1;35m\"[0m[31m,[1;35m\"[0m[31mtimezone[1;35m\"[0m[31m:[1;35m\"[0m[31mAmerica/Kralendijk[1;35m\"[0m[31m,[1;35m\"[0m[31mlast_saw_api[1;35m\"[0m[31m:null,[1;35m\"[0m[31mlast_saw_mq[1;35m\"[0m[31m:null,[1;35m\"[0m[31mtz_offset_hrs[1;35m\"[0m[31m:-4,[1;35m\"[0m[31mfbos_version[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_until[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_at[1;35m\"[0m[31m:null}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_144.sync.Device.145[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31md133ad1f-80d6-4059-9e2b-3f0c366d4454[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:94,[1;35m\"[0m[31mcreated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.335Z[1;35m\"[0m[31m,[1;35m\"[0m[31mupdated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.335Z[1;35m\"[0m[31m,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mDr. Suzi Miller[1;35m\"[0m[31m,[1;35m\"[0m[31memail[1;35m\"[0m[31m:[1;35m\"[0m[31mkathaleengoldner@gerlachdenesik.biz[1;35m\"[0m[31m}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_144.sync.User.94[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31md133ad1f-80d6-4059-9e2b-3f0c366d4454[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:2,[1;35m\"[0m[31murl[1;35m\"[0m[31m:[1;35m\"[0m[31mUrl![1;35m\"[0m[31m,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mName![1;35m\"[0m[31m,[1;35m\"[0m[31mupdated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.341Z[1;35m\"[0m[31m,[1;35m\"[0m[31mcreated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.341Z[1;35m\"[0m[31m}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_144.sync.WebcamFeed.2[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m6374acef-2b38-45e3-a03d-7ee95b80eca1[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:146,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mGreen 2[1;35m\"[0m[31m,[1;35m\"[0m[31mtimezone[1;35m\"[0m[31m:[1;35m\"[0m[31mAustralia/Broken_Hill[1;35m\"[0m[31m,[1;35m\"[0m[31mlast_saw_api[1;35m\"[0m[31m:null,[1;35m\"[0m[31mlast_saw_mq[1;35m\"[0m[31m:null,[1;35m\"[0m[31mtz_offset_hrs[1;35m\"[0m[31m:9,[1;35m\"[0m[31mfbos_version[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_until[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_at[1;35m\"[0m[31m:null}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_145.sync.Device.146[1;31m"[0m[31m[0m}]]}[32m>[0m,
|
||||
@calls[32m=[0m{},
|
||||
@connection[32m=[0m
|
||||
[32m#<FakeTransport:0x000055afff2652a0[0m
|
||||
@calls[32m=[0m
|
||||
{[33m:create_channel[0m=>[[]],
|
||||
[33m:topic[0m=>[[[31m[1;31m"[0m[31mamq.topic[1;31m"[0m[31m[0m, {[33m:auto_delete[0m=>[1;36mtrue[0m}]],
|
||||
[33m:publish[0m=>
|
||||
[[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m4649201b-c882-4cdd-9eef-3bb61f4459b4[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:92,[1;35m\"[0m[31mcreated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.266Z[1;35m\"[0m[31m,[1;35m\"[0m[31mupdated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.266Z[1;35m\"[0m[31m,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mElden Goodwin[1;35m\"[0m[31m,[1;35m\"[0m[31memail[1;35m\"[0m[31m:[1;35m\"[0m[31mfaviola@kozey.co[1;35m\"[0m[31m}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_142.sync.User.92[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m4649201b-c882-4cdd-9eef-3bb61f4459b4[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:12,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mOmastarLapras[1;35m\"[0m[31m,[1;35m\"[0m[31mcolor[1;35m\"[0m[31m:null,[1;35m\"[0m[31mdevice_id[1;35m\"[0m[31m:143,[1;35m\"[0m[31min_use[1;35m\"[0m[31m:false,[1;35m\"[0m[31mregimen_items[1;35m\"[0m[31m:[]}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_142.sync.Regimen.12[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m4649201b-c882-4cdd-9eef-3bb61f4459b4[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:11,[1;35m\"[0m[31mstart_time[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-03T20:56:42.275Z[1;35m\"[0m[31m,[1;35m\"[0m[31mend_time[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-05T00:01:00.000Z[1;35m\"[0m[31m,[1;35m\"[0m[31mrepeat[1;35m\"[0m[31m:3,[1;35m\"[0m[31mtime_unit[1;35m\"[0m[31m:[1;35m\"[0m[31mhourly[1;35m\"[0m[31m,[1;35m\"[0m[31mexecutable_id[1;35m\"[0m[31m:12,[1;35m\"[0m[31mexecutable_type[1;35m\"[0m[31m:[1;35m\"[0m[31mRegimen[1;35m\"[0m[31m,[1;35m\"[0m[31mcalendar[1;35m\"[0m[31m:[]}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_142.sync.FarmEvent.11[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m4559a4a8-1878-49df-98c7-b7ba9daccd00[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:144,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mRed Leader[1;35m\"[0m[31m,[1;35m\"[0m[31mtimezone[1;35m\"[0m[31m:[1;35m\"[0m[31mAfrica/Tunis[1;35m\"[0m[31m,[1;35m\"[0m[31mlast_saw_api[1;35m\"[0m[31m:null,[1;35m\"[0m[31mlast_saw_mq[1;35m\"[0m[31m:null,[1;35m\"[0m[31mtz_offset_hrs[1;35m\"[0m[31m:1,[1;35m\"[0m[31mfbos_version[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_until[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_at[1;35m\"[0m[31m:null}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_143.sync.Device.144[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m4559a4a8-1878-49df-98c7-b7ba9daccd00[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:93,[1;35m\"[0m[31mcreated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.300Z[1;35m\"[0m[31m,[1;35m\"[0m[31mupdated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.300Z[1;35m\"[0m[31m,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mPerla Schowalter[1;35m\"[0m[31m,[1;35m\"[0m[31memail[1;35m\"[0m[31m:[1;35m\"[0m[31mmurrayortiz@trantow.com[1;35m\"[0m[31m}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_143.sync.User.93[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31md133ad1f-80d6-4059-9e2b-3f0c366d4454[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:1,[1;35m\"[0m[31murl[1;35m\"[0m[31m:[1;35m\"[0m[31murl1[1;35m\"[0m[31m,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mname1[1;35m\"[0m[31m,[1;35m\"[0m[31mupdated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.313Z[1;35m\"[0m[31m,[1;35m\"[0m[31mcreated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.313Z[1;35m\"[0m[31m}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_144.sync.WebcamFeed.1[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31md133ad1f-80d6-4059-9e2b-3f0c366d4454[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:145,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mGray Leader[1;35m\"[0m[31m,[1;35m\"[0m[31mtimezone[1;35m\"[0m[31m:[1;35m\"[0m[31mAmerica/Kralendijk[1;35m\"[0m[31m,[1;35m\"[0m[31mlast_saw_api[1;35m\"[0m[31m:null,[1;35m\"[0m[31mlast_saw_mq[1;35m\"[0m[31m:null,[1;35m\"[0m[31mtz_offset_hrs[1;35m\"[0m[31m:-4,[1;35m\"[0m[31mfbos_version[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_until[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_at[1;35m\"[0m[31m:null}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_144.sync.Device.145[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31md133ad1f-80d6-4059-9e2b-3f0c366d4454[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:94,[1;35m\"[0m[31mcreated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.335Z[1;35m\"[0m[31m,[1;35m\"[0m[31mupdated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.335Z[1;35m\"[0m[31m,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mDr. Suzi Miller[1;35m\"[0m[31m,[1;35m\"[0m[31memail[1;35m\"[0m[31m:[1;35m\"[0m[31mkathaleengoldner@gerlachdenesik.biz[1;35m\"[0m[31m}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_144.sync.User.94[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31md133ad1f-80d6-4059-9e2b-3f0c366d4454[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:2,[1;35m\"[0m[31murl[1;35m\"[0m[31m:[1;35m\"[0m[31mUrl![1;35m\"[0m[31m,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mName![1;35m\"[0m[31m,[1;35m\"[0m[31mupdated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.341Z[1;35m\"[0m[31m,[1;35m\"[0m[31mcreated_at[1;35m\"[0m[31m:[1;35m\"[0m[31m2018-08-02T20:56:42.341Z[1;35m\"[0m[31m}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_144.sync.WebcamFeed.2[1;31m"[0m[31m[0m}],
|
||||
[[31m[1;31m"[0m[31m{[1;35m\"[0m[31margs[1;35m\"[0m[31m:{[1;35m\"[0m[31mlabel[1;35m\"[0m[31m:[1;35m\"[0m[31m6374acef-2b38-45e3-a03d-7ee95b80eca1[1;35m\"[0m[31m},[1;35m\"[0m[31mbody[1;35m\"[0m[31m:{[1;35m\"[0m[31mid[1;35m\"[0m[31m:146,[1;35m\"[0m[31mname[1;35m\"[0m[31m:[1;35m\"[0m[31mGreen 2[1;35m\"[0m[31m,[1;35m\"[0m[31mtimezone[1;35m\"[0m[31m:[1;35m\"[0m[31mAustralia/Broken_Hill[1;35m\"[0m[31m,[1;35m\"[0m[31mlast_saw_api[1;35m\"[0m[31m:null,[1;35m\"[0m[31mlast_saw_mq[1;35m\"[0m[31m:null,[1;35m\"[0m[31mtz_offset_hrs[1;35m\"[0m[31m:9,[1;35m\"[0m[31mfbos_version[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_until[1;35m\"[0m[31m:null,[1;35m\"[0m[31mthrottled_at[1;35m\"[0m[31m:null}}[1;31m"[0m[31m[0m,
|
||||
{[33m:routing_key[0m=>[31m[1;31m"[0m[31mbot.device_145.sync.Device.146[1;31m"[0m[31m[0m}]]}[32m>[0m[32m>[0m
|
|
@ -15,34 +15,15 @@ describe LogService do
|
|||
FakeDeliveryInfo.new("bot.device_#{device_id}.logs")
|
||||
end
|
||||
|
||||
class FakeLogChan
|
||||
attr_reader :subcribe_calls
|
||||
|
||||
def initialize
|
||||
@subcribe_calls = 0
|
||||
end
|
||||
|
||||
def subscribe(*)
|
||||
@subcribe_calls += 1
|
||||
end
|
||||
it "has a log_channel" do
|
||||
calls = Transport.current.log_channel.calls[:bind]
|
||||
expect(calls).to include(["amq.topic", {routing_key: "bot.*.logs"}])
|
||||
end
|
||||
|
||||
it "calls .subscribe() on Transport." do
|
||||
Transport.current.clear!
|
||||
load "./lib/log_service_runner.rb"
|
||||
arg1 = Transport.current.connection.calls[:subscribe].last[0]
|
||||
routing_key = Transport.current.connection.calls[:bind].last[1][:routing_key]
|
||||
expect(arg1).to eq({block: true})
|
||||
expect(routing_key).to eq("bot.*.logs")
|
||||
end
|
||||
|
||||
it "calls .subscribe() on Transport." do
|
||||
Transport.current.clear!
|
||||
load "./lib/resource_service_runner.rb"
|
||||
arg1 = Transport.current.connection.calls[:subscribe].last[0]
|
||||
routing_key = Transport.current.connection.calls[:bind].last[1][:routing_key]
|
||||
expect(arg1).to eq({block: true})
|
||||
expect(routing_key).to eq("bot.*.resources_v0.#")
|
||||
it "has a resource_channel" do
|
||||
calls = Transport.current.resource_channel.calls[:bind]
|
||||
expect(calls)
|
||||
.to include(["amq.topic", {routing_key: "bot.*.resources_v0.#"}])
|
||||
end
|
||||
|
||||
it "creates new messages in the DB when called" do
|
||||
|
|
|
@ -67,16 +67,54 @@ describe Resources::Job do
|
|||
expect(result.name).to eq("Heyo!")
|
||||
end
|
||||
|
||||
it "does not support `create` yet" do
|
||||
device = FactoryBot.create(:device)
|
||||
it "updates a tool" do
|
||||
tool = FactoryBot.create(:tool)
|
||||
result = Resources::Job.run!(body: {name: "Heyo!"},
|
||||
resource: Tool,
|
||||
resource_id: tool.id,
|
||||
device: tool.device,
|
||||
action: "save",
|
||||
uuid: "whatever")
|
||||
expect(result).to be_kind_of(Tool)
|
||||
expect(result.name).to eq("Heyo!")
|
||||
end
|
||||
|
||||
it "can't update someone elses tool" do
|
||||
theirs = FactoryBot.create(:tool)
|
||||
them = theirs.device
|
||||
me = FactoryBot.create(:device)
|
||||
result = Resources::Job.run(body: {name: "Heyo!"},
|
||||
resource: Point,
|
||||
resource_id: 0,
|
||||
device: device,
|
||||
resource: Tool,
|
||||
resource_id: theirs.id,
|
||||
device: me,
|
||||
action: "save",
|
||||
uuid: "whatever")
|
||||
expect(result.errors.fetch("body").message)
|
||||
.to eq(Resources::Job::NO_CREATE_YET)
|
||||
expect(result.errors.message_list).to include(Resources::Job::NOT_FOUND)
|
||||
expect(theirs.reload.name).not_to eq("Heyo!")
|
||||
end
|
||||
|
||||
it "updates a saved_garden" do
|
||||
saved_garden = FactoryBot.create(:saved_garden)
|
||||
result = Resources::Job.run!(body: {name: "Heyo!"},
|
||||
resource: SavedGarden,
|
||||
resource_id: saved_garden.id,
|
||||
device: saved_garden.device,
|
||||
action: "save",
|
||||
uuid: "whatever")
|
||||
expect(result).to be_kind_of(SavedGarden)
|
||||
expect(result.name).to eq("Heyo!")
|
||||
end
|
||||
|
||||
it "updates a plant_template" do
|
||||
plant_template = FactoryBot.create(:plant_template)
|
||||
result = Resources::Job.run!(body: {name: "Heyo!"},
|
||||
resource: PlantTemplate,
|
||||
resource_id: plant_template.id,
|
||||
device: plant_template.device,
|
||||
action: "save",
|
||||
uuid: "whatever")
|
||||
expect(result).to be_kind_of(PlantTemplate)
|
||||
expect(result.name).to eq("Heyo!")
|
||||
end
|
||||
|
||||
it "deals with points" do
|
||||
|
@ -96,4 +134,27 @@ describe Resources::Job do
|
|||
expect(res.where(discarded_at: nil).count).to eq(count - 1)
|
||||
end
|
||||
end
|
||||
|
||||
it "creates a point" do
|
||||
device = FactoryBot.create(:device)
|
||||
body = { name: SecureRandom.uuid,
|
||||
x: 1,
|
||||
y: 1,
|
||||
z: 1,
|
||||
radius: 1,
|
||||
meta: {} }
|
||||
result = Resources::Job.run!(body: body,
|
||||
resource: Point,
|
||||
resource_id: 0,
|
||||
device: device,
|
||||
action: "save",
|
||||
uuid: "whatever")
|
||||
expect(result).to be_kind_of(GenericPointer)
|
||||
expect(result[:x]).to eq(1)
|
||||
expect(result[:y]).to eq(1)
|
||||
expect(result[:z]).to eq(1)
|
||||
expect(result[:radius]).to eq(1)
|
||||
expect(result[:meta]).to eq({})
|
||||
expect(Point.where(name: body[:name]).count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,11 +22,10 @@ import {
|
|||
TaggedSavedGarden,
|
||||
TaggedPlantTemplate,
|
||||
} from "farmbot";
|
||||
import { ExecutableType } from "../../farm_designer/interfaces";
|
||||
import { fakeResource } from "../fake_resource";
|
||||
import { emptyToolSlot } from "../../tools/components/empty_tool_slot";
|
||||
import { FirmwareConfig } from "../../config_storage/firmware_configs";
|
||||
import { PinBindingType } from "../../devices/pin_bindings/interfaces";
|
||||
import { ExecutableType, PinBindingType } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
export let resources: Everything["resources"] = buildResourceIndex();
|
||||
let idCounter = 1;
|
||||
|
|
|
@ -2,25 +2,25 @@
|
|||
// /questions/32911630/how-do-i-deal-with-localstorage-in-jest-tests
|
||||
|
||||
// https://github.com/facebook/jest/issues/2098
|
||||
function whatever() {
|
||||
function Whatever() {
|
||||
var store = {};
|
||||
|
||||
return {
|
||||
store,
|
||||
clear() {
|
||||
store = {};
|
||||
},
|
||||
getItem(key) {
|
||||
return store[key];
|
||||
},
|
||||
setItem(key, value) {
|
||||
store[key] = value.toString();
|
||||
},
|
||||
removeItem(key) {
|
||||
delete store[key];
|
||||
}
|
||||
}
|
||||
store.isFakeStore = true;
|
||||
|
||||
store.getItem = (key) => {
|
||||
return store[key];
|
||||
};
|
||||
|
||||
store.setItem = (key, value) => {
|
||||
store[key] = value;
|
||||
};
|
||||
|
||||
store.removeItem = (key) => {
|
||||
store[key] = undefined;
|
||||
};
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
global.localStorage = whatever();
|
||||
global.sessionStorage = whatever();
|
||||
global.localStorage = Whatever();
|
||||
global.sessionStorage = Whatever();
|
||||
|
|
|
@ -13,7 +13,7 @@ describe("fetchStoredToken", () => {
|
|||
});
|
||||
|
||||
it("can fetch token", () => {
|
||||
localStorage["session"] = JSON.stringify(auth);
|
||||
localStorage.setItem("session", JSON.stringify(auth));
|
||||
expect(Session.fetchStoredToken()).toEqual(auth);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -79,7 +79,7 @@ export const fetchLabFeatures = (): LabsFeature[] => ([
|
|||
description: t(Content.VIRTUAL_TRAIL),
|
||||
storageKey: BooleanSetting.display_trail,
|
||||
value: false,
|
||||
callback: () => sessionStorage.virtualTrailRecords = "[]"
|
||||
callback: () => sessionStorage.setItem("virtualTrailRecords", "[]")
|
||||
},
|
||||
].map(fetchRealValue));
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ import {
|
|||
} from "../../connect_device";
|
||||
import { onLogs } from "../../log_handlers";
|
||||
import { Actions, Content } from "../../../constants";
|
||||
import { Log } from "../../../interfaces";
|
||||
import { Log } from "farmbot/dist/resources/api_resources";
|
||||
import { ALLOWED_CHANNEL_NAMES, ALLOWED_MESSAGE_TYPES, Farmbot } from "farmbot";
|
||||
import { success, error, info, warning } from "farmbot-toastr";
|
||||
import { dispatchNetworkUp, dispatchNetworkDown } from "../../index";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { fetchNewDevice, getDevice } from "../device";
|
||||
import { dispatchNetworkUp, dispatchNetworkDown } from "./index";
|
||||
import { Log } from "../interfaces";
|
||||
import { Log } from "farmbot/dist/resources/api_resources";
|
||||
import { Farmbot, BotStateTree, TaggedResource, SpecialStatus } from "farmbot";
|
||||
import { noop, throttle } from "lodash";
|
||||
import { success, error, info, warning } from "farmbot-toastr";
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from "./connect_device";
|
||||
import { GetState } from "../redux/interfaces";
|
||||
import { dispatchNetworkDown } from ".";
|
||||
import { Log } from "../interfaces";
|
||||
import { Log } from "farmbot/dist/resources/api_resources";
|
||||
import * as _ from "lodash";
|
||||
import { globalQueue } from "./batch_queue";
|
||||
|
||||
|
|
|
@ -5,12 +5,6 @@ export interface PeripheralState {
|
|||
isEditing: boolean;
|
||||
}
|
||||
|
||||
export interface Peripheral {
|
||||
id?: number;
|
||||
pin: number | undefined;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface PeripheralFormProps {
|
||||
dispatch: Function;
|
||||
peripherals: TaggedPeripheral[];
|
||||
|
|
|
@ -3,7 +3,7 @@ import axios from "axios";
|
|||
import * as _ from "lodash";
|
||||
import { success, warning, info, error } from "farmbot-toastr";
|
||||
import { getDevice } from "../device";
|
||||
import { Log, Everything } from "../interfaces";
|
||||
import { Everything } from "../interfaces";
|
||||
import {
|
||||
GithubRelease, MoveRelProps, MinOsFeatureLookup, SourceFwConfig, Axis
|
||||
} from "./interfaces";
|
||||
|
@ -28,6 +28,7 @@ import { getFbosConfig } from "../resources/selectors_by_kind";
|
|||
import { FbosConfig } from "../config_storage/fbos_configs";
|
||||
import { FirmwareConfig } from "../config_storage/firmware_configs";
|
||||
import { CONFIG_DEFAULTS } from "farmbot/dist/config";
|
||||
import { Log } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
const ON = 1, OFF = 0;
|
||||
export type ConfigKey = keyof McuParams;
|
||||
|
|
|
@ -33,7 +33,7 @@ export function HomingRow(props: HomingRowProps) {
|
|||
<LockableButton
|
||||
disabled={disabled || botDisconnected}
|
||||
onClick={() => findHome(axis)}>
|
||||
{t("HOME {{axis}}", { axis })}
|
||||
{t("FIND HOME {{axis}}", { axis })}
|
||||
</LockableButton>
|
||||
</Col>;
|
||||
})}
|
||||
|
|
|
@ -20,9 +20,7 @@ import {
|
|||
fakeSequence
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { initSave } from "../../../api/crud";
|
||||
import {
|
||||
PinBindingInputGroupProps, PinBindingType, PinBindingSpecialAction
|
||||
} from "../interfaces";
|
||||
import { PinBindingInputGroupProps } from "../interfaces";
|
||||
import {
|
||||
PinBindingInputGroup, PinNumberInputGroup, BindingTypeDropDown,
|
||||
ActionTargetDropDown, SequenceTargetDropDown
|
||||
|
@ -31,6 +29,7 @@ import { error, warning } from "farmbot-toastr";
|
|||
import {
|
||||
fakeResourceIndex
|
||||
} from "../../../sequences/step_tiles/tile_move_absolute/test_helpers";
|
||||
import { PinBindingType, PinBindingSpecialAction } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
describe("<PinBindingInputGroup/>", () => {
|
||||
function fakeProps(): PinBindingInputGroupProps {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { PinBindingType, PinBindingSpecialAction } from "farmbot/dist/resources/api_resources";
|
||||
const mockDevice = {
|
||||
registerGpio: jest.fn(() => { return Promise.resolve(); }),
|
||||
unregisterGpio: jest.fn(() => { return Promise.resolve(); }),
|
||||
|
@ -10,7 +11,6 @@ jest.mock("../../../api/crud", () => ({
|
|||
destroy: jest.fn()
|
||||
}));
|
||||
|
||||
import { PinBindingSpecialAction, PinBindingType, } from "../interfaces";
|
||||
const mockData = [{
|
||||
pin_number: 1, sequence_id: undefined,
|
||||
special_action: PinBindingSpecialAction.sync,
|
||||
|
|
|
@ -8,9 +8,12 @@ import {
|
|||
import {
|
||||
fakeSequence, fakePinBinding
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { PinBindingsProps } from "../interfaces";
|
||||
import {
|
||||
PinBindingsProps, PinBindingType, PinBindingSpecialAction, SpecialPinBinding
|
||||
} from "../interfaces";
|
||||
SpecialPinBinding,
|
||||
PinBindingType,
|
||||
PinBindingSpecialAction
|
||||
} from "farmbot/dist/resources/api_resources";
|
||||
|
||||
describe("<PinBindings/>", () => {
|
||||
function fakeProps(): PinBindingsProps {
|
||||
|
@ -62,7 +65,7 @@ describe("<PinBindings/>", () => {
|
|||
p.shouldDisplay = () => true;
|
||||
const wrapper = mount(<PinBindings {...p} />);
|
||||
["pin bindings", "pin number", "none", "bind", "stock bindings"]
|
||||
.map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
.map(string => expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
["26", "action"].map(string =>
|
||||
expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
const buttons = wrapper.find("button");
|
||||
|
|
|
@ -1,36 +1,10 @@
|
|||
import { BotState, ShouldDisplay } from "../interfaces";
|
||||
import { NetworkState } from "../../connectivity/interfaces";
|
||||
import { ResourceIndex } from "../../resources/interfaces";
|
||||
|
||||
export type PinBinding = StandardPinBinding | SpecialPinBinding;
|
||||
|
||||
interface PinBindingBase { id?: number; pin_num: number; }
|
||||
|
||||
export enum PinBindingType {
|
||||
special = "special",
|
||||
standard = "standard",
|
||||
}
|
||||
|
||||
interface StandardPinBinding extends PinBindingBase {
|
||||
binding_type: PinBindingType.standard;
|
||||
sequence_id: number;
|
||||
}
|
||||
|
||||
export interface SpecialPinBinding extends PinBindingBase {
|
||||
binding_type: PinBindingType.special;
|
||||
special_action: PinBindingSpecialAction;
|
||||
}
|
||||
|
||||
export enum PinBindingSpecialAction {
|
||||
emergency_lock = "emergency_lock",
|
||||
emergency_unlock = "emergency_unlock",
|
||||
sync = "sync",
|
||||
reboot = "reboot",
|
||||
power_off = "power_off",
|
||||
dump_info = "dump_info",
|
||||
read_status = "read_status",
|
||||
take_photo = "take_photo",
|
||||
}
|
||||
import {
|
||||
PinBindingType,
|
||||
PinBindingSpecialAction
|
||||
} from "farmbot/dist/resources/api_resources";
|
||||
|
||||
export interface PinBindingsProps {
|
||||
bot: BotState;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { t } from "i18next";
|
||||
import { PinBindingType, PinBindingSpecialAction } from "./interfaces";
|
||||
import {
|
||||
PinBindingType,
|
||||
PinBindingSpecialAction
|
||||
} from "farmbot/dist/resources/api_resources";
|
||||
import { DropDownItem } from "../../ui";
|
||||
import { gpio } from "./rpi_gpio_diagram";
|
||||
import { flattenDeep, isNumber } from "lodash";
|
||||
|
|
|
@ -5,8 +5,8 @@ import { PinBindingColWidth } from "./pin_bindings";
|
|||
import { Popover, Position } from "@blueprintjs/core";
|
||||
import { RpiGpioDiagram } from "./rpi_gpio_diagram";
|
||||
import {
|
||||
PinBindingType, PinBindingSpecialAction,
|
||||
PinBindingInputGroupProps, PinBindingInputGroupState
|
||||
PinBindingInputGroupProps,
|
||||
PinBindingInputGroupState
|
||||
} from "./interfaces";
|
||||
import { isNumber, includes } from "lodash";
|
||||
import { Feature, ShouldDisplay } from "../interfaces";
|
||||
|
@ -22,6 +22,7 @@ import {
|
|||
} from "./list_and_label_support";
|
||||
import { SequenceSelectBox } from "../../sequences/sequence_select_box";
|
||||
import { ResourceIndex } from "../../resources/interfaces";
|
||||
import { PinBindingType, PinBindingSpecialAction } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
export class PinBindingInputGroup
|
||||
extends React.Component<PinBindingInputGroupProps, PinBindingInputGroupState> {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Feature, BotState } from "../interfaces";
|
|||
import { selectAllPinBindings } from "../../resources/selectors";
|
||||
import { MustBeOnline } from "../must_be_online";
|
||||
import {
|
||||
PinBinding, PinBindingSpecialAction, PinBindingType, PinBindingsProps,
|
||||
PinBindingsProps,
|
||||
PinBindingListItems
|
||||
} from "./interfaces";
|
||||
import { PinBindingsList } from "./pin_bindings_list";
|
||||
|
@ -16,6 +16,11 @@ import {
|
|||
} from "./tagged_pin_binding_init";
|
||||
import { ResourceIndex } from "../../resources/interfaces";
|
||||
import { Popover, Position, PopoverInteractionKind } from "@blueprintjs/core";
|
||||
import {
|
||||
PinBindingSpecialAction,
|
||||
PinBindingType,
|
||||
PinBinding
|
||||
} from "farmbot/dist/resources/api_resources";
|
||||
|
||||
/** Width of UI columns in Pin Bindings widget. */
|
||||
export enum PinBindingColWidth {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import * as React from "react";
|
||||
import {
|
||||
PinBindingType, PinBindingSpecialAction, PinBinding, PinBindingListItems
|
||||
} from "./interfaces";
|
||||
PinBindingType,
|
||||
PinBindingSpecialAction,
|
||||
PinBinding
|
||||
} from "farmbot/dist/resources/api_resources";
|
||||
import { PinBindingListItems } from "./interfaces";
|
||||
import { TaggedPinBinding, SpecialStatus } from "farmbot";
|
||||
import { ShouldDisplay, Feature } from "../interfaces";
|
||||
import { stockPinBindings } from "./list_and_label_support";
|
||||
|
|
|
@ -63,7 +63,7 @@ describe("<FarmDesigner/>", () => {
|
|||
}
|
||||
|
||||
it("loads default map settings", () => {
|
||||
localStorage["showPoints"] = "false";
|
||||
localStorage.setItem("showPoints", "false");
|
||||
const wrapper = mount(<FarmDesigner {...fakeProps()} />);
|
||||
const legendProps = wrapper.find("GardenMapLegend").props() as GardenMapLegendProps;
|
||||
expect(legendProps.legendMenuOpen).toBeFalsy();
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from "../../../__test_support__/resource_index_builder";
|
||||
import * as moment from "moment";
|
||||
import { countBy } from "lodash";
|
||||
import { TimeUnit } from "../../interfaces";
|
||||
import { TimeUnit } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
function testState() {
|
||||
|
|
|
@ -10,11 +10,11 @@ import { entries } from "../../resources/util";
|
|||
import { Link } from "react-router";
|
||||
import {
|
||||
AddEditFarmEventProps,
|
||||
TaggedExecutable,
|
||||
ExecutableType
|
||||
TaggedExecutable
|
||||
} from "../interfaces";
|
||||
import { BackArrow } from "../../ui/index";
|
||||
import { SpecialStatus } from "farmbot";
|
||||
import { ExecutableType } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
interface State {
|
||||
uuid: string;
|
||||
|
|
|
@ -7,9 +7,9 @@ import {
|
|||
gracePeriodSeconds
|
||||
} from "../scheduler";
|
||||
import * as moment from "moment";
|
||||
import { TimeUnit } from "../../../interfaces";
|
||||
import { Moment } from "moment";
|
||||
import { range, padStart } from "lodash";
|
||||
import { TimeUnit } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
describe("scheduler", () => {
|
||||
it("runs every 4 hours, starting Tu, until Th w/ origin of Mo", () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { FarmEvent, ExecutableType } from "../../interfaces";
|
||||
import { Regimen } from "../../../regimens/interfaces";
|
||||
import { Sequence } from "../../../sequences/interfaces";
|
||||
import { ExecutableType, FarmEvent } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
/** Would it be better to make a fully formed farm event? Join regimen, sequence, etc. */
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as moment from "moment";
|
||||
import { Moment, unitOfTime } from "moment";
|
||||
import { range } from "lodash";
|
||||
import { TimeUnit } from "../../interfaces";
|
||||
import { NEVER } from "../edit_fe_form";
|
||||
import { TimeUnit } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
interface SchedulerProps {
|
||||
startTime: Moment;
|
||||
|
|
|
@ -5,9 +5,7 @@ import { success, error } from "farmbot-toastr";
|
|||
import {
|
||||
TaggedFarmEvent, SpecialStatus, TaggedSequence, TaggedRegimen
|
||||
} from "farmbot";
|
||||
import {
|
||||
TimeUnit, ExecutableQuery, ExecutableType, FarmEvent
|
||||
} from "../interfaces";
|
||||
import { ExecutableQuery } from "../interfaces";
|
||||
import { formatTime, formatDate } from "./map_state_to_props_add_edit";
|
||||
import {
|
||||
BackArrow,
|
||||
|
@ -31,6 +29,7 @@ import { EventTimePicker } from "./event_time_picker";
|
|||
import { TzWarning } from "./tz_warning";
|
||||
import { nextRegItemTimes } from "./map_state_to_props";
|
||||
import { first } from "lodash";
|
||||
import { TimeUnit, ExecutableType, FarmEvent } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
type FormEvent = React.SyntheticEvent<HTMLInputElement>;
|
||||
export const NEVER: TimeUnit = "never";
|
||||
|
|
|
@ -5,9 +5,9 @@ import {
|
|||
} from "../../ui/index";
|
||||
import { repeatOptions } from "./map_state_to_props_add_edit";
|
||||
import { keyBy } from "lodash";
|
||||
import { TimeUnit } from "../interfaces";
|
||||
import { FarmEventViewModel } from "./edit_fe_form";
|
||||
import { EventTimePicker } from "./event_time_picker";
|
||||
import { TimeUnit } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
type Ev = React.SyntheticEvent<HTMLInputElement>;
|
||||
type Key = keyof FarmEventViewModel;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AddEditFarmEventProps, ExecutableType } from "../interfaces";
|
||||
import { AddEditFarmEventProps } from "../interfaces";
|
||||
import { Everything } from "../../interfaces";
|
||||
import * as moment from "moment";
|
||||
import { t } from "i18next";
|
||||
|
@ -29,6 +29,7 @@ import {
|
|||
import { sourceFbosConfigValue } from "../../devices/components/source_config_value";
|
||||
import { Feature } from "../../devices/interfaces";
|
||||
import { hasId } from "../../resources/util";
|
||||
import { ExecutableType } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
export let formatTime = (input: string, timeOffset: number) => {
|
||||
const iso = new Date(input).toISOString();
|
||||
|
|
|
@ -9,7 +9,6 @@ import {
|
|||
TaggedPlantPointer,
|
||||
TaggedImage,
|
||||
} from "farmbot";
|
||||
import { PlantPointer } from "../interfaces";
|
||||
import { SlotWithTool } from "../resources/interfaces";
|
||||
import { BotPosition, StepsPerMmXY, BotLocationData } from "../devices/interfaces";
|
||||
import { isNumber } from "lodash";
|
||||
|
@ -18,6 +17,7 @@ import { AxisNumberProperty, BotSize } from "./map/interfaces";
|
|||
import { SelectionBoxData } from "./map/selection_box";
|
||||
import { BooleanConfigKey } from "../config_storage/web_app_configs";
|
||||
import { GetWebAppConfigValue } from "../config_storage/actions";
|
||||
import { ExecutableType, PlantPointer } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
/* BotOriginQuadrant diagram
|
||||
|
||||
|
@ -67,27 +67,6 @@ export interface Props {
|
|||
getConfigValue: GetWebAppConfigValue;
|
||||
}
|
||||
|
||||
export type TimeUnit =
|
||||
| "never"
|
||||
| "minutely"
|
||||
| "hourly"
|
||||
| "daily"
|
||||
| "weekly"
|
||||
| "monthly"
|
||||
| "yearly";
|
||||
|
||||
export type ExecutableType = "Sequence" | "Regimen";
|
||||
|
||||
export interface FarmEvent {
|
||||
id?: number | undefined;
|
||||
start_time: string;
|
||||
end_time?: string | undefined;
|
||||
repeat?: number | undefined;
|
||||
time_unit: TimeUnit;
|
||||
executable_id: number;
|
||||
executable_type: ExecutableType;
|
||||
}
|
||||
|
||||
export interface MovePlantProps {
|
||||
deltaX: number;
|
||||
deltaY: number;
|
||||
|
@ -265,17 +244,6 @@ export interface CurrentPointPayl {
|
|||
color?: string;
|
||||
}
|
||||
|
||||
export interface PlantTemplate {
|
||||
id?: number;
|
||||
saved_garden_id: number;
|
||||
radius: number;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
name: string;
|
||||
openfarm_slug: string;
|
||||
}
|
||||
|
||||
export interface SavedGarden {
|
||||
id?: number;
|
||||
name?: string;
|
||||
|
|
|
@ -8,9 +8,9 @@ import * as React from "react";
|
|||
import { ToolSlotLayer, ToolSlotLayerProps } from "../tool_slot_layer";
|
||||
import { fakeMapTransformProps } from "../../../../__test_support__/map_transform_props";
|
||||
import { fakeResource } from "../../../../__test_support__/fake_resource";
|
||||
import { ToolSlotPointer } from "../../../../interfaces";
|
||||
import { shallow } from "enzyme";
|
||||
import { history } from "../../../../history";
|
||||
import { ToolSlotPointer } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
describe("<ToolSlotLayer/>", () => {
|
||||
function fakeProps(): ToolSlotLayerProps {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as React from "react";
|
||||
import { Color } from "../../ui/index";
|
||||
import { trim } from "../../util";
|
||||
import { ToolPulloutDirection } from "../../interfaces";
|
||||
import { BotOriginQuadrant } from "../interfaces";
|
||||
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
export interface ToolGraphicProps {
|
||||
x: number;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from "react";
|
||||
import { Color } from "../../ui/index";
|
||||
import { ToolPulloutDirection } from "../../interfaces";
|
||||
import { BotOriginQuadrant } from "../interfaces";
|
||||
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
enum Anchor {
|
||||
start = 0,
|
||||
|
|
|
@ -5,12 +5,12 @@ import { fakeMapTransformProps } from "../../../../__test_support__/map_transfor
|
|||
|
||||
describe("<BotTrail/>", () => {
|
||||
function fakeProps(): BotTrailProps {
|
||||
sessionStorage[VirtualTrail.records] = JSON.stringify([
|
||||
sessionStorage.setItem(VirtualTrail.records, JSON.stringify([
|
||||
{ coord: { x: 0, y: 0 }, water: 0 },
|
||||
{ coord: { x: 1, y: 1 }, water: 10 },
|
||||
{ coord: { x: 2, y: 2 }, water: 0 },
|
||||
{ coord: { x: 3, y: 3 }, water: 0 },
|
||||
{ coord: { x: 4, y: 4 }, water: 20 }]);
|
||||
{ coord: { x: 4, y: 4 }, water: 20 }]));
|
||||
return {
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
mapTransformProps: fakeMapTransformProps(),
|
||||
|
@ -19,7 +19,7 @@ describe("<BotTrail/>", () => {
|
|||
}
|
||||
|
||||
it("shows custom length trail", () => {
|
||||
sessionStorage[VirtualTrail.length] = JSON.stringify(5);
|
||||
sessionStorage.setItem(VirtualTrail.length, JSON.stringify(5));
|
||||
const p = fakeProps();
|
||||
p.mapTransformProps.quadrant = 2;
|
||||
const wrapper = shallow(<BotTrail {...p} />);
|
||||
|
@ -42,14 +42,14 @@ describe("<BotTrail/>", () => {
|
|||
});
|
||||
|
||||
it("shows default length trail", () => {
|
||||
sessionStorage[VirtualTrail.length] = undefined;
|
||||
sessionStorage.removeItem(VirtualTrail.length);
|
||||
const wrapper = shallow(<BotTrail {...fakeProps()} />);
|
||||
const lines = wrapper.find(".virtual-bot-trail").find("line");
|
||||
expect(lines.length).toEqual(5);
|
||||
});
|
||||
|
||||
it("doesn't store duplicate last trail point", () => {
|
||||
sessionStorage[VirtualTrail.length] = undefined;
|
||||
sessionStorage.removeItem(VirtualTrail.length);
|
||||
const p = fakeProps();
|
||||
p.position = { x: 4, y: 4, z: 0 };
|
||||
const wrapper = shallow(<BotTrail {...p} />);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { PlantOptions } from "./interfaces";
|
||||
import { PlantPointer } from "../interfaces";
|
||||
import { PlantPointer } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
export const DEFAULT_PLANT_RADIUS = 25;
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ interface SelectPlantsState {
|
|||
stashedUuid: string | undefined;
|
||||
stashedIcon: string;
|
||||
}
|
||||
const YOU_SURE = "Are you sure you want to delete {{length}} plants?";
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class SelectPlants
|
||||
|
@ -66,7 +67,7 @@ export class SelectPlants
|
|||
|
||||
destroySelected = (plantUUIDs: string[]) => {
|
||||
if (plantUUIDs &&
|
||||
confirm(t("Are you sure you want to delete {{length}} plants?", { length: plantUUIDs.length }))) {
|
||||
confirm(t(YOU_SURE, { length: plantUUIDs.length }))) {
|
||||
plantUUIDs.map(uuid => {
|
||||
this
|
||||
.props
|
||||
|
|
|
@ -2,8 +2,8 @@ import axios, { AxiosPromise } from "axios";
|
|||
import * as _ from "lodash";
|
||||
import { OpenFarm, CropSearchResult } from "./openfarm";
|
||||
import { DEFAULT_ICON } from "../open_farm/icons";
|
||||
import { ExecutableType } from "./interfaces";
|
||||
import { Actions } from "../constants";
|
||||
import { ExecutableType } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
const url = (q: string) => `${OpenFarm.cropUrl}?include=pictures&filter=${q}`;
|
||||
const openFarmSearchQuery = (q: string): AxiosPromise<CropSearchResult> =>
|
||||
|
|
|
@ -1,20 +1,5 @@
|
|||
import { TaggedImage } from "farmbot";
|
||||
|
||||
export interface Image {
|
||||
id: number;
|
||||
device_id: number;
|
||||
attachment_processed_at: string | undefined;
|
||||
updated_at: string;
|
||||
created_at: string;
|
||||
attachment_url: string;
|
||||
meta: {
|
||||
x: number | undefined;
|
||||
y: number | undefined;
|
||||
z: number | undefined;
|
||||
name?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ImageFlipperProps {
|
||||
onFlip(uuid: string | undefined): void;
|
||||
images: TaggedImage[];
|
||||
|
|
|
@ -30,9 +30,4 @@ export interface FarmwareConfigMenuProps {
|
|||
firstPartyFwsInstalled: boolean;
|
||||
}
|
||||
|
||||
export interface FarmwareInstallation {
|
||||
id?: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export type Farmwares = Dictionary<FarmwareManifest | undefined>;
|
||||
|
|
|
@ -5,12 +5,12 @@ import { success, error } from "farmbot-toastr";
|
|||
import { Thunk } from "../../redux/interfaces";
|
||||
import { API } from "../../api";
|
||||
import { Progress, ProgressCallback } from "../../util";
|
||||
import { GenericPointer } from "../../interfaces";
|
||||
import { getDevice } from "../../device";
|
||||
import { WDENVKey } from "./remote_env/interfaces";
|
||||
import { NumericValues } from "./image_workspace";
|
||||
import { envSave } from "./remote_env/actions";
|
||||
import { noop } from "lodash";
|
||||
import { GenericPointer } from "farmbot/dist/resources/api_resources";
|
||||
type Key = keyof NumericValues;
|
||||
type Translation = Record<Key, WDENVKey>;
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { AuthState } from "./auth/interfaces";
|
||||
import { ConfigState } from "./config/interfaces";
|
||||
import { BotState } from "./devices/interfaces";
|
||||
import { Color as FarmBotJsColor, ALLOWED_MESSAGE_TYPES, PlantStage } from "farmbot";
|
||||
import { Color as FarmBotJsColor } from "farmbot";
|
||||
import { Point } from "farmbot/dist/resources/api_resources";
|
||||
import { DraggableState } from "./draggable/interfaces";
|
||||
import { PeripheralState } from "./controls/peripherals/interfaces";
|
||||
import { RestResources } from "./resources/interfaces";
|
||||
import { ChannelName } from "./sequences/interfaces";
|
||||
|
||||
/** Regimens and sequences may have a "color" which determines how it looks
|
||||
in the UI. Only certain colors are valid. */
|
||||
|
@ -34,19 +34,6 @@ export interface DeviceConfig {
|
|||
key: string;
|
||||
value: string | number | boolean;
|
||||
}
|
||||
export interface Log {
|
||||
id?: number | undefined;
|
||||
message: string;
|
||||
type: ALLOWED_MESSAGE_TYPES;
|
||||
x?: number;
|
||||
y?: number;
|
||||
z?: number;
|
||||
verbosity?: number;
|
||||
major_version?: number;
|
||||
minor_version?: number;
|
||||
channels: ChannelName[];
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
interface Location {
|
||||
/** EX: /app/designer */
|
||||
|
@ -85,48 +72,4 @@ export interface Everything {
|
|||
// tslint:disable-next-line:no-any
|
||||
export type UnsafeError = any;
|
||||
|
||||
interface BasePoint {
|
||||
id?: number | undefined;
|
||||
dirty?: boolean | undefined;
|
||||
created_at?: string | undefined;
|
||||
updated_at?: string | undefined;
|
||||
radius: number;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
pointer_id?: number | undefined;
|
||||
meta: { [key: string]: (string | undefined) };
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface PlantPointer extends BasePoint {
|
||||
openfarm_slug: string;
|
||||
pointer_type: "Plant";
|
||||
planted_at?: string;
|
||||
plant_stage: PlantStage;
|
||||
}
|
||||
|
||||
export enum ToolPulloutDirection {
|
||||
NONE = 0,
|
||||
POSITIVE_X = 1,
|
||||
NEGATIVE_X = 2,
|
||||
POSITIVE_Y = 3,
|
||||
NEGATIVE_Y = 4,
|
||||
}
|
||||
|
||||
export interface ToolSlotPointer extends BasePoint {
|
||||
tool_id: number | undefined;
|
||||
pointer_type: "ToolSlot";
|
||||
pullout_direction: ToolPulloutDirection;
|
||||
}
|
||||
|
||||
export interface GenericPointer extends BasePoint {
|
||||
pointer_type: "GenericPointer";
|
||||
}
|
||||
|
||||
export type Point =
|
||||
| GenericPointer
|
||||
| ToolSlotPointer
|
||||
| PlantPointer;
|
||||
|
||||
export type PointerTypeName = Point["pointer_type"];
|
||||
|
|
|
@ -32,13 +32,13 @@ type OFIcon = Readonly<OFCropAttrs>;
|
|||
const STORAGE_KEY = "openfarm_icons_with_spread";
|
||||
|
||||
function initLocalStorage() {
|
||||
localStorage[STORAGE_KEY] = "{}";
|
||||
localStorage.setItem(STORAGE_KEY, "{}");
|
||||
return {};
|
||||
}
|
||||
|
||||
function getAllIconsFromCache(): Dictionary<OFIcon | undefined> {
|
||||
try {
|
||||
const dictionary = JSON.parse(localStorage[STORAGE_KEY]);
|
||||
const dictionary = JSON.parse(localStorage.getItem(STORAGE_KEY) || "");
|
||||
return isObject(dictionary) ? dictionary : initLocalStorage();
|
||||
} catch (error) {
|
||||
return initLocalStorage();
|
||||
|
@ -53,7 +53,7 @@ function localStorageIconFetch(slug: string): Promise<OFIcon> | undefined {
|
|||
function localStorageIconSet(icon: OFIcon): void {
|
||||
const dictionary = getAllIconsFromCache();
|
||||
dictionary[icon.slug] = icon;
|
||||
localStorage[STORAGE_KEY] = JSON.stringify(dictionary);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(dictionary));
|
||||
}
|
||||
|
||||
/** PROBLEM: HTTP requests get fired too fast. If you have 10 garlic plants,
|
||||
|
|
|
@ -25,7 +25,7 @@ describe("configureStore", () => {
|
|||
});
|
||||
|
||||
it("does not crash on malformed states", () => {
|
||||
sessionStorage.lastState = "Not JSON at all.";
|
||||
sessionStorage.setItem("lastState", "Not JSON at all.");
|
||||
const result1 = configureStore().getState();
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
expect(result1.auth).toBe(null); // Initialize to default value.
|
||||
|
|
|
@ -29,13 +29,13 @@ describe("unsavedCheck", () => {
|
|||
}
|
||||
|
||||
it("stops users if they have unsaved work", () => {
|
||||
localStorage.session = "YES";
|
||||
localStorage.setItem("session", "YES");
|
||||
unsavedCheck(setItUp(SpecialStatus.DIRTY, { discard_unsaved: false }));
|
||||
expect(window.onbeforeunload).toBe(stopThem);
|
||||
});
|
||||
|
||||
it("does nothing when logged out", () => {
|
||||
localStorage.session = undefined;
|
||||
localStorage.removeItem("session");
|
||||
unsavedCheck(setItUp(SpecialStatus.DIRTY, { discard_unsaved: false }));
|
||||
expect(window.onbeforeunload).toBe(dontStopThem);
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Actions } from "../constants";
|
||||
import { API } from "../api";
|
||||
import { Log } from "../interfaces";
|
||||
import { noop, throttle } from "lodash";
|
||||
import axios from "axios";
|
||||
import { ResourceName } from "farmbot";
|
||||
import { Log } from "farmbot/dist/resources/api_resources";
|
||||
const name: ResourceName = "Log";
|
||||
|
||||
/** re-Downloads all logs from the API and force replaces all entries for logs
|
||||
|
|
|
@ -31,7 +31,7 @@ export let store = configureStore();
|
|||
* Returns {} if nothing is found. Used mostly for hot reloading. */
|
||||
function maybeFetchOldState() {
|
||||
try {
|
||||
return JSON.parse(sessionStorage["lastState"] || "{}");
|
||||
return JSON.parse(sessionStorage.getItem("lastState") || "{}");
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export function unsavedCheck(state: Everything) {
|
|||
const total = dirty.length;
|
||||
const doStop = (total !== 0);
|
||||
const conf = getWebAppConfig(index);
|
||||
const loggedOut = !localStorage.session;
|
||||
const loggedOut = !localStorage.getItem("session");
|
||||
|
||||
if ((conf && conf.body.discard_unsaved) || loggedOut) {
|
||||
window.onbeforeunload = dontStopThem;
|
||||
|
|
|
@ -19,13 +19,13 @@ export namespace Session {
|
|||
|
||||
/** Replace the contents of session storage. */
|
||||
export function replaceToken(nextState: AuthState) {
|
||||
localStorage[KEY] = JSON.stringify(nextState);
|
||||
localStorage.setItem(KEY, JSON.stringify(nextState));
|
||||
}
|
||||
|
||||
/** Fetch the previous session. */
|
||||
export function fetchStoredToken(): AuthState | undefined {
|
||||
try {
|
||||
const v: AuthState = JSON.parse(localStorage[KEY]);
|
||||
const v: AuthState = JSON.parse(localStorage.getItem(KEY) || "");
|
||||
if (box(v).kind === "object") {
|
||||
return v;
|
||||
} else {
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import axios from "axios";
|
||||
import { Log, Point, SensorReading, Sensor, DeviceConfig } from "../interfaces";
|
||||
import { SensorReading, Sensor, DeviceConfig } from "../interfaces";
|
||||
import { API } from "../api";
|
||||
import { Sequence } from "../sequences/interfaces";
|
||||
import { Tool } from "../tools/interfaces";
|
||||
import { Regimen } from "../regimens/interfaces";
|
||||
import { Peripheral } from "../controls/peripherals/interfaces";
|
||||
import { FarmEvent, SavedGarden, PlantTemplate } from "../farm_designer/interfaces";
|
||||
import { Image } from "../farmware/images/interfaces";
|
||||
import { SavedGarden } from "../farm_designer/interfaces";
|
||||
import { DeviceAccountSettings } from "../devices/interfaces";
|
||||
import { ResourceName, DiagnosticDump } from "farmbot";
|
||||
import { User } from "../auth/interfaces";
|
||||
|
@ -14,9 +12,17 @@ import { WebcamFeed } from "../controls/interfaces";
|
|||
import { WebAppConfig } from "../config_storage/web_app_configs";
|
||||
import { Session } from "../session";
|
||||
import { FbosConfig } from "../config_storage/fbos_configs";
|
||||
import { FarmwareInstallation } from "../farmware/interfaces";
|
||||
import { FirmwareConfig } from "../config_storage/firmware_configs";
|
||||
import { PinBinding } from "../devices/pin_bindings/interfaces";
|
||||
import {
|
||||
FarmEvent,
|
||||
Image,
|
||||
Log,
|
||||
Point,
|
||||
Peripheral,
|
||||
FarmwareInstallation,
|
||||
PinBinding,
|
||||
PlantTemplate
|
||||
} from "farmbot/dist/resources/api_resources";
|
||||
|
||||
export interface ResourceReadyPayl {
|
||||
name: ResourceName;
|
||||
|
|
|
@ -2,9 +2,9 @@ import * as React from "react";
|
|||
import { t } from "i18next";
|
||||
import { FBSelect, DropDownItem } from "../../ui/index";
|
||||
import { TaggedToolSlotPointer } from "farmbot";
|
||||
import { ToolPulloutDirection } from "../../interfaces";
|
||||
import { edit } from "../../api/crud";
|
||||
import { isNumber } from "lodash";
|
||||
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
const DIRECTION_CHOICES_DDI: { [index: number]: DropDownItem } = {
|
||||
[ToolPulloutDirection.NONE]:
|
||||
|
|
|
@ -3,9 +3,9 @@ import { t } from "i18next";
|
|||
import { isNumber } from "lodash";
|
||||
import { BotPosition } from "../../devices/interfaces";
|
||||
import { TaggedToolSlotPointer } from "farmbot";
|
||||
import { ToolPulloutDirection } from "../../interfaces";
|
||||
import { edit } from "../../api/crud";
|
||||
import { SlotDirectionSelect } from "./toolbay_slot_direction_selection";
|
||||
import { ToolPulloutDirection } from "farmbot/dist/resources/api_resources";
|
||||
|
||||
const positionIsDefined = (position: BotPosition): boolean => {
|
||||
return isNumber(position.x) && isNumber(position.y) && isNumber(position.z);
|
||||
|
|
|
@ -17,7 +17,7 @@ jest.mock("axios", () => {
|
|||
});
|
||||
|
||||
import * as React from "react";
|
||||
import { TosUpdate } from "../index";
|
||||
import { TosUpdate } from "../component";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import axios from "axios";
|
||||
import { API } from "../../api/index";
|
|
@ -0,0 +1,16 @@
|
|||
jest.mock("i18next", () => ({ init: jest.fn((_, ok) => ok()) }));
|
||||
jest.mock("react-dom", () => ({ render: jest.fn() }));
|
||||
jest.mock("../../i18n",
|
||||
() => ({ detectLanguage: jest.fn(() => Promise.resolve()) }));
|
||||
|
||||
import { detectLanguage } from "../../i18n";
|
||||
import { render } from "react-dom";
|
||||
|
||||
describe("index.ts", () => {
|
||||
it("attaches the TOS page to the DOM", async () => {
|
||||
await import("../index");
|
||||
expect(detectLanguage).toHaveBeenCalled();
|
||||
expect(document.getElementById("root")).toBeTruthy();
|
||||
expect(render).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,122 @@
|
|||
import * as React from "react";
|
||||
import axios from "axios";
|
||||
import { t } from "i18next";
|
||||
import { fun as log, error as logError, init as logInit } from "farmbot-toastr";
|
||||
import { AuthState } from "../auth/interfaces";
|
||||
import { Session } from "../session";
|
||||
import { prettyPrintApiErrors } from "../util";
|
||||
import { API } from "../api";
|
||||
import "../css/_index.scss";
|
||||
import { Row, Col, Widget, WidgetHeader, WidgetBody } from "../ui/index";
|
||||
|
||||
interface Props { }
|
||||
interface State {
|
||||
email: string;
|
||||
password: string;
|
||||
agree_to_terms: boolean;
|
||||
}
|
||||
|
||||
export class TosUpdate extends React.Component<Props, Partial<State>> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.submit = this.submit.bind(this);
|
||||
this.state = { agree_to_terms: true };
|
||||
}
|
||||
|
||||
set = (name: keyof State) => (event: React.FormEvent<HTMLInputElement>) => {
|
||||
const state: { [name: string]: State[keyof State] } = {};
|
||||
state[name] = (event.currentTarget).value;
|
||||
this.setState(state);
|
||||
};
|
||||
|
||||
submit(e: React.SyntheticEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
const { email, password, agree_to_terms } = this.state;
|
||||
const payload = { user: { email, password, agree_to_terms } };
|
||||
API.setBaseUrl(API.fetchBrowserLocation());
|
||||
axios
|
||||
.post<AuthState>(API.current.tokensPath, payload)
|
||||
.then(resp => {
|
||||
Session.replaceToken(resp.data);
|
||||
window.location.href = "/app/controls";
|
||||
})
|
||||
.catch(error => {
|
||||
logError(prettyPrintApiErrors(error));
|
||||
});
|
||||
}
|
||||
|
||||
get tosLoadOk() { return (globalConfig.TOS_URL && globalConfig.PRIV_URL); }
|
||||
|
||||
tosForm() {
|
||||
if (this.tosLoadOk) {
|
||||
return <form onSubmit={this.submit}>
|
||||
<div className="input-group">
|
||||
<label> {t("Email")} </label>
|
||||
<input type="email"
|
||||
onChange={this.set("email").bind(this)}>
|
||||
</input>
|
||||
<label>{t("Password")}</label>
|
||||
<input type="password"
|
||||
onChange={this.set("password").bind(this)}>
|
||||
</input>
|
||||
<ul>
|
||||
<li>
|
||||
<a href={globalConfig.TOS_URL}>
|
||||
{t("Terms of Service")}
|
||||
</a>
|
||||
<span className="fa fa-external-link" />
|
||||
</li>
|
||||
<li>
|
||||
<a href={globalConfig.PRIV_URL}>
|
||||
{t("Privacy Policy")}
|
||||
</a>
|
||||
<span className="fa fa-external-link" />
|
||||
</li>
|
||||
</ul>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<button className="green fb-button">
|
||||
{t("I Agree to the Terms of Service")}
|
||||
</button>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</form>;
|
||||
} else {
|
||||
return <div>
|
||||
<p>
|
||||
{t("Something went wrong while rendering this page.")}
|
||||
</p>
|
||||
<p>
|
||||
{t("Please send us an email at contact@farmbot.io or see the ")}
|
||||
<a href="http://forum.farmbot.org/">
|
||||
{t("FarmBot forum.")}
|
||||
</a>
|
||||
</p>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
logInit();
|
||||
const body = t("Before logging in, you must agree to our latest Terms" +
|
||||
" of Service and Privacy Policy");
|
||||
log(body, "New Terms of Service");
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className="static-page">
|
||||
<div className="all-content-wrapper">
|
||||
<Widget>
|
||||
<WidgetHeader title={
|
||||
this.tosLoadOk
|
||||
? "Agree to Terms of Service"
|
||||
: "Problem Loading Terms of Service"} />
|
||||
<WidgetBody>
|
||||
{this.tosForm()}
|
||||
</WidgetBody>
|
||||
</Widget>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
|
@ -1,141 +1,16 @@
|
|||
import * as React from "react";
|
||||
import { render } from "react-dom";
|
||||
import axios from "axios";
|
||||
import { t, init } from "i18next";
|
||||
import { fun as log, error as logError, init as logInit } from "farmbot-toastr";
|
||||
import { AuthState } from "../auth/interfaces";
|
||||
import { Session } from "../session";
|
||||
import { prettyPrintApiErrors } from "../util";
|
||||
import { init } from "i18next";
|
||||
import { detectLanguage } from "../i18n";
|
||||
import { API } from "../api";
|
||||
import "../css/_index.scss";
|
||||
import { Row, Col, Widget, WidgetHeader, WidgetBody } from "../ui/index";
|
||||
import * as React from "react";
|
||||
import { TosUpdate } from "./component";
|
||||
|
||||
interface Props { }
|
||||
interface State {
|
||||
email: string;
|
||||
password: string;
|
||||
agree_to_terms: boolean;
|
||||
}
|
||||
const node = document.createElement("DIV");
|
||||
node.id = "root";
|
||||
document.body.appendChild(node);
|
||||
const domElem = document.getElementById("root");
|
||||
const reactElem = React.createElement(TosUpdate, {});
|
||||
|
||||
export class TosUpdate extends React.Component<Props, Partial<State>> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.submit = this.submit.bind(this);
|
||||
this.state = { agree_to_terms: true };
|
||||
}
|
||||
|
||||
set = (name: keyof State) => (event: React.FormEvent<HTMLInputElement>) => {
|
||||
const state: { [name: string]: State[keyof State] } = {};
|
||||
state[name] = (event.currentTarget).value;
|
||||
this.setState(state);
|
||||
};
|
||||
const ok = () => domElem && render(reactElem, domElem);
|
||||
|
||||
submit(e: React.SyntheticEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
const { email, password, agree_to_terms } = this.state;
|
||||
const payload = { user: { email, password, agree_to_terms } };
|
||||
API.setBaseUrl(API.fetchBrowserLocation());
|
||||
axios
|
||||
.post<AuthState>(API.current.tokensPath, payload)
|
||||
.then(resp => {
|
||||
Session.replaceToken(resp.data);
|
||||
window.location.href = "/app/controls";
|
||||
})
|
||||
.catch(error => {
|
||||
logError(prettyPrintApiErrors(error));
|
||||
});
|
||||
}
|
||||
|
||||
get tosLoadOk() { return (globalConfig.TOS_URL && globalConfig.PRIV_URL); }
|
||||
|
||||
tosForm() {
|
||||
if (this.tosLoadOk) {
|
||||
return <form onSubmit={this.submit}>
|
||||
<div className="input-group">
|
||||
<label> {t("Email")} </label>
|
||||
<input type="email"
|
||||
onChange={this.set("email").bind(this)}>
|
||||
</input>
|
||||
<label>{t("Password")}</label>
|
||||
<input type="password"
|
||||
onChange={this.set("password").bind(this)}>
|
||||
</input>
|
||||
<ul>
|
||||
<li>
|
||||
<a href={globalConfig.TOS_URL}>
|
||||
{t("Terms of Service")}
|
||||
</a>
|
||||
<span className="fa fa-external-link" />
|
||||
</li>
|
||||
<li>
|
||||
<a href={globalConfig.PRIV_URL}>
|
||||
{t("Privacy Policy")}
|
||||
</a>
|
||||
<span className="fa fa-external-link" />
|
||||
</li>
|
||||
</ul>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<button className="green fb-button">
|
||||
{t("I Agree to the Terms of Service")}
|
||||
</button>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</form>;
|
||||
} else {
|
||||
return <div>
|
||||
<p>
|
||||
{t("Something went wrong while rendering this page.")}
|
||||
</p>
|
||||
<p>
|
||||
{t("Please send us an email at contact@farmbot.io or see the ")}
|
||||
<a href="http://forum.farmbot.org/">
|
||||
{t("FarmBot forum.")}
|
||||
</a>
|
||||
</p>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
logInit();
|
||||
const body = t("Before logging in, you must agree to our latest Terms" +
|
||||
" of Service and Privacy Policy");
|
||||
log(body, "New Terms of Service");
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className="static-page">
|
||||
<div className="all-content-wrapper">
|
||||
<Widget>
|
||||
<WidgetHeader title={
|
||||
this.tosLoadOk
|
||||
? "Agree to Terms of Service"
|
||||
: "Problem Loading Terms of Service"} />
|
||||
<WidgetBody>
|
||||
{this.tosForm()}
|
||||
</WidgetBody>
|
||||
</Widget>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
detectLanguage().then((config) => {
|
||||
init(config, (_, t2) => {
|
||||
const node = document.createElement("DIV");
|
||||
node.id = "root";
|
||||
document.body.appendChild(node);
|
||||
|
||||
const reactElem = React.createElement(TosUpdate, {});
|
||||
const domElem = document.getElementById("root");
|
||||
|
||||
if (domElem) {
|
||||
render(reactElem, domElem);
|
||||
} else {
|
||||
throw new Error(t2("Add a div with id `root` to the page first."));
|
||||
}
|
||||
});
|
||||
});
|
||||
detectLanguage().then(conf => init(conf, ok));
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -2417,9 +2417,9 @@ farmbot-toastr@^1.0.0, farmbot-toastr@^1.0.3:
|
|||
farmbot-toastr "^1.0.0"
|
||||
typescript "^2.3.4"
|
||||
|
||||
farmbot@6.4.2:
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/farmbot/-/farmbot-6.4.2.tgz#8a3a7727cf9329fb4bc39ad4dbec5d17c8146669"
|
||||
farmbot@6.4.3:
|
||||
version "6.4.3"
|
||||
resolved "https://registry.yarnpkg.com/farmbot/-/farmbot-6.4.3.tgz#08f6c361e006410aac87dbba28d3c1a291a458b2"
|
||||
dependencies:
|
||||
mqtt "2.15.0"
|
||||
|
||||
|
@ -7164,9 +7164,9 @@ typedarray@^0.0.6:
|
|||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
|
||||
typescript@2.9.2:
|
||||
version "2.9.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
|
||||
typescript@3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.1.tgz#43738f29585d3a87575520a4b93ab6026ef11fdb"
|
||||
|
||||
typescript@^2.0.9, typescript@^2.3.4:
|
||||
version "2.8.3"
|
||||
|
|
Loading…
Reference in New Issue