parent
d96b9bd467
commit
110d7acd87
|
@ -1,6 +1,6 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- 8.9.4
|
||||
- 8.11.3
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
module Api
|
||||
class DiagnosticDumpsController < Api::AbstractController
|
||||
|
||||
def index
|
||||
render json: diagnostic_dumps
|
||||
end
|
||||
|
||||
def create
|
||||
Rollbar.info("Device #{current_device.id} created a diagnostic")
|
||||
mutate DiagnosticDumps::Create.run(raw_json, device: current_device)
|
||||
end
|
||||
|
||||
def destroy
|
||||
diagnostic_dump.destroy!
|
||||
render json: ""
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def diagnostic_dumps
|
||||
current_device.diagnostic_dumps
|
||||
end
|
||||
|
||||
def diagnostic_dump
|
||||
@diagnostic_dump ||= diagnostic_dumps.find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,7 +18,7 @@ module CeleryScriptSettingsBag
|
|||
install_farmware update_farmware take_photo zero
|
||||
install_first_party_farmware remove_farmware
|
||||
find_home register_gpio unregister_gpio
|
||||
set_servo_angle change_ownership)
|
||||
set_servo_angle change_ownership dump_info)
|
||||
ALLOWED_PACKAGES = %w(farmbot_os arduino_firmware)
|
||||
ALLOWED_CHAGES = %w(add remove update)
|
||||
RESOURCE_NAME = %w(images plants regimens peripherals
|
||||
|
@ -227,6 +227,7 @@ module CeleryScriptSettingsBag
|
|||
.node(:parameter_declaration, [:label, :data_type], [])
|
||||
.node(:set_servo_angle, [:pin_number, :pin_value], [])
|
||||
.node(:change_ownership, [], [:pair])
|
||||
.node(:dump_info, [], [])
|
||||
.node(:install_first_party_farmware, [])
|
||||
|
||||
ANY_ARG_NAME = Corpus.as_json[:args].pluck("name").map(&:to_s)
|
||||
|
|
|
@ -12,24 +12,25 @@ class Device < ApplicationRecord
|
|||
"Resuming log storage."
|
||||
CACHE_KEY = "devices.%s"
|
||||
|
||||
has_many :device_configs, dependent: :destroy
|
||||
has_many :farm_events, dependent: :destroy
|
||||
has_many :device_configs, dependent: :destroy
|
||||
has_many :farm_events, dependent: :destroy
|
||||
has_many :farmware_installations, dependent: :destroy
|
||||
has_many :images, dependent: :destroy
|
||||
has_many :logs, dependent: :destroy
|
||||
has_many :peripherals, dependent: :destroy
|
||||
has_many :pin_bindings, dependent: :destroy
|
||||
has_many :plant_templates, dependent: :destroy
|
||||
has_many :points, dependent: :destroy
|
||||
has_many :regimens, dependent: :destroy
|
||||
has_many :saved_gardens, dependent: :destroy
|
||||
has_many :sensor_readings, dependent: :destroy
|
||||
has_many :sensors, dependent: :destroy
|
||||
has_many :sequences, dependent: :destroy
|
||||
has_many :token_issuances, dependent: :destroy
|
||||
has_many :tools, dependent: :destroy
|
||||
has_many :webcam_feeds, dependent: :destroy
|
||||
has_one :fbos_config, dependent: :destroy
|
||||
has_many :images, dependent: :destroy
|
||||
has_many :logs, dependent: :destroy
|
||||
has_many :peripherals, dependent: :destroy
|
||||
has_many :pin_bindings, dependent: :destroy
|
||||
has_many :plant_templates, dependent: :destroy
|
||||
has_many :points, dependent: :destroy
|
||||
has_many :regimens, dependent: :destroy
|
||||
has_many :saved_gardens, dependent: :destroy
|
||||
has_many :sensor_readings, dependent: :destroy
|
||||
has_many :sensors, dependent: :destroy
|
||||
has_many :sequences, dependent: :destroy
|
||||
has_many :token_issuances, dependent: :destroy
|
||||
has_many :tools, dependent: :destroy
|
||||
has_many :webcam_feeds, dependent: :destroy
|
||||
has_many :diagnostic_dumps, dependent: :destroy
|
||||
has_one :fbos_config, dependent: :destroy
|
||||
has_many :in_use_tools
|
||||
has_many :in_use_points
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
class DiagnosticDump < ApplicationRecord
|
||||
belongs_to :device
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
module DiagnosticDumps
|
||||
class Create < Mutations::Command
|
||||
required do
|
||||
model :device, class: Device
|
||||
string :fbos_version
|
||||
string :fbos_commit
|
||||
string :firmware_commit
|
||||
string :network_interface
|
||||
string :fbos_dmesg_dump
|
||||
string :firmware_state
|
||||
end
|
||||
|
||||
def execute
|
||||
DiagnosticDump
|
||||
.create!(device: device,
|
||||
ticket_identifier: rand(36**5).to_s(36),
|
||||
fbos_version: fbos_version,
|
||||
fbos_commit: fbos_commit,
|
||||
firmware_commit: firmware_commit,
|
||||
network_interface: network_interface,
|
||||
fbos_dmesg_dump: fbos_dmesg_dump,
|
||||
firmware_state: firmware_state,)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class DiagnosticDumpSerializer < ActiveModel::Serializer
|
||||
attributes :id, :device_id, :ticket_identifier, :fbos_commit, :fbos_version,
|
||||
:firmware_commit, :firmware_state, :network_interface,
|
||||
:fbos_dmesg_dump, :created_at, :updated_at
|
||||
end
|
|
@ -43,7 +43,7 @@ DEPS = `yarn outdated`
|
|||
.map{|y| y.split }
|
||||
.map{|y| "#{y[0]}@#{y[3]}"}
|
||||
.sort
|
||||
|
||||
.reject { |x| x.include?("router") }
|
||||
# puts "Making sure that type checks pass WITHOUT any upgrades"
|
||||
tc_ok = type_check
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ FarmBot::Application.routes.draw do
|
|||
namespace :api, defaults: {format: :json}, constraints: { format: "json" } do
|
||||
# Standard API Resources:
|
||||
{
|
||||
diagnostic_dumps: [:create, :destroy, :index],
|
||||
farm_events: [:create, :destroy, :index, :update],
|
||||
farmware_installations: [:create, :destroy, :index],
|
||||
images: [:create, :destroy, :index, :show],
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
class CreateDiagnosticDumps < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :diagnostic_dumps do |t|
|
||||
t.references :device, foreign_key: true, null: false
|
||||
t.string :ticket_identifier, null: false, unique: true
|
||||
t.string :fbos_commit, null: false
|
||||
t.string :fbos_version, null: false
|
||||
t.string :firmware_commit, null: false
|
||||
t.string :firmware_state, null: false
|
||||
t.string :network_interface, null: false
|
||||
t.text :fbos_dmesg_dump, null: false
|
||||
t.timestamps
|
||||
|
||||
end
|
||||
end
|
||||
end
|
17
db/schema.rb
17
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2018_06_09_144559) do
|
||||
ActiveRecord::Schema.define(version: 2018_06_15_153318) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "hstore"
|
||||
|
@ -53,6 +53,20 @@ ActiveRecord::Schema.define(version: 2018_06_09_144559) do
|
|||
t.index ["timezone"], name: "index_devices_on_timezone"
|
||||
end
|
||||
|
||||
create_table "diagnostic_dumps", force: :cascade do |t|
|
||||
t.bigint "device_id", null: false
|
||||
t.string "ticket_identifier", null: false
|
||||
t.string "fbos_commit", null: false
|
||||
t.string "fbos_version", null: false
|
||||
t.string "firmware_commit", null: false
|
||||
t.string "firmware_state", null: false
|
||||
t.string "network_interface", null: false
|
||||
t.text "fbos_dmesg_dump", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["device_id"], name: "index_diagnostic_dumps_on_device_id"
|
||||
end
|
||||
|
||||
create_table "edge_nodes", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
|
@ -478,6 +492,7 @@ ActiveRecord::Schema.define(version: 2018_06_09_144559) do
|
|||
end
|
||||
|
||||
add_foreign_key "device_configs", "devices"
|
||||
add_foreign_key "diagnostic_dumps", "devices"
|
||||
add_foreign_key "edge_nodes", "sequences"
|
||||
add_foreign_key "farmware_installations", "devices"
|
||||
add_foreign_key "peripherals", "devices"
|
||||
|
|
|
@ -83,7 +83,7 @@ class CorpusEmitter
|
|||
end
|
||||
end
|
||||
|
||||
HASH = JSON.load(open("http://localhost:3000/api/corpuses/3")).deep_symbolize_keys
|
||||
HASH = JSON.load(open("http://localhost:3000/api/corpus")).deep_symbolize_keys
|
||||
ARGS = {}
|
||||
HASH[:args].map{ |x| CSArg.new(x) }.each{|x| ARGS[x.name] = x}
|
||||
NODES = HASH[:nodes].map { |x| CSNode.new(x) }
|
||||
|
|
20
package.json
20
package.json
|
@ -34,11 +34,11 @@
|
|||
"@types/fastclick": "^1.0.28",
|
||||
"@types/history": "^4.6.1",
|
||||
"@types/i18next": "^8.4.2",
|
||||
"@types/jest": "23.0.2",
|
||||
"@types/jest": "23.1.0",
|
||||
"@types/lodash": "4.14.109",
|
||||
"@types/markdown-it": "^0.0.4",
|
||||
"@types/moxios": "^0.4.5",
|
||||
"@types/node": "10.3.2",
|
||||
"@types/node": "10.3.3",
|
||||
"@types/react": "16.3.14",
|
||||
"@types/react-color": "2.13.5",
|
||||
"@types/react-dom": "16.0.5",
|
||||
|
@ -51,11 +51,11 @@
|
|||
"css-loader": "0.28.11",
|
||||
"enzyme": "^3.1.0",
|
||||
"enzyme-adapter-react-16": "^1.1.0",
|
||||
"farmbot": "6.0.0-rc2",
|
||||
"farmbot": "6.0.1",
|
||||
"farmbot-toastr": "^1.0.3",
|
||||
"fastclick": "^1.0.6",
|
||||
"file-loader": "1.1.11",
|
||||
"i18next": "11.3.2",
|
||||
"i18next": "11.3.3",
|
||||
"imports-loader": "0.8.0",
|
||||
"jest": "23.1.0",
|
||||
"json-loader": "0.5.7",
|
||||
|
@ -67,14 +67,14 @@
|
|||
"node-sass": "4.9.0",
|
||||
"optimize-css-assets-webpack-plugin": "4.0.2",
|
||||
"raf": "^3.4.0",
|
||||
"react": "16.4",
|
||||
"react": "16.4.1",
|
||||
"react-addons-css-transition-group": "^15.6.2",
|
||||
"react-addons-test-utils": "^15.6.2",
|
||||
"react-color": "2.14.1",
|
||||
"react-dom": "16.4",
|
||||
"react-dom": "16.4.1",
|
||||
"react-redux": "^5.0.6",
|
||||
"react-router": "^3",
|
||||
"react-test-renderer": "16.4.0",
|
||||
"react-test-renderer": "16.4.1",
|
||||
"react-transition-group": "^2.3.1",
|
||||
"redux": "^3.7.2",
|
||||
"redux-immutable-state-invariant": "^2.1.0",
|
||||
|
@ -87,7 +87,7 @@
|
|||
"ts-lint": "^4.5.1",
|
||||
"ts-loader": "^4.4.1",
|
||||
"tslint": "5.10.0",
|
||||
"typescript": "2.9.1",
|
||||
"typescript": "2.9.2",
|
||||
"url-loader": "1.0.1",
|
||||
"webpack": "4.12.0",
|
||||
"webpack-uglify-js-plugin": "1.1.9",
|
||||
|
@ -96,8 +96,8 @@
|
|||
"yarn": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jscpd": "0.6.19",
|
||||
"webpack-cli": "3.0.4",
|
||||
"jscpd": "0.6.21",
|
||||
"webpack-cli": "3.0.8",
|
||||
"webpack-notifier": "^1.5.0"
|
||||
},
|
||||
"jest": {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Api::DiagnosticDumpsController do
|
||||
let(:device) { FactoryBot.create(:device) }
|
||||
let(:user) { FactoryBot.create(:user, device: device) }
|
||||
|
||||
include Devise::Test::ControllerHelpers
|
||||
|
||||
it 'lists all diagnostics' do
|
||||
sign_in user
|
||||
DiagnosticDump.destroy_all
|
||||
device_config = FactoryBot.create_list(:diagnostic_dump, 3, device: device)
|
||||
get :index
|
||||
expect(json.length).to eq(3)
|
||||
expect(json.pluck(:device_id).uniq).to eq([user.device.id])
|
||||
end
|
||||
|
||||
it 'creates a dump' do
|
||||
sign_in user
|
||||
DiagnosticDump.destroy_all
|
||||
b4 = DiagnosticDump.count
|
||||
input = {
|
||||
fbos_version: "123_fbos_version",
|
||||
fbos_commit: "123_fbos_commit",
|
||||
firmware_commit: "123_firmware_commit",
|
||||
network_interface: "123_network_interface",
|
||||
fbos_dmesg_dump: "123_fbos_dmesg_dump",
|
||||
firmware_state: "123_firmware_state",
|
||||
}
|
||||
post :create, body: input.to_json
|
||||
expect(response.status).to eq(200)
|
||||
expect(DiagnosticDump.count).to be > b4
|
||||
expect(DiagnosticDump.last.device).to eq(device)
|
||||
expect(json[:fbos_version]).to eq("123_fbos_version")
|
||||
expect(json[:fbos_commit]).to eq("123_fbos_commit")
|
||||
expect(json[:firmware_commit]).to eq("123_firmware_commit")
|
||||
expect(json[:network_interface]).to eq("123_network_interface")
|
||||
expect(json[:fbos_dmesg_dump]).to eq("123_fbos_dmesg_dump")
|
||||
expect(json[:firmware_state]).to eq("123_firmware_state")
|
||||
expect(json[:ticket_identifier].length).to eq(5)
|
||||
end
|
||||
|
||||
it 'deletes' do
|
||||
sign_in user
|
||||
# DiagnosticDump.destroy_all
|
||||
device_config = FactoryBot.create(:diagnostic_dump, device: device)
|
||||
id = device_config.id
|
||||
delete :destroy, params: { id: device_config.id }
|
||||
expect(response.status).to be(200)
|
||||
expect(DiagnosticDump.exists?(id)).to be false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
FactoryBot.define do
|
||||
factory :diagnostic_dump do
|
||||
device
|
||||
fbos_version "123_fbos_version"
|
||||
fbos_commit "123_fbos_commit"
|
||||
firmware_commit "123_firmware_commit"
|
||||
network_interface "123_network_interface"
|
||||
fbos_dmesg_dump "123_fbos_dmesg_dump"
|
||||
firmware_state "123_firmware_state"
|
||||
ticket_identifier { rand(36**5).to_s(36) }
|
||||
end
|
||||
end
|
|
@ -7,6 +7,7 @@ export const panelState = (): ControlPanelState => {
|
|||
encoders_and_endstops: false,
|
||||
danger_zone: false,
|
||||
power_and_reset: false,
|
||||
pin_guard: false
|
||||
pin_guard: false,
|
||||
diagnostic_dumps: false
|
||||
};
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ export let bot: Everything["bot"] = {
|
|||
"danger_zone": false,
|
||||
"power_and_reset": false,
|
||||
"pin_guard": false,
|
||||
"diagnostic_dumps": false
|
||||
},
|
||||
"hardware": {
|
||||
"gpio_registry": {},
|
||||
|
|
|
@ -8,7 +8,8 @@ import {
|
|||
TaggedSensor,
|
||||
TaggedFirmwareConfig,
|
||||
TaggedPinBinding,
|
||||
TaggedLog
|
||||
TaggedLog,
|
||||
TaggedDiagnosticDump
|
||||
} from "../../resources/tagged_resources";
|
||||
import { ExecutableType } from "../../farm_designer/interfaces";
|
||||
import { fakeResource } from "../fake_resource";
|
||||
|
@ -117,6 +118,23 @@ export function fakePlant(): TaggedPlantPointer {
|
|||
});
|
||||
}
|
||||
|
||||
export function fakeDiagnosticDump(): TaggedDiagnosticDump {
|
||||
const string = "----PLACEHOLDER DIAG STUFF ---";
|
||||
return fakeResource("DiagnosticDump", {
|
||||
id: idCounter++,
|
||||
device_id: 123,
|
||||
ticket_identifier: string,
|
||||
fbos_commit: string,
|
||||
fbos_version: string,
|
||||
firmware_commit: string,
|
||||
firmware_state: string,
|
||||
network_interface: string,
|
||||
fbos_dmesg_dump: string,
|
||||
created_at: string,
|
||||
updated_at: string,
|
||||
});
|
||||
}
|
||||
|
||||
export function fakePoint(): TaggedGenericPointer {
|
||||
return fakeResource("Point", {
|
||||
id: idCounter++,
|
||||
|
|
|
@ -10,12 +10,22 @@ jest.mock("axios",
|
|||
|
||||
import { API } from "../../api";
|
||||
import { Content } from "../../constants";
|
||||
import { requestAccountExport } from "../request_account_export";
|
||||
import { requestAccountExport, generateFilename } from "../request_account_export";
|
||||
import { success } from "farmbot-toastr";
|
||||
import axios from "axios";
|
||||
import { fakeDevice } from "../../__test_support__/resource_index_builder";
|
||||
|
||||
API.setBaseUrl("http://www.foo.bar");
|
||||
|
||||
describe("generateFilename", () => {
|
||||
it("generates a filename", () => {
|
||||
const device = fakeDevice().body;
|
||||
device.name = "FOO";
|
||||
device.id = 123;
|
||||
const result = generateFilename({ device });
|
||||
expect(result).toEqual("export_foo_123.json");
|
||||
});
|
||||
});
|
||||
describe("requestAccountExport", () => {
|
||||
it("pops toast on completion (when API has email support)", async () => {
|
||||
await requestAccountExport();
|
||||
|
|
|
@ -8,13 +8,14 @@ import { DeviceAccountSettings } from "../devices/interfaces";
|
|||
interface DataDumpExport { device?: DeviceAccountSettings; }
|
||||
type Response = AxiosResponse<DataDumpExport | undefined>;
|
||||
|
||||
function generateFilename({ device }: DataDumpExport): string {
|
||||
const name = (device && device.name + "_" + device.id) || "farmbot";
|
||||
export function generateFilename({ device }: DataDumpExport): string {
|
||||
let name: string;
|
||||
name = device ? (device.name + "_" + device.id) : "farmbot";
|
||||
return `export_${name}.json`.toLowerCase();
|
||||
}
|
||||
|
||||
// Thanks, @KOL - https://stackoverflow.com/a/19328891/1064917
|
||||
function handleNow(data: DataDumpExport) {
|
||||
export function jsonDownload(data: object, fname = generateFilename(data)) {
|
||||
// When email is not available on the API (self hosted).
|
||||
// Will synchronously load backup over the wire (slow)
|
||||
const a = document.createElement("a");
|
||||
|
@ -24,7 +25,7 @@ function handleNow(data: DataDumpExport) {
|
|||
blob = new Blob([json], { type: "octet/stream" }),
|
||||
url = window.URL.createObjectURL(blob);
|
||||
a.href = url;
|
||||
a.download = generateFilename(data);
|
||||
a.download = fname;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
return a;
|
||||
|
@ -32,7 +33,7 @@ function handleNow(data: DataDumpExport) {
|
|||
|
||||
const ok = (resp: Response) => {
|
||||
const { data } = resp;
|
||||
return data ? handleNow(data) : success(t(Content.EXPORT_SENT));
|
||||
return data ? jsonDownload(data) : success(t(Content.EXPORT_SENT));
|
||||
};
|
||||
|
||||
export const requestAccountExport =
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { API } from "../api";
|
||||
|
||||
describe("API", () => {
|
||||
type L = typeof location;
|
||||
const fakeLocation = (input: Partial<L>) => input as L;
|
||||
it("requires initialization", () => {
|
||||
expect(() => API.current).toThrow();
|
||||
const BASE = "http://localhost:3000";
|
||||
API.setBaseUrl(BASE);
|
||||
[
|
||||
[API.current.pointSearchPath, BASE + "/api/points/search"],
|
||||
[API.current.sensorReadingPath, BASE + "/api/sensor_readings"],
|
||||
[API.current.deviceConfigPath, BASE + "/api/device_configs"],
|
||||
[API.current.plantTemplatePath, BASE + "/api/plant_templates"],
|
||||
[API.current.diagnosticDumpsPath, BASE + "/api/diagnostic_dumps"],
|
||||
[API.current.farmwareInstallationPath, BASE + "/api/farmware_installations"],
|
||||
].map(x => expect(x[0]).toEqual(x[1]));
|
||||
});
|
||||
|
||||
it("infers the correct port", () => {
|
||||
const xmp: [string, L][] = [
|
||||
["3000", fakeLocation({ port: "3808" })],
|
||||
["1234", fakeLocation({ port: "1234" })],
|
||||
["80", fakeLocation({ port: undefined })],
|
||||
["443", fakeLocation({ port: undefined, origin: "https://x.y.z" })],
|
||||
];
|
||||
xmp.map(x => expect(API.inferPort(x[1])).toEqual(x[0]));
|
||||
});
|
||||
});
|
|
@ -17,7 +17,7 @@ interface UrlInfo {
|
|||
export class API {
|
||||
/** Guesses the most appropriate API port based on a number of environment
|
||||
* factors such as hostname and protocol (HTTP vs. HTTPS). */
|
||||
static inferPort(): string {
|
||||
static inferPort(location = window.location): string {
|
||||
|
||||
// ATTEMPT 1: Most devs running a webpack server on localhost
|
||||
// run the API on port 3000.
|
||||
|
@ -115,7 +115,7 @@ export class API {
|
|||
/** /api/points/ */
|
||||
get pointsPath() { return `${this.baseUrl}/api/points/`; }
|
||||
/** /api/points/search */
|
||||
get pointSearchPath() { return `${this.pointsPath}/search/`; }
|
||||
get pointSearchPath() { return `${this.pointsPath}search`; }
|
||||
/** Rather than returning ALL logs, returns a filtered subset.
|
||||
* /api/logs/search */
|
||||
get filteredLogsPath() { return `${this.baseUrl}/api/logs/search`; }
|
||||
|
@ -145,6 +145,8 @@ export class API {
|
|||
get exportDataPath() { return `${this.baseUrl}/api/export_data`; }
|
||||
/** /api/plant_templates/:id */
|
||||
get plantTemplatePath() { return `${this.baseUrl}/api/plant_templates`; }
|
||||
/** /api/diagnostic_dumps/:id */
|
||||
get diagnosticDumpsPath() { return `${this.baseUrl}/api/diagnostic_dumps`; }
|
||||
/** /api/farmware_installations/:id */
|
||||
get farmwareInstallationPath() {
|
||||
return `${this.baseUrl}/api/farmware_installations`;
|
||||
|
|
|
@ -778,3 +778,21 @@ ul {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.panel-link {
|
||||
// TODO: Might need to move this to a better place. CC: @gabrielBurnworth
|
||||
//
|
||||
// PROBLEM: <a> links in the device panel are invisible.
|
||||
// SOLUTION: Add {color: $dark_gray}
|
||||
// PROBLEM 2: This rule probably does not belong at the bottom of
|
||||
// global.scss
|
||||
color: $dark_gray;
|
||||
|
||||
&:visited {
|
||||
color: $dark_gray;
|
||||
};
|
||||
|
||||
&:active {
|
||||
color: $dark_gray;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,7 +13,8 @@ const mockDevice = {
|
|||
home: jest.fn(() => { return Promise.resolve(); }),
|
||||
sync: jest.fn(() => { return Promise.resolve(); }),
|
||||
readStatus: jest.fn(() => Promise.resolve()),
|
||||
updateConfig: jest.fn(() => Promise.resolve())
|
||||
updateConfig: jest.fn(() => Promise.resolve()),
|
||||
dumpInfo: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
|
||||
jest.mock("../../device", () => ({
|
||||
|
@ -174,6 +175,13 @@ describe("MCUFactoryReset()", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("requestDiagnostic", () => {
|
||||
it("requests that FBOS build a diagnostic report", () => {
|
||||
actions.requestDiagnostic();
|
||||
expect(mockDevice.dumpInfo).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("settingToggle()", () => {
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks();
|
||||
|
|
|
@ -45,8 +45,12 @@ describe("botRedcuer", () => {
|
|||
type: Actions.BULK_TOGGLE_CONTROL_PANEL,
|
||||
payload: true
|
||||
});
|
||||
_.values(_.omit(state.controlPanelState, "power_and_reset"))
|
||||
.map(value => expect(value).toBeTruthy());
|
||||
|
||||
const bulkToggable =
|
||||
_.omit(state.controlPanelState, "power_and_reset", "diagnostic_dumps");
|
||||
_.values(bulkToggable).map(value => {
|
||||
expect(value).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("fetches OS update info", () => {
|
||||
|
|
|
@ -140,6 +140,11 @@ export function execSequence(sequence: Sequence) {
|
|||
}
|
||||
}
|
||||
|
||||
export function requestDiagnostic() {
|
||||
const noun = "Diagnostic Request";
|
||||
return getDevice().dumpInfo().then(commandOK(noun), commandErr(noun));
|
||||
}
|
||||
|
||||
export let saveAccountChanges: Thunk = function (_dispatch, getState) {
|
||||
return save(getDeviceAccountSettings(getState().resources.index));
|
||||
};
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
jest.mock("../../../account/request_account_export", () => {
|
||||
return { jsonDownload: jest.fn() };
|
||||
});
|
||||
|
||||
jest.mock("../../../api/crud", () => {
|
||||
return { destroy: jest.fn() };
|
||||
});
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { DiagnosticDumpRow } from "../diagnostic_dump_row";
|
||||
import { fakeDiagnosticDump } from "../../../__test_support__/fake_state/resources";
|
||||
import { jsonDownload } from "../../../account/request_account_export";
|
||||
import { destroy } from "../../../api/crud";
|
||||
|
||||
describe("<DiagnosticDumpRow/>", () => {
|
||||
it("renders a single diagnostic dump", () => {
|
||||
const dispatch = jest.fn();
|
||||
const diag = fakeDiagnosticDump();
|
||||
diag.body.ticket_identifier = "0000";
|
||||
const el = mount(<DiagnosticDumpRow dispatch={dispatch} diag={diag} />);
|
||||
expect(el.text()).toContain("0000");
|
||||
el.find("a").first().simulate("click");
|
||||
expect(jsonDownload).toHaveBeenCalledWith(diag.body, "farmbot_diagnostics_0000.json");
|
||||
el.find("button.red").first().simulate("click");
|
||||
expect(destroy).toHaveBeenCalledWith(diag.uuid);
|
||||
});
|
||||
});
|
|
@ -26,6 +26,7 @@ describe("<FarmbotOsSettings/>", () => {
|
|||
const fakeProps = (): FarmbotOsProps => {
|
||||
return {
|
||||
account: fakeResource("Device", { id: 0, name: "", tz_offset_hrs: 0 }),
|
||||
diagnostics: [],
|
||||
dispatch: jest.fn(),
|
||||
bot,
|
||||
botToMqttLastSeen: "",
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import * as React from "react";
|
||||
import { render } from "enzyme";
|
||||
import { SendDiagnosticReport } from "../send_diagnostic_report";
|
||||
import { fakeDiagnosticDump } from "../../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("<SendDiagnosticReport/>", () => {
|
||||
it("renders", () => {
|
||||
const dispatch = jest.fn();
|
||||
const shouldDisplay = jest.fn(() => true);
|
||||
const fake = fakeDiagnosticDump();
|
||||
const el = render(<SendDiagnosticReport
|
||||
diagnostics={[fake]}
|
||||
expanded={true}
|
||||
dispatch={dispatch}
|
||||
shouldDisplay={shouldDisplay} />);
|
||||
expect(el.text()).toContain("DIAGNOSTIC CHECK");
|
||||
expect(shouldDisplay).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("doesn't render", () => {
|
||||
const dispatch = jest.fn();
|
||||
const shouldDisplay = jest.fn(() => false);
|
||||
const fake = fakeDiagnosticDump();
|
||||
const el = render(<SendDiagnosticReport
|
||||
diagnostics={[fake]}
|
||||
expanded={true}
|
||||
dispatch={dispatch}
|
||||
shouldDisplay={shouldDisplay} />);
|
||||
expect(el.text()).toEqual("");
|
||||
expect(shouldDisplay).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col } from "../../ui";
|
||||
import { TaggedDiagnosticDump } from "../../resources/tagged_resources";
|
||||
import { jsonDownload } from "../../account/request_account_export";
|
||||
import { destroy } from "../../api/crud";
|
||||
import { ago } from "../connectivity/status_checks";
|
||||
|
||||
export interface Props {
|
||||
diag: TaggedDiagnosticDump;
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
export class DiagnosticDumpRow extends React.Component<Props, {}> {
|
||||
get ticket() { return this.props.diag.body.ticket_identifier; }
|
||||
|
||||
get age() { return ago(this.props.diag.body.created_at); }
|
||||
|
||||
destroy = () => this.props.dispatch(destroy(this.props.diag.uuid));
|
||||
|
||||
download = (e: React.MouseEvent<{}>) => {
|
||||
e.preventDefault();
|
||||
const { body } = this.props.diag;
|
||||
const { ticket_identifier } = body;
|
||||
const fileName = `farmbot_diagnostics_${ticket_identifier}.json`;
|
||||
jsonDownload(body, fileName);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Row>
|
||||
<Col xs={1}>
|
||||
<span>
|
||||
<button
|
||||
className="red fb-button del-button"
|
||||
onClick={this.destroy}>
|
||||
<i className="fa fa-times" />
|
||||
</button>
|
||||
</span>
|
||||
</Col>
|
||||
<Col xs={11}>
|
||||
<a onClick={this.download} className="panel-link">
|
||||
Download diagnostic report {this.ticket} (Saved {this.age})
|
||||
</a>
|
||||
</Col>
|
||||
</Row >;
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ import { AutoUpdateRow } from "./fbos_settings/auto_update_row";
|
|||
import { AutoSyncRow } from "./fbos_settings/auto_sync_row";
|
||||
import { isUndefined } from "lodash";
|
||||
import { PowerAndReset } from "./fbos_settings/power_and_reset";
|
||||
import { SendDiagnosticReport } from "./send_diagnostic_report";
|
||||
|
||||
import axios from "axios";
|
||||
|
||||
export enum ColWidth {
|
||||
|
@ -166,6 +168,11 @@ export class FarmbotOsSettings
|
|||
sourceFbosConfig={sourceFbosConfig}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
botOnline={botOnline} />
|
||||
<SendDiagnosticReport
|
||||
diagnostics={this.props.diagnostics}
|
||||
expanded={this.props.bot.controlPanelState.diagnostic_dumps}
|
||||
shouldDisplay={this.props.shouldDisplay}
|
||||
dispatch={this.props.dispatch} />
|
||||
</MustBeOnline>
|
||||
</WidgetBody>
|
||||
</form>
|
||||
|
|
|
@ -15,7 +15,7 @@ export function PowerAndReset(props: PowerAndResetProps) {
|
|||
return <section>
|
||||
<div style={{ fontSize: "1px" }}>
|
||||
<Header
|
||||
bool={power_and_reset}
|
||||
expanded={power_and_reset}
|
||||
title={t("Power and Reset")}
|
||||
name={"power_and_reset"}
|
||||
dispatch={dispatch} />
|
||||
|
|
|
@ -7,7 +7,7 @@ describe("<Header/>", () => {
|
|||
const fn = jest.fn();
|
||||
const el = shallow(<Header
|
||||
title="FOO"
|
||||
bool={true}
|
||||
expanded={true}
|
||||
name={"motors"}
|
||||
dispatch={fn} />);
|
||||
expect(el.text()).toContain("FOO");
|
||||
|
|
|
@ -13,7 +13,7 @@ export function DangerZone(props: DangerZoneProps) {
|
|||
|
||||
return <section>
|
||||
<Header
|
||||
bool={danger_zone}
|
||||
expanded={danger_zone}
|
||||
title={t("Danger Zone")}
|
||||
name={"danger_zone"}
|
||||
dispatch={dispatch} />
|
||||
|
|
|
@ -21,7 +21,7 @@ export function EncodersAndEndStops(props: EncodersProps) {
|
|||
|
||||
return <section>
|
||||
<Header
|
||||
bool={encoders_and_endstops}
|
||||
expanded={encoders_and_endstops}
|
||||
title={t("Encoders and Endstops")}
|
||||
name={"encoders_and_endstops"}
|
||||
dispatch={dispatch} />
|
||||
|
|
|
@ -7,12 +7,12 @@ interface Props {
|
|||
dispatch: Function;
|
||||
name: keyof ControlPanelState;
|
||||
title: string;
|
||||
bool: boolean;
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
export let Header = (props: Props) => {
|
||||
const { dispatch, name, title, bool } = props;
|
||||
const icon_string = bool ? "minus" : "plus";
|
||||
const { dispatch, name, title, expanded } = props;
|
||||
const icon_string = expanded ? "minus" : "plus";
|
||||
return <h4 onClick={() => dispatch(toggleControlPanel(name))}>
|
||||
{t(title)}
|
||||
<span className="icon-toggle">
|
||||
|
|
|
@ -36,7 +36,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
|||
title={t("Homing and Calibration")}
|
||||
name={"homing_and_calibration"}
|
||||
dispatch={dispatch}
|
||||
bool={homing_and_calibration} />
|
||||
expanded={homing_and_calibration} />
|
||||
<Collapse isOpen={!!homing_and_calibration}>
|
||||
<HomingRow hardware={hardware} botDisconnected={botDisconnected} />
|
||||
<CalibrationRow hardware={hardware} botDisconnected={botDisconnected} />
|
||||
|
|
|
@ -25,7 +25,7 @@ export function Motors(props: MotorsProps) {
|
|||
|
||||
return <section>
|
||||
<Header
|
||||
bool={controlPanelState.motors}
|
||||
expanded={controlPanelState.motors}
|
||||
title={t("Motors")}
|
||||
name={"motors"}
|
||||
dispatch={dispatch} />
|
||||
|
|
|
@ -15,7 +15,7 @@ export function PinGuard(props: PinGuardProps) {
|
|||
|
||||
return <section>
|
||||
<Header
|
||||
bool={pin_guard}
|
||||
expanded={pin_guard}
|
||||
title={t("Pin Guard")}
|
||||
name={"pin_guard"}
|
||||
dispatch={dispatch} />
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col } from "../../ui";
|
||||
import { ColWidth } from "./farmbot_os_settings";
|
||||
import { t } from "i18next";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { Header } from "./hardware_settings/header";
|
||||
import { ShouldDisplay, Feature } from "../interfaces";
|
||||
import { TaggedDiagnosticDump } from "../../resources/tagged_resources";
|
||||
import { DiagnosticDumpRow } from "./diagnostic_dump_row";
|
||||
import { requestDiagnostic } from "../actions";
|
||||
|
||||
export interface DiagReportProps {
|
||||
dispatch: Function;
|
||||
expanded: boolean;
|
||||
shouldDisplay: ShouldDisplay;
|
||||
diagnostics: TaggedDiagnosticDump[];
|
||||
}
|
||||
|
||||
export class SendDiagnosticReport extends React.Component<DiagReportProps, {}>{
|
||||
show = () => {
|
||||
return <section>
|
||||
<div style={{ fontSize: "1px" }}>
|
||||
<Header
|
||||
expanded={this.props.expanded}
|
||||
title={t("Diagnostic Reports")}
|
||||
name={Feature.diagnostic_dumps}
|
||||
dispatch={this.props.dispatch} />
|
||||
</div>
|
||||
<Collapse isOpen={this.props.expanded}>
|
||||
<Row>
|
||||
<Col xs={ColWidth.label}>
|
||||
<label>
|
||||
{t("DIAGNOSTIC CHECK")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<p>...</p>
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
<button
|
||||
className="fb-button yellow"
|
||||
onClick={requestDiagnostic}>
|
||||
{t("Record Diagnostic")}
|
||||
</button>
|
||||
</Col>
|
||||
</Row>
|
||||
{this.props.diagnostics.map(d => {
|
||||
return <DiagnosticDumpRow
|
||||
key={d.uuid}
|
||||
diag={d}
|
||||
dispatch={this.props.dispatch} />;
|
||||
})}
|
||||
</Collapse>
|
||||
</section>;
|
||||
}
|
||||
|
||||
noShow = () => <div />;
|
||||
|
||||
render() {
|
||||
const show = this.props.shouldDisplay(Feature.diagnostic_dumps);
|
||||
return (show ? this.show : this.noShow)();
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ const SIX_HOURS = HOUR * 6;
|
|||
|
||||
const NOT_SEEN = t("No messages seen yet.");
|
||||
|
||||
function ago(input: string) {
|
||||
export function ago(input: string) {
|
||||
return moment(new Date(input)).fromNow();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Diagnosis, DiagnosisName } from "./connectivity/diagnosis";
|
|||
import { StatusRowProps } from "./connectivity/connectivity_row";
|
||||
import { resetConnectionInfo } from "./actions";
|
||||
import { PinBindings } from "./components/pin_bindings";
|
||||
import { selectAllDiagnosticDumps } from "../resources/selectors";
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export class Devices extends React.Component<Props, {}> {
|
||||
|
@ -58,6 +59,7 @@ export class Devices extends React.Component<Props, {}> {
|
|||
<Row>
|
||||
<Col xs={12} sm={6}>
|
||||
<FarmbotOsSettings
|
||||
diagnostics={selectAllDiagnosticDumps(this.props.resources)}
|
||||
account={this.props.deviceAccount}
|
||||
dispatch={this.props.dispatch}
|
||||
bot={this.props.bot}
|
||||
|
|
|
@ -11,7 +11,8 @@ import {
|
|||
TaggedImage,
|
||||
TaggedPeripheral,
|
||||
TaggedDevice,
|
||||
TaggedSensor
|
||||
TaggedSensor,
|
||||
TaggedDiagnosticDump
|
||||
} from "../resources/tagged_resources";
|
||||
import { ResourceIndex } from "../resources/interfaces";
|
||||
import { TaggedUser } from "../resources/tagged_resources";
|
||||
|
@ -64,6 +65,7 @@ export enum Feature {
|
|||
jest_feature = "jest_feature", // for tests
|
||||
backscheduled_regimens = "backscheduled_regimens",
|
||||
endstop_invert = "endstop_invert",
|
||||
diagnostic_dumps = "diagnostic_dumps"
|
||||
}
|
||||
/** Object fetched from FEATURE_MIN_VERSIONS_URL. */
|
||||
export type MinOsFeatureLookup = Partial<Record<Feature, string>>;
|
||||
|
@ -148,6 +150,7 @@ export interface CalibrationButtonProps {
|
|||
|
||||
export interface FarmbotOsProps {
|
||||
bot: BotState;
|
||||
diagnostics: TaggedDiagnosticDump[];
|
||||
account: TaggedDevice;
|
||||
botToMqttStatus: NetworkState;
|
||||
botToMqttLastSeen: string;
|
||||
|
@ -221,4 +224,5 @@ export interface ControlPanelState {
|
|||
danger_zone: boolean;
|
||||
power_and_reset: boolean;
|
||||
pin_guard: boolean;
|
||||
diagnostic_dumps: boolean;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ export let initialState = (): BotState => ({
|
|||
danger_zone: false,
|
||||
power_and_reset: false,
|
||||
pin_guard: false,
|
||||
diagnostic_dumps: false
|
||||
},
|
||||
hardware: {
|
||||
gpio_registry: {},
|
||||
|
|
|
@ -79,7 +79,8 @@ export function emptyState(): RestResources {
|
|||
DeviceConfig: [],
|
||||
PinBinding: [],
|
||||
PlantTemplate: [],
|
||||
SavedGarden: []
|
||||
SavedGarden: [],
|
||||
DiagnosticDump: []
|
||||
},
|
||||
byKindAndId: {},
|
||||
references: {}
|
||||
|
@ -110,6 +111,7 @@ export let resourceReducer = generateReducer
|
|||
switch (resource.kind) {
|
||||
case "Crop":
|
||||
case "Device":
|
||||
case "DiagnosticDump":
|
||||
case "FarmEvent":
|
||||
case "FarmwareInstallation":
|
||||
case "FbosConfig":
|
||||
|
@ -144,6 +146,7 @@ export let resourceReducer = generateReducer
|
|||
switch (resource.kind) {
|
||||
case "Crop":
|
||||
case "Device":
|
||||
case "DiagnosticDump":
|
||||
case "FarmEvent":
|
||||
case "FarmwareInstallation":
|
||||
case "FbosConfig":
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
TaggedFirmwareConfig,
|
||||
TaggedToolSlotPointer,
|
||||
TaggedPinBinding,
|
||||
TaggedDiagnosticDump,
|
||||
} from "./tagged_resources";
|
||||
import { sortResourcesById, betterCompact, bail } from "../util";
|
||||
import { error } from "farmbot-toastr";
|
||||
|
@ -79,6 +80,8 @@ export const selectAllToolSlots = (i: ResourceIndex): TaggedToolSlotPointer[] =>
|
|||
}));
|
||||
};
|
||||
|
||||
export const selectAllDiagnosticDumps =
|
||||
(i: ResourceIndex) => findAll<TaggedDiagnosticDump>(i, "DiagnosticDump");
|
||||
export const selectAllRegimens = (i: ResourceIndex) => findAll<TaggedRegimen>(i, "Regimen");
|
||||
export const selectAllSensors = (i: ResourceIndex) => findAll<TaggedSensor>(i, "Sensor");
|
||||
export const selectAllPinBindings =
|
||||
|
|
|
@ -30,6 +30,7 @@ export type ResourceName =
|
|||
| "Crop"
|
||||
| "Device"
|
||||
| "DeviceConfig"
|
||||
| "DiagnosticDump"
|
||||
| "FarmEvent"
|
||||
| "FarmwareInstallation"
|
||||
| "FbosConfig"
|
||||
|
@ -95,6 +96,7 @@ export interface Resource<T extends ResourceName, U extends object>
|
|||
export type TaggedResource =
|
||||
| TaggedCrop
|
||||
| TaggedDevice
|
||||
| TaggedDiagnosticDump
|
||||
| TaggedFarmEvent
|
||||
| TaggedFarmwareInstallation
|
||||
| TaggedFbosConfig
|
||||
|
@ -132,6 +134,7 @@ export type TaggedSensorReading = Resource<"SensorReading", SensorReading>;
|
|||
export type TaggedSensor = Resource<"Sensor", Sensor>;
|
||||
export type TaggedSavedGarden = Resource<"SavedGarden", SavedGarden>;
|
||||
export type TaggedPlantTemplate = Resource<"PlantTemplate", PlantTemplate>;
|
||||
export type TaggedDiagnosticDump = Resource<"DiagnosticDump", DiagnosticDump>;
|
||||
|
||||
type PointUnion = GenericPointer | PlantPointer | ToolSlotPointer;
|
||||
|
||||
|
@ -147,6 +150,20 @@ export type TaggedWebcamFeed = Resource<"WebcamFeed", WebcamFeed>;
|
|||
export type TaggedFarmwareInstallation =
|
||||
Resource<"FarmwareInstallation", FarmwareInstallation>;
|
||||
|
||||
export interface DiagnosticDump {
|
||||
id: number;
|
||||
device_id: number;
|
||||
ticket_identifier: string;
|
||||
fbos_commit: string;
|
||||
fbos_version: string;
|
||||
firmware_commit: string;
|
||||
firmware_state: string;
|
||||
network_interface: string;
|
||||
fbos_dmesg_dump: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
/** Spot check to be certain a TaggedResource is what it says it is. */
|
||||
export function sanityCheck(x: object): x is TaggedResource {
|
||||
if (isTaggedResource(x)) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Peripheral } from "../controls/peripherals/interfaces";
|
|||
import { FarmEvent, SavedGarden, PlantTemplate } from "../farm_designer/interfaces";
|
||||
import { Image } from "../farmware/images/interfaces";
|
||||
import { DeviceAccountSettings } from "../devices/interfaces";
|
||||
import { ResourceName } from "../resources/tagged_resources";
|
||||
import { ResourceName, DiagnosticDump } from "../resources/tagged_resources";
|
||||
import { User } from "../auth/interfaces";
|
||||
import { WebcamFeed } from "../controls/interfaces";
|
||||
import { WebAppConfig } from "../config_storage/web_app_configs";
|
||||
|
@ -61,5 +61,5 @@ export function fetchSyncData(dispatch: Function) {
|
|||
fetch<PinBinding[]>("PinBinding", API.current.pinBindingPath);
|
||||
fetch<SavedGarden[]>("SavedGarden", API.current.savedGardensPath);
|
||||
fetch<PlantTemplate[]>("PlantTemplate", API.current.plantTemplatePath);
|
||||
|
||||
fetch<DiagnosticDump[]>("DiagnosticDump", API.current.diagnosticDumpsPath);
|
||||
}
|
||||
|
|
69
yarn.lock
69
yarn.lock
|
@ -84,9 +84,9 @@
|
|||
version "8.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/i18next/-/i18next-8.4.3.tgz#9136a9551bf5bf7169aa9f3125c1743f1f8dd6de"
|
||||
|
||||
"@types/jest@23.0.2":
|
||||
version "23.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.0.2.tgz#f03f9e6dd2206cc2a2e8fd033161b0b7cf905db6"
|
||||
"@types/jest@23.1.0":
|
||||
version "23.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.1.0.tgz#8054dd838ba23dc331794d26456b86c7e50bf0f6"
|
||||
|
||||
"@types/lodash@4.14.109":
|
||||
version "4.14.109"
|
||||
|
@ -106,9 +106,9 @@
|
|||
version "10.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.1.2.tgz#1b928a0baa408fc8ae3ac012cc81375addc147c6"
|
||||
|
||||
"@types/node@10.3.2":
|
||||
version "10.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.3.2.tgz#3840ec6c12556fdda6e0e6d036df853101d732a4"
|
||||
"@types/node@10.3.3":
|
||||
version "10.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.3.3.tgz#8798d9e39af2fa604f715ee6a6b19796528e46c3"
|
||||
|
||||
"@types/react-color@2.13.5":
|
||||
version "2.13.5"
|
||||
|
@ -2392,9 +2392,9 @@ farmbot-toastr@^1.0.0, farmbot-toastr@^1.0.3:
|
|||
farmbot-toastr "^1.0.0"
|
||||
typescript "^2.3.4"
|
||||
|
||||
farmbot@6.0.0-rc2:
|
||||
version "6.0.0-rc2"
|
||||
resolved "https://registry.yarnpkg.com/farmbot/-/farmbot-6.0.0-rc2.tgz#614e418de2264d8366ef1e8fd83121fc18f40356"
|
||||
farmbot@6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/farmbot/-/farmbot-6.0.1.tgz#46629b90e942141a6bacf6008059afac9928764a"
|
||||
dependencies:
|
||||
mqtt "2.15.0"
|
||||
|
||||
|
@ -3090,9 +3090,9 @@ https-browserify@^1.0.0:
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||
|
||||
i18next@11.3.2:
|
||||
version "11.3.2"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-11.3.2.tgz#4a1a7bb14383ba6aed4abca139b03681fc96e023"
|
||||
i18next@11.3.3:
|
||||
version "11.3.3"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-11.3.3.tgz#a6ca3c2a93237c94e242bda7df3411588ac37ea1"
|
||||
|
||||
iconv-lite@0.4.19:
|
||||
version "0.4.19"
|
||||
|
@ -4049,9 +4049,9 @@ jsbn@~0.1.0:
|
|||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||
|
||||
jscpd@0.6.19:
|
||||
version "0.6.19"
|
||||
resolved "https://registry.yarnpkg.com/jscpd/-/jscpd-0.6.19.tgz#c2ef022db2e06c41768cc3b4fbd7ec97e7117e8c"
|
||||
jscpd@0.6.21:
|
||||
version "0.6.21"
|
||||
resolved "https://registry.yarnpkg.com/jscpd/-/jscpd-0.6.21.tgz#bf4f0be95526146412738657aa96c9d03930fa4b"
|
||||
dependencies:
|
||||
blamer "^0.1.9"
|
||||
bluebird "^3.0.5"
|
||||
|
@ -5806,9 +5806,9 @@ react-day-picker@^7.0.7:
|
|||
dependencies:
|
||||
prop-types "^15.6.1"
|
||||
|
||||
react-dom@16.4:
|
||||
version "16.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.0.tgz#099f067dd5827ce36a29eaf9a6cdc7cbf6216b1e"
|
||||
react-dom@16.4.1:
|
||||
version "16.4.1"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.1.tgz#7f8b0223b3a5fbe205116c56deb85de32685dad6"
|
||||
dependencies:
|
||||
fbjs "^0.8.16"
|
||||
loose-envify "^1.1.0"
|
||||
|
@ -5819,6 +5819,10 @@ react-is@^16.4.0:
|
|||
version "16.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.0.tgz#cc9fdc855ac34d2e7d9d2eb7059bbc240d35ffcf"
|
||||
|
||||
react-is@^16.4.1:
|
||||
version "16.4.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e"
|
||||
|
||||
react-popper@^0.8.2:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.8.3.tgz#0f73333137c9fb0af6ec4074d2d0585a0a0461e1"
|
||||
|
@ -5858,7 +5862,16 @@ react-router@^3:
|
|||
prop-types "^15.5.6"
|
||||
warning "^3.0.0"
|
||||
|
||||
react-test-renderer@16.4.0, react-test-renderer@^16.0.0-0:
|
||||
react-test-renderer@16.4.1:
|
||||
version "16.4.1"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.1.tgz#f2fb30c2c7b517db6e5b10ed20bb6b0a7ccd8d70"
|
||||
dependencies:
|
||||
fbjs "^0.8.16"
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.0"
|
||||
react-is "^16.4.1"
|
||||
|
||||
react-test-renderer@^16.0.0-0:
|
||||
version "16.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.0.tgz#0dbe0e24263e94e1830c7afb1f403707fad313a3"
|
||||
dependencies:
|
||||
|
@ -5885,9 +5898,9 @@ react-transition-group@^2.3.1:
|
|||
loose-envify "^1.3.1"
|
||||
prop-types "^15.6.1"
|
||||
|
||||
react@16.4:
|
||||
version "16.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.4.0.tgz#402c2db83335336fba1962c08b98c6272617d585"
|
||||
react@16.4.1:
|
||||
version "16.4.1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32"
|
||||
dependencies:
|
||||
fbjs "^0.8.16"
|
||||
loose-envify "^1.1.0"
|
||||
|
@ -7074,9 +7087,9 @@ typedarray@^0.0.6:
|
|||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
|
||||
typescript@2.9.1:
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.1.tgz#fdb19d2c67a15d11995fd15640e373e09ab09961"
|
||||
typescript@2.9.2:
|
||||
version "2.9.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
|
||||
|
||||
typescript@^2.0.9, typescript@^2.3.4:
|
||||
version "2.8.3"
|
||||
|
@ -7341,9 +7354,9 @@ webidl-conversions@^4.0.2:
|
|||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||
|
||||
webpack-cli@3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.0.4.tgz#55d6ad2cdd608de8c0f757dde5bc4bf5bd2dec68"
|
||||
webpack-cli@3.0.8:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.0.8.tgz#90eddcf04a4bfc31aa8c0edc4c76785bc4f1ccd9"
|
||||
dependencies:
|
||||
chalk "^2.4.1"
|
||||
cross-spawn "^6.0.5"
|
||||
|
|
Loading…
Reference in New Issue