access API FBOS config storage
parent
867d1714aa
commit
2765d345ac
|
@ -0,0 +1,12 @@
|
|||
import { ControlPanelState } from "../devices/interfaces";
|
||||
|
||||
export const panelState = (): ControlPanelState => {
|
||||
return {
|
||||
homing_and_calibration: false,
|
||||
motors: false,
|
||||
encoders_and_endstops: false,
|
||||
danger_zone: false,
|
||||
power_and_reset: false,
|
||||
pin_guard: false
|
||||
};
|
||||
};
|
|
@ -3,7 +3,7 @@ import { buildResourceIndex } from "../resource_index_builder";
|
|||
import {
|
||||
TaggedFarmEvent, TaggedSequence, TaggedRegimen, TaggedImage,
|
||||
TaggedTool, TaggedToolSlotPointer, TaggedUser, TaggedWebcamFeed,
|
||||
TaggedPlantPointer, TaggedGenericPointer, TaggedPeripheral
|
||||
TaggedPlantPointer, TaggedGenericPointer, TaggedPeripheral, TaggedFbosConfig
|
||||
} from "../../resources/tagged_resources";
|
||||
import { ExecutableType } from "../../farm_designer/interfaces";
|
||||
import { fakeResource } from "../fake_resource";
|
||||
|
@ -136,3 +136,25 @@ export function fakePeripheral(): TaggedPeripheral {
|
|||
pin: 1
|
||||
});
|
||||
}
|
||||
|
||||
export function fakeFbosConfig(): TaggedFbosConfig {
|
||||
return fakeResource("FbosConfig", {
|
||||
id: 1,
|
||||
device_id: 1,
|
||||
created_at: "",
|
||||
updated_at: "",
|
||||
auto_sync: false,
|
||||
beta_opt_in: false,
|
||||
disable_factory_reset: false,
|
||||
firmware_input_log: false,
|
||||
firmware_output_log: false,
|
||||
sequence_body_log: false,
|
||||
sequence_complete_log: false,
|
||||
sequence_init_log: false,
|
||||
network_not_found_timer: 0,
|
||||
firmware_hardware: "arduino",
|
||||
api_migrated: false,
|
||||
os_auto_update: false,
|
||||
arduino_debug_messages: false
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import "../controls/interfaces";
|
|||
import "../controls/peripherals/interfaces";
|
||||
import "../controls/webcam/interfaces";
|
||||
import "../devices/components/interfaces";
|
||||
import "../devices/components/fbos_settings/interfaces";
|
||||
import "../devices/interfaces";
|
||||
import "../draggable/interfaces";
|
||||
import "../farm_designer/farm_events/calendar/interfaces";
|
||||
|
|
|
@ -115,8 +115,10 @@ export class API {
|
|||
get logsPath() { return `${this.baseUrl}/api/logs/`; }
|
||||
/** /api/webcam_feed */
|
||||
get webcamFeedPath() { return `${this.baseUrl}/api/webcam_feeds/`; }
|
||||
/** /api/webcam_feed */
|
||||
/** /api/web_app_config */
|
||||
get webAppConfigPath() { return `${this.baseUrl}/api/web_app_config/`; }
|
||||
/** /api/fbos_config */
|
||||
get fbosConfigPath() { return `${this.baseUrl}/api/fbos_config/`; }
|
||||
/** /api/users/verify/:token */
|
||||
verificationPath = (token: string) => ("/api/users/verify/" + token);
|
||||
}
|
||||
|
|
|
@ -218,6 +218,7 @@ export function urlFor(tag: ResourceName) {
|
|||
Image: API.current.imagesPath,
|
||||
Log: API.current.logsPath,
|
||||
WebcamFeed: API.current.webcamFeedPath,
|
||||
FbosConfig: API.current.fbosConfigPath,
|
||||
WebAppConfig: API.current.webAppConfigPath
|
||||
};
|
||||
const url = OPTIONS[tag];
|
||||
|
@ -229,7 +230,7 @@ export function urlFor(tag: ResourceName) {
|
|||
}
|
||||
}
|
||||
|
||||
const SINGULAR_RESOURCE: ResourceName[] = ["WebAppConfig"];
|
||||
const SINGULAR_RESOURCE: ResourceName[] = ["WebAppConfig", "FbosConfig"];
|
||||
|
||||
/** Shared functionality in create() and update(). */
|
||||
function updateViaAjax(payl: AjaxUpdatePayload) {
|
||||
|
|
|
@ -7,6 +7,7 @@ const BLACKLIST: ResourceName[] = [
|
|||
"WebcamFeed",
|
||||
"User",
|
||||
"WebAppConfig",
|
||||
"FbosConfig",
|
||||
];
|
||||
|
||||
export function maybeStartTracking(uuid: string) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { BooleanConfigKey } from "./web_app_configs";
|
||||
import { BooleanConfigKey as BooleanWebAppConfigKey } from "./web_app_configs";
|
||||
import { GetState } from "../redux/interfaces";
|
||||
import { getWebAppConfig } from "../resources/selectors";
|
||||
import { edit, save } from "../api/crud";
|
||||
|
||||
/** Inverts boolean config key in WebAppConfig object, stored in the API. */
|
||||
export function toggleWebAppBool(key: BooleanConfigKey) {
|
||||
export function toggleWebAppBool(key: BooleanWebAppConfigKey) {
|
||||
return (dispatch: Function, getState: GetState) => {
|
||||
const conf = getWebAppConfig(getState().resources.index);
|
||||
if (conf) {
|
||||
|
|
|
@ -102,6 +102,7 @@ export interface ToggleButtonProps {
|
|||
toggleValue: number | string | boolean | undefined;
|
||||
disabled?: boolean | undefined;
|
||||
customText?: { textFalse: string, textTrue: string };
|
||||
dim?: boolean;
|
||||
}
|
||||
|
||||
export interface WebcamFeed {
|
||||
|
|
|
@ -44,7 +44,7 @@ export class ToggleButton extends React.Component<ToggleButtonProps, {}> {
|
|||
const cb = () => !this.props.disabled && this.props.toggleAction();
|
||||
return <button
|
||||
disabled={!!this.props.disabled}
|
||||
className={this.css()}
|
||||
className={this.css() + (this.props.dim ? " dim" : "")}
|
||||
onClick={cb}>
|
||||
{this.caption()}
|
||||
</button>;
|
||||
|
|
|
@ -248,6 +248,9 @@
|
|||
&:hover {
|
||||
background: $green;
|
||||
}
|
||||
&.dim {
|
||||
background: lighten($green, 20%) !important;
|
||||
}
|
||||
}
|
||||
&.red {
|
||||
text-align: right !important;
|
||||
|
@ -263,6 +266,9 @@
|
|||
left: 0.2rem;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 4px 0px 0 rgba(0, 0, 0, 0.04), 0 4px 9px rgba(0, 0, 0, 0.13), 0 3px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
&.dim {
|
||||
background: lighten($red, 10%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,9 @@ input:not([role="combobox"]) {
|
|||
&.day {
|
||||
display: none;
|
||||
}
|
||||
&.dim {
|
||||
background: darken($white, 2%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.day-selector-wrapper {
|
||||
|
@ -47,6 +50,17 @@ select {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.filter-search {
|
||||
&.dim {
|
||||
Button {
|
||||
background: darken($white, 2%) !important;
|
||||
&:hover {
|
||||
background: darken($white, 2%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-search-item-none::after {
|
||||
content: "*";
|
||||
}
|
||||
|
|
|
@ -45,6 +45,9 @@
|
|||
background: $black;
|
||||
.markdown p {
|
||||
font-weight: 600;
|
||||
code {
|
||||
background: $dark_gray;
|
||||
}
|
||||
}
|
||||
.status-ticker-created-at {
|
||||
font-size: 1.2rem;
|
||||
|
|
|
@ -12,7 +12,8 @@ const mockDevice = {
|
|||
togglePin: jest.fn(() => { return Promise.resolve(); }),
|
||||
home: jest.fn(() => { return Promise.resolve(); }),
|
||||
sync: jest.fn(() => { return Promise.resolve(); }),
|
||||
readStatus: jest.fn(() => Promise.resolve())
|
||||
readStatus: jest.fn(() => Promise.resolve()),
|
||||
updateConfig: jest.fn(() => Promise.resolve())
|
||||
};
|
||||
|
||||
jest.mock("../../device", () => ({
|
||||
|
@ -37,13 +38,14 @@ jest.mock("axios", () => ({
|
|||
}));
|
||||
|
||||
import * as actions from "../actions";
|
||||
import { fakeSequence } from "../../__test_support__/fake_state/resources";
|
||||
import { fakeSequence, fakeFbosConfig } from "../../__test_support__/fake_state/resources";
|
||||
import { fakeState } from "../../__test_support__/fake_state";
|
||||
import { changeStepSize, resetNetwork, resetConnectionInfo } from "../actions";
|
||||
import { Actions } from "../../constants";
|
||||
import { fakeDevice } from "../../__test_support__/resource_index_builder";
|
||||
import { fakeDevice, buildResourceIndex } from "../../__test_support__/resource_index_builder";
|
||||
import { API } from "../../api/index";
|
||||
import axios from "axios";
|
||||
import { SpecialStatus } from "../../resources/tagged_resources";
|
||||
|
||||
describe("checkControllerUpdates()", function () {
|
||||
beforeEach(function () {
|
||||
|
@ -302,3 +304,36 @@ describe("fetchReleases()", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateConfig()", () => {
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("updates config: configUpdate", () => {
|
||||
const dispatch = jest.fn();
|
||||
const state = fakeState();
|
||||
state.resources.index = buildResourceIndex([fakeFbosConfig()]).index;
|
||||
actions.updateConfig({ auto_sync: true })(dispatch, () => state);
|
||||
expect(mockDevice.updateConfig).toHaveBeenCalledWith({ auto_sync: true });
|
||||
expect(dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates config: FbosConfig", () => {
|
||||
const dispatch = jest.fn(() => Promise.resolve());
|
||||
const state = fakeState();
|
||||
const fakeFBOSConfig = fakeFbosConfig();
|
||||
fakeFBOSConfig.body.api_migrated = true;
|
||||
state.resources.index = buildResourceIndex([fakeFBOSConfig]).index;
|
||||
actions.updateConfig({ auto_sync: true })(dispatch, () => state);
|
||||
expect(mockDevice.updateConfig).not.toHaveBeenCalled();
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
payload: {
|
||||
specialStatus: SpecialStatus.DIRTY,
|
||||
update: { auto_sync: true },
|
||||
uuid: expect.stringContaining("FbosConfig")
|
||||
},
|
||||
type: Actions.EDIT_RESOURCE
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,8 @@ describe("<Devices/>", () => {
|
|||
deviceAccount: fakeDevice(),
|
||||
images: [],
|
||||
dispatch: jest.fn(),
|
||||
resources: buildResourceIndex(FAKE_RESOURCES).index
|
||||
resources: buildResourceIndex(FAKE_RESOURCES).index,
|
||||
sourceFbosConfig: jest.fn()
|
||||
});
|
||||
|
||||
it("renders relevant panels", () => {
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import { fakeFbosConfig } from "../../__test_support__/fake_state/resources";
|
||||
|
||||
let mockFbosConfig: TaggedFbosConfig | undefined = fakeFbosConfig();
|
||||
jest.mock("../../resources/selectors", () => ({
|
||||
getDeviceAccountSettings: jest.fn(),
|
||||
assertUuid: jest.fn(),
|
||||
getFbosConfig: () => mockFbosConfig,
|
||||
selectAllImages: jest.fn()
|
||||
}));
|
||||
|
||||
import { mapStateToProps } from "../state_to_props";
|
||||
import { fakeState } from "../../__test_support__/fake_state";
|
||||
import { TaggedFbosConfig } from "../../resources/tagged_resources";
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("API source of FBOS settings", () => {
|
||||
const fakeApiConfig = fakeFbosConfig();
|
||||
fakeApiConfig.body.auto_sync = true;
|
||||
fakeApiConfig.body.api_migrated = true;
|
||||
mockFbosConfig = fakeApiConfig;
|
||||
const props = mapStateToProps(fakeState());
|
||||
expect(props.sourceFbosConfig("auto_sync")).toEqual({
|
||||
value: true, consistent: false
|
||||
});
|
||||
});
|
||||
|
||||
it("bot source of FBOS settings", () => {
|
||||
const state = fakeState();
|
||||
state.bot.hardware.configuration.auto_sync = false;
|
||||
mockFbosConfig = undefined;
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.sourceFbosConfig("auto_sync")).toEqual({
|
||||
value: false, consistent: true
|
||||
});
|
||||
});
|
||||
|
||||
it("bot source of FBOS settings: ignore API defaults", () => {
|
||||
const state = fakeState();
|
||||
state.bot.hardware.configuration.auto_sync = false;
|
||||
const fakeApiConfig = fakeFbosConfig();
|
||||
fakeApiConfig.body.auto_sync = true;
|
||||
fakeApiConfig.body.api_migrated = false;
|
||||
mockFbosConfig = fakeApiConfig;
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.sourceFbosConfig("auto_sync")).toEqual({
|
||||
value: false, consistent: true
|
||||
});
|
||||
});
|
||||
});
|
|
@ -12,13 +12,14 @@ import { Sequence } from "../sequences/interfaces";
|
|||
import { ControlPanelState } from "../devices/interfaces";
|
||||
import { API } from "../api/index";
|
||||
import { User } from "../auth/interfaces";
|
||||
import { getDeviceAccountSettings } from "../resources/selectors";
|
||||
import { getDeviceAccountSettings, getFbosConfig } from "../resources/selectors";
|
||||
import { TaggedDevice } from "../resources/tagged_resources";
|
||||
import { versionOK } from "./reducer";
|
||||
import { HttpData, oneOf } from "../util";
|
||||
import { Actions, Content } from "../constants";
|
||||
import { mcuParamValidator } from "./update_interceptor";
|
||||
import { pingAPI } from "../connectivity/ping_mqtt";
|
||||
import { edit, save as apiSave } from "../api/crud";
|
||||
|
||||
const ON = 1, OFF = 0;
|
||||
export type ConfigKey = keyof McuParams;
|
||||
|
@ -277,11 +278,27 @@ export function updateMCU(key: ConfigKey, val: string) {
|
|||
|
||||
export function updateConfig(config: Configuration) {
|
||||
const noun = "Update Config";
|
||||
return function (dispatch: Function) {
|
||||
getDevice()
|
||||
.updateConfig(config)
|
||||
.then(() => updateOK(dispatch, noun))
|
||||
.catch(() => updateNO(dispatch, noun));
|
||||
let useAPI = false;
|
||||
return function (dispatch: Function, getState?: () => Everything) {
|
||||
if (getState) {
|
||||
const fbosConfig = getFbosConfig(getState().resources.index);
|
||||
if (fbosConfig) {
|
||||
useAPI = fbosConfig.body.api_migrated;
|
||||
if (useAPI) {
|
||||
const [key, value] = Object.entries(config)[0];
|
||||
dispatch(edit(fbosConfig, { [key]: value }));
|
||||
dispatch(apiSave(fbosConfig.uuid))
|
||||
.then(() => updateOK(_.noop, noun))
|
||||
.catch(() => updateNO(_.noop, noun));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!useAPI) {
|
||||
getDevice()
|
||||
.updateConfig(config)
|
||||
.then(() => updateOK(_.noop, noun))
|
||||
.catch(() => updateNO(_.noop, noun));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
const mockDevice = {
|
||||
updateConfig: jest.fn(() => { return Promise.resolve(); }),
|
||||
};
|
||||
jest.mock("../../../device", () => ({
|
||||
getDevice: () => (mockDevice)
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { BotConfigInputBox, BotConfigInputBoxProps } from "../bot_config_input_box";
|
||||
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||
|
||||
describe("<BotConfigInputBox />", () => {
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const fakeProps = (): BotConfigInputBoxProps => {
|
||||
return {
|
||||
setting: "network_not_found_timer",
|
||||
dispatch: jest.fn(x => x()),
|
||||
sourceFbosConfig: (x) => {
|
||||
return { value: bot.hardware.configuration[x], consistent: true };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
it("renders value: number", () => {
|
||||
bot.hardware.configuration.network_not_found_timer = 10;
|
||||
const wrapper = shallow(<BotConfigInputBox {...fakeProps() } />);
|
||||
const inputBoxProps = wrapper.find("BlurableInput").props();
|
||||
expect(inputBoxProps.value).toEqual("10");
|
||||
expect(inputBoxProps.className).toEqual("");
|
||||
});
|
||||
|
||||
it("doesn't render value: string", () => {
|
||||
// tslint:disable-next-line:no-any
|
||||
bot.hardware.configuration.network_not_found_timer = "bad" as any;
|
||||
const wrapper = shallow(<BotConfigInputBox {...fakeProps() } />);
|
||||
expect(wrapper.find("BlurableInput").props().value).toEqual("");
|
||||
});
|
||||
|
||||
it("updates value", () => {
|
||||
bot.hardware.configuration.network_not_found_timer = 0;
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<BotConfigInputBox {...p} />);
|
||||
wrapper.find("BlurableInput")
|
||||
.simulate("commit", { currentTarget: { value: "10" } });
|
||||
expect(mockDevice.updateConfig)
|
||||
.toHaveBeenCalledWith({ network_not_found_timer: 10 });
|
||||
});
|
||||
|
||||
it("doesn't update value: same value", () => {
|
||||
bot.hardware.configuration.network_not_found_timer = 10;
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<BotConfigInputBox {...p} />);
|
||||
wrapper.find("BlurableInput")
|
||||
.simulate("commit", { currentTarget: { value: "10" } });
|
||||
expect(mockDevice.updateConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("doesn't update value: NaN", () => {
|
||||
bot.hardware.configuration.network_not_found_timer = 10;
|
||||
const p = fakeProps();
|
||||
const wrapper = shallow(<BotConfigInputBox {...p} />);
|
||||
wrapper.find("BlurableInput")
|
||||
.simulate("commit", { currentTarget: { value: "x" } });
|
||||
expect(mockDevice.updateConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("not consistent", () => {
|
||||
const p = fakeProps();
|
||||
p.sourceFbosConfig = x => { return { value: 10, consistent: false }; };
|
||||
const wrapper = shallow(<BotConfigInputBox {...p} />);
|
||||
expect(wrapper.find("BlurableInput").props().className).toEqual("dim");
|
||||
});
|
||||
});
|
|
@ -7,23 +7,29 @@ jest.mock("axios", () => ({
|
|||
|
||||
import * as React from "react";
|
||||
import { FarmbotOsSettings } from "../farmbot_os_settings";
|
||||
import { mount } from "enzyme";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||
import { fakeResource } from "../../../__test_support__/fake_resource";
|
||||
import { FbosDetails } from "../fbos_settings/farmbot_os_row";
|
||||
import { FarmbotOsProps } from "../../interfaces";
|
||||
import axios from "axios";
|
||||
import { FbosDetailsProps } from "../fbos_settings/interfaces";
|
||||
import { Actions } from "../../../constants";
|
||||
import { SpecialStatus } from "../../../resources/tagged_resources";
|
||||
|
||||
describe("<FarmbotOsSettings/>", () => {
|
||||
function fakeProps(): FarmbotOsProps {
|
||||
const fakeProps = (): FarmbotOsProps => {
|
||||
return {
|
||||
account: fakeResource("Device", { id: 0, name: "", tz_offset_hrs: 0 }),
|
||||
dispatch: jest.fn(),
|
||||
bot: bot,
|
||||
bot,
|
||||
botToMqttLastSeen: "",
|
||||
botToMqttStatus: "up"
|
||||
botToMqttStatus: "up",
|
||||
sourceFbosConfig: (x) => {
|
||||
return { value: bot.hardware.configuration[x], consistent: true };
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
it("renders settings", () => {
|
||||
const osSettings = mount(<FarmbotOsSettings {...fakeProps() } />);
|
||||
|
@ -50,11 +56,37 @@ describe("<FarmbotOsSettings/>", () => {
|
|||
expect(osSettings.state().osReleaseNotes)
|
||||
.toEqual("Could not get release notes.");
|
||||
});
|
||||
|
||||
it("changes bot name", () => {
|
||||
const p = fakeProps();
|
||||
const osSettings = shallow(<FarmbotOsSettings {...p} />);
|
||||
osSettings.find("input")
|
||||
.simulate("change", { currentTarget: { value: "new bot name" } });
|
||||
expect(p.dispatch).toHaveBeenCalledWith({
|
||||
payload: {
|
||||
specialStatus: SpecialStatus.DIRTY,
|
||||
update: { name: "new bot name" },
|
||||
uuid: expect.stringContaining("Device")
|
||||
},
|
||||
type: Actions.EDIT_RESOURCE
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("<FbosDetails />", () => {
|
||||
const fakeProps = (): FbosDetailsProps => {
|
||||
return {
|
||||
dispatch: jest.fn(),
|
||||
bot: bot,
|
||||
sourceFbosConfig: (x) => {
|
||||
return { value: bot.hardware.configuration[x], consistent: true };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<FbosDetails {...bot} />);
|
||||
const wrapper = mount(<FbosDetails {...fakeProps() } />);
|
||||
["Environment: ---",
|
||||
"Commit: ---",
|
||||
"Target: ---",
|
||||
|
|
|
@ -1,32 +1,30 @@
|
|||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import { HardwareSettings } from "../hardware_settings";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import { ControlPanelState } from "../../interfaces";
|
||||
import { HardwareSettingsProps } from "../../interfaces";
|
||||
import { Actions } from "../../../constants";
|
||||
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||
import { panelState } from "../../../__test_support__/control_panel_state";
|
||||
|
||||
describe("<HardwareSettings />", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
function panelState(): ControlPanelState {
|
||||
const fakeProps = (): HardwareSettingsProps => {
|
||||
return {
|
||||
homing_and_calibration: false,
|
||||
motors: false,
|
||||
encoders_and_endstops: false,
|
||||
danger_zone: false,
|
||||
power_and_reset: false,
|
||||
pin_guard: false
|
||||
bot,
|
||||
controlPanelState: panelState(),
|
||||
botToMqttStatus: "up",
|
||||
dispatch: jest.fn(),
|
||||
sourceFbosConfig: (x) => {
|
||||
return { value: bot.hardware.configuration[x], consistent: true };
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<HardwareSettings
|
||||
controlPanelState={panelState()}
|
||||
dispatch={jest.fn()}
|
||||
bot={fakeState().bot}
|
||||
botToMqttStatus={"up"} />);
|
||||
const wrapper = mount(<HardwareSettings {...fakeProps() } />);
|
||||
["expand all", "x axis", "motors"].map(string =>
|
||||
expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
});
|
||||
|
@ -37,16 +35,12 @@ describe("<HardwareSettings />", () => {
|
|||
buttonText: string,
|
||||
type: string,
|
||||
payload: boolean | string) {
|
||||
const dispatch = jest.fn();
|
||||
const wrapper = mount(<HardwareSettings
|
||||
controlPanelState={panelState()}
|
||||
dispatch={dispatch}
|
||||
bot={fakeState().bot}
|
||||
botToMqttStatus={"up"} />);
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<HardwareSettings {...p} />);
|
||||
const button = wrapper.find(buttonElement).at(buttonIndex);
|
||||
expect(button.text().toLowerCase()).toContain(buttonText);
|
||||
button.simulate("click");
|
||||
expect(dispatch).toHaveBeenCalledWith({ payload, type });
|
||||
expect(p.dispatch).toHaveBeenCalledWith({ payload, type });
|
||||
}
|
||||
|
||||
it("expands all", () => {
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { sourceFbosConfigValue } from "../source_fbos_config_value";
|
||||
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||
import { fakeFbosConfig } from "../../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("sourceFbosConfigValue()", () => {
|
||||
it("returns api value", () => {
|
||||
const fakeConfig = fakeFbosConfig().body;
|
||||
fakeConfig.auto_sync = false;
|
||||
bot.hardware.configuration.auto_sync = true;
|
||||
const source = sourceFbosConfigValue(fakeConfig, bot.hardware.configuration);
|
||||
expect(source("auto_sync")).toEqual({ value: false, consistent: false });
|
||||
});
|
||||
|
||||
it("returns bot value", () => {
|
||||
bot.hardware.configuration.auto_sync = true;
|
||||
const source = sourceFbosConfigValue(undefined, bot.hardware.configuration);
|
||||
expect(source("auto_sync")).toEqual({ value: true, consistent: true });
|
||||
});
|
||||
|
||||
it("returns api value: consistent with bot", () => {
|
||||
const fakeConfig = fakeFbosConfig().body;
|
||||
fakeConfig.auto_sync = true;
|
||||
bot.hardware.configuration.auto_sync = true;
|
||||
const source = sourceFbosConfigValue(fakeConfig, bot.hardware.configuration);
|
||||
expect(source("auto_sync")).toEqual({ value: true, consistent: true });
|
||||
});
|
||||
});
|
|
@ -1,25 +1,28 @@
|
|||
import * as React from "react";
|
||||
import * as _ from "lodash";
|
||||
import { BlurableInput } from "../../ui/index";
|
||||
import { StepsPerMMBoxProps } from "../interfaces";
|
||||
import { SourceFbosConfig } from "../interfaces";
|
||||
import { ConfigurationName } from "farmbot/dist";
|
||||
import { updateConfig } from "../actions";
|
||||
|
||||
/**
|
||||
* Steps per mm is not an actual Arduino command.
|
||||
* We needed to fake it on the UI layer to give the appearance that the settings
|
||||
* all coming from the same place.
|
||||
*/
|
||||
export class BotConfigInputBox extends React.Component<StepsPerMMBoxProps, {}> {
|
||||
export interface BotConfigInputBoxProps {
|
||||
setting: ConfigurationName;
|
||||
dispatch: Function;
|
||||
disabled?: boolean;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
get setting() { return this.props.setting; }
|
||||
export class BotConfigInputBox
|
||||
extends React.Component<BotConfigInputBoxProps, {}> {
|
||||
|
||||
get config() { return this.props.bot.hardware.configuration; }
|
||||
get config() {
|
||||
return this.props.sourceFbosConfig(this.props.setting);
|
||||
}
|
||||
|
||||
change = (key: ConfigurationName, dispatch: Function) => {
|
||||
return (event: React.FormEvent<HTMLInputElement>) => {
|
||||
const next = parseInt(event.currentTarget.value, 10);
|
||||
const current = this.config[this.setting];
|
||||
const current = this.config.value;
|
||||
if (!_.isNaN(next) && (next !== current)) {
|
||||
dispatch(updateConfig({ [key]: next }));
|
||||
}
|
||||
|
@ -27,13 +30,15 @@ export class BotConfigInputBox extends React.Component<StepsPerMMBoxProps, {}> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const hmm = this.config[this.setting];
|
||||
const value = (_.isNumber(hmm) || _.isBoolean(hmm)) ? hmm.toString() : "";
|
||||
const current = this.config.value;
|
||||
const boxValue = (_.isNumber(current) || _.isBoolean(current))
|
||||
? current.toString() : "";
|
||||
|
||||
return <BlurableInput
|
||||
type="number"
|
||||
className={!this.config.consistent ? "dim" : ""}
|
||||
onCommit={this.change(this.props.setting, this.props.dispatch)}
|
||||
value={value}
|
||||
value={boxValue}
|
||||
disabled={this.props.disabled} />;
|
||||
}
|
||||
}
|
|
@ -91,25 +91,11 @@ export class FarmbotOsSettings
|
|||
device={this.props.account} />;
|
||||
}
|
||||
|
||||
// TODO: Delete this function on 1 Jan 2018. This is a backwards compatibility
|
||||
// fix because old FBOS breaks when `auto_sync` is toggled. - RC
|
||||
maybeShowAutoSync = () => {
|
||||
const { auto_sync } = this.props.bot.hardware.configuration;
|
||||
const isDevMode = location.host.includes("localhost"); // Enable in dev.
|
||||
// Old FBOS => no auto_sync option => breaks when toggled.
|
||||
const properFbosVersion = !isUndefined(auto_sync);
|
||||
|
||||
if (isDevMode || properFbosVersion) {
|
||||
return <AutoSyncRow currentValue={!!auto_sync} />;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account } = this.props;
|
||||
const { hardware } = this.props.bot;
|
||||
const { firmware_version } = hardware.informational_settings;
|
||||
const { controller_version } = hardware.informational_settings;
|
||||
|
||||
const { account, sourceFbosConfig } = this.props;
|
||||
const {
|
||||
firmware_version, controller_version, sync_status
|
||||
} = this.props.bot.hardware.informational_settings;
|
||||
return <Widget className="device-widget">
|
||||
<form onSubmit={(e) => e.preventDefault()}>
|
||||
<WidgetHeader title="Device" helpText={ToolTips.OS_SETTINGS}>
|
||||
|
@ -149,20 +135,32 @@ export class FarmbotOsSettings
|
|||
</Row>
|
||||
<this.lastSeen />
|
||||
<MustBeOnline
|
||||
syncStatus={this.props.bot.hardware.informational_settings.sync_status}
|
||||
syncStatus={sync_status}
|
||||
networkState={this.props.botToMqttStatus}
|
||||
lockOpen={process.env.NODE_ENV !== "production"}>
|
||||
<FarmbotOsRow
|
||||
bot={this.props.bot}
|
||||
controller_version={controller_version}
|
||||
osReleaseNotes={this.state.osReleaseNotes} />
|
||||
<AutoUpdateRow bot={this.props.bot} />
|
||||
{this.maybeShowAutoSync()}
|
||||
<CameraSelection env={hardware.user_env} />
|
||||
<BoardType firmwareVersion={firmware_version} />
|
||||
osReleaseNotes={this.state.osReleaseNotes}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
<AutoUpdateRow
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
{(location.host.includes("localhost")
|
||||
|| !isUndefined(sourceFbosConfig("auto_sync").value)) &&
|
||||
<AutoSyncRow
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />}
|
||||
<CameraSelection env={this.props.bot.hardware.user_env} />
|
||||
<BoardType
|
||||
firmwareVersion={firmware_version}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
<PowerAndReset
|
||||
bot={this.props.bot}
|
||||
dispatch={this.props.dispatch} />
|
||||
controlPanelState={this.props.bot.controlPanelState}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
</MustBeOnline>
|
||||
</WidgetBody>
|
||||
</form>
|
||||
|
|
|
@ -10,16 +10,28 @@ import * as React from "react";
|
|||
import { AutoSyncRow } from "../auto_sync_row";
|
||||
import { mount } from "enzyme";
|
||||
import { Content } from "../../../../constants";
|
||||
import { AutoSyncRowProps } from "../interfaces";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
|
||||
describe("<AutoSyncRow/>", () => {
|
||||
const fakeProps = (): AutoSyncRowProps => {
|
||||
return {
|
||||
dispatch: jest.fn(x => x()),
|
||||
sourceFbosConfig: (x) => {
|
||||
return { value: bot.hardware.configuration[x], consistent: true };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<AutoSyncRow currentValue={true} />);
|
||||
const wrapper = mount(<AutoSyncRow {...fakeProps() } />);
|
||||
["AUTO SYNC", Content.AUTO_SYNC]
|
||||
.map(string => expect(wrapper.text()).toContain(string));
|
||||
});
|
||||
|
||||
it("toggles", () => {
|
||||
const wrapper = mount(<AutoSyncRow currentValue={true} />);
|
||||
bot.hardware.configuration.auto_sync = true;
|
||||
const wrapper = mount(<AutoSyncRow {...fakeProps() } />);
|
||||
wrapper.find("button").simulate("click");
|
||||
expect(mockDevice.updateConfig)
|
||||
.toHaveBeenCalledWith({ auto_sync: false });
|
||||
|
|
|
@ -9,20 +9,30 @@ import * as React from "react";
|
|||
import { AutoUpdateRow } from "../auto_update_row";
|
||||
import { mount } from "enzyme";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
import { AutoUpdateRowProps } from "../interfaces";
|
||||
|
||||
describe("<AutoUpdateRow/>", () => {
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const fakeProps = (): AutoUpdateRowProps => {
|
||||
return {
|
||||
dispatch: jest.fn(x => x()),
|
||||
sourceFbosConfig: (x) => {
|
||||
return { value: bot.hardware.configuration[x], consistent: true };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<AutoUpdateRow bot={bot} />);
|
||||
const wrapper = mount(<AutoUpdateRow {...fakeProps() } />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("auto update");
|
||||
});
|
||||
|
||||
it("toggles auto-update on", () => {
|
||||
bot.hardware.configuration.os_auto_update = 0;
|
||||
const wrapper = mount(<AutoUpdateRow bot={bot} />);
|
||||
const wrapper = mount(<AutoUpdateRow {...fakeProps() } />);
|
||||
wrapper.find("button").first().simulate("click");
|
||||
expect(mockDevice.updateConfig)
|
||||
.toHaveBeenCalledWith({ os_auto_update: 1 });
|
||||
|
@ -30,7 +40,7 @@ describe("<AutoUpdateRow/>", () => {
|
|||
|
||||
it("toggles auto-update off", () => {
|
||||
bot.hardware.configuration.os_auto_update = 1;
|
||||
const wrapper = mount(<AutoUpdateRow bot={bot} />);
|
||||
const wrapper = mount(<AutoUpdateRow {...fakeProps() } />);
|
||||
wrapper.find("button").first().simulate("click");
|
||||
expect(mockDevice.updateConfig)
|
||||
.toHaveBeenCalledWith({ os_auto_update: 0 });
|
||||
|
|
|
@ -15,41 +15,60 @@ jest.mock("farmbot-toastr", () => ({
|
|||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { BoardType } from "../board_type";
|
||||
import { BoardTypeProps } from "../interfaces";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
|
||||
describe("<BoardType/>", () => {
|
||||
const fakeProps = (): BoardTypeProps => {
|
||||
return {
|
||||
firmwareVersion: "",
|
||||
dispatch: jest.fn(),
|
||||
sourceFbosConfig: (x) => {
|
||||
return { value: bot.hardware.configuration[x], consistent: true };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
it("Farmduino", () => {
|
||||
const wrapper = mount(<BoardType
|
||||
firmwareVersion={"5.0.3.F"} />);
|
||||
const p = fakeProps();
|
||||
p.firmwareVersion = "5.0.3.F";
|
||||
const wrapper = mount(<BoardType {...p} />);
|
||||
expect(wrapper.text()).toContain("Farmduino");
|
||||
});
|
||||
|
||||
it("Arduino/RAMPS", () => {
|
||||
const wrapper = mount(<BoardType
|
||||
firmwareVersion={"5.0.3.R"} />);
|
||||
const p = fakeProps();
|
||||
p.firmwareVersion = "5.0.3.R";
|
||||
const wrapper = mount(<BoardType {...p} />);
|
||||
expect(wrapper.text()).toContain("Arduino/RAMPS");
|
||||
});
|
||||
|
||||
it("Other", () => {
|
||||
const wrapper = mount(<BoardType
|
||||
firmwareVersion={"4.0.2"} />);
|
||||
const p = fakeProps();
|
||||
p.firmwareVersion = "4.0.2";
|
||||
const wrapper = mount(<BoardType {...p} />);
|
||||
expect(wrapper.text()).toContain("Arduino/RAMPS");
|
||||
});
|
||||
|
||||
it("Undefined", () => {
|
||||
const wrapper = mount(<BoardType
|
||||
firmwareVersion={undefined} />);
|
||||
const p = fakeProps();
|
||||
p.firmwareVersion = undefined;
|
||||
const wrapper = mount(<BoardType {...p} />);
|
||||
expect(wrapper.text()).toContain("None");
|
||||
});
|
||||
|
||||
it("Disconnected", () => {
|
||||
const wrapper = mount(<BoardType
|
||||
firmwareVersion={"Arduino Disconnected!"} />);
|
||||
const p = fakeProps();
|
||||
p.firmwareVersion = "Arduino Disconnected!";
|
||||
const wrapper = mount(<BoardType {...p} />);
|
||||
expect(wrapper.text()).toContain("None");
|
||||
});
|
||||
|
||||
it("calls updateConfig", () => {
|
||||
const wrapper = shallow(<BoardType
|
||||
firmwareVersion={"Arduino Disconnected!"} />);
|
||||
const p = fakeProps();
|
||||
p.firmwareVersion = "Arduino Disconnected!";
|
||||
p.dispatch = jest.fn(x => Promise.resolve(x()));
|
||||
const wrapper = shallow(<BoardType {...p} />);
|
||||
wrapper.find("FBSelect").simulate("change",
|
||||
{ label: "firmware_hardware", value: "farmduino" });
|
||||
expect(mockDevice.updateConfig)
|
||||
|
|
|
@ -9,12 +9,23 @@ import * as React from "react";
|
|||
import { FbosDetails } from "../farmbot_os_row";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
import { FbosDetailsProps } from "../interfaces";
|
||||
|
||||
describe("<FbosDetails/>", () => {
|
||||
beforeEach(function () {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const fakeProps = (): FbosDetailsProps => {
|
||||
return {
|
||||
bot,
|
||||
dispatch: jest.fn(x => x()),
|
||||
sourceFbosConfig: (x) => {
|
||||
return { value: bot.hardware.configuration[x], consistent: true };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
bot.hardware.informational_settings.env = "fakeEnv";
|
||||
bot.hardware.informational_settings.commit = "fakeCommit";
|
||||
|
@ -22,7 +33,7 @@ describe("<FbosDetails/>", () => {
|
|||
bot.hardware.informational_settings.node_name = "fakeName";
|
||||
bot.hardware.informational_settings.firmware_version = "fakeFirmware";
|
||||
bot.hardware.informational_settings.firmware_commit = "fakeFwCommit";
|
||||
const wrapper = shallow(<FbosDetails {...bot} />);
|
||||
const wrapper = shallow(<FbosDetails {...fakeProps() } />);
|
||||
["Environment", "fakeEnv",
|
||||
"Commit", "fakeComm",
|
||||
"Target", "fakeTarget",
|
||||
|
@ -35,15 +46,16 @@ describe("<FbosDetails/>", () => {
|
|||
});
|
||||
|
||||
it("simplifies node name", () => {
|
||||
bot.hardware.informational_settings.node_name = "name@nodeName";
|
||||
const wrapper = shallow(<FbosDetails {...bot} />);
|
||||
const p = fakeProps();
|
||||
p.bot.hardware.informational_settings.node_name = "name@nodeName";
|
||||
const wrapper = shallow(<FbosDetails {...p} />);
|
||||
expect(wrapper.text()).toContain("nodeName");
|
||||
expect(wrapper.text()).not.toContain("name@");
|
||||
});
|
||||
|
||||
it("toggles os beta opt in setting on", () => {
|
||||
bot.hardware.configuration.beta_opt_in = false;
|
||||
const wrapper = mount(<FbosDetails {...bot} />);
|
||||
const wrapper = mount(<FbosDetails {...fakeProps() } />);
|
||||
wrapper.find("button").simulate("click");
|
||||
expect(mockDevice.updateConfig).not.toHaveBeenCalled();
|
||||
window.confirm = () => true;
|
||||
|
@ -54,7 +66,7 @@ describe("<FbosDetails/>", () => {
|
|||
|
||||
it("toggles os beta opt in setting off", () => {
|
||||
bot.hardware.configuration.beta_opt_in = true;
|
||||
const wrapper = mount(<FbosDetails {...bot} />);
|
||||
const wrapper = mount(<FbosDetails {...fakeProps() } />);
|
||||
window.confirm = () => false;
|
||||
wrapper.find("button").simulate("click");
|
||||
expect(mockDevice.updateConfig)
|
||||
|
|
|
@ -8,12 +8,25 @@ jest.mock("../../../../device", () => ({
|
|||
import * as React from "react";
|
||||
import { PowerAndReset } from "../power_and_reset";
|
||||
import { mount } from "enzyme";
|
||||
import { PowerAndResetProps } from "../interfaces";
|
||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
import { panelState } from "../../../../__test_support__/control_panel_state";
|
||||
|
||||
describe("<PowerAndReset/>", () => {
|
||||
const fakeProps = (): PowerAndResetProps => {
|
||||
return {
|
||||
controlPanelState: panelState(),
|
||||
dispatch: jest.fn(x => x()),
|
||||
sourceFbosConfig: (x) => {
|
||||
return { value: bot.hardware.configuration[x], consistent: true };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
it("open", () => {
|
||||
bot.controlPanelState.power_and_reset = true;
|
||||
const wrapper = mount(<PowerAndReset bot={bot} dispatch={jest.fn()} />);
|
||||
const p = fakeProps();
|
||||
p.controlPanelState.power_and_reset = true;
|
||||
const wrapper = mount(<PowerAndReset {...p} />);
|
||||
["Power and Reset", "Restart", "Shutdown", "Factory Reset",
|
||||
"Automatic Factory Reset", "Connection Attempt Period"]
|
||||
.map(string => expect(wrapper.text().toLowerCase())
|
||||
|
@ -21,8 +34,9 @@ describe("<PowerAndReset/>", () => {
|
|||
});
|
||||
|
||||
it("closed", () => {
|
||||
bot.controlPanelState.power_and_reset = false;
|
||||
const wrapper = mount(<PowerAndReset bot={bot} dispatch={jest.fn()} />);
|
||||
const p = fakeProps();
|
||||
p.controlPanelState.power_and_reset = false;
|
||||
const wrapper = mount(<PowerAndReset {...p} />);
|
||||
expect(wrapper.text().toLowerCase())
|
||||
.toContain("Power and Reset".toLowerCase());
|
||||
expect(wrapper.text().toLowerCase())
|
||||
|
@ -30,18 +44,20 @@ describe("<PowerAndReset/>", () => {
|
|||
});
|
||||
|
||||
it("timer input disabled", () => {
|
||||
bot.controlPanelState.power_and_reset = true;
|
||||
bot.hardware.configuration.disable_factory_reset = true;
|
||||
const wrapper = mount(<PowerAndReset bot={bot} dispatch={jest.fn()} />);
|
||||
const p = fakeProps();
|
||||
p.controlPanelState.power_and_reset = true;
|
||||
const wrapper = mount(<PowerAndReset {...p} />);
|
||||
expect(wrapper.find("input").last().props().disabled).toBeTruthy();
|
||||
expect(wrapper.find("label").last().props().style)
|
||||
.toEqual({ color: "grey" });
|
||||
});
|
||||
|
||||
it("toggles auto reset", () => {
|
||||
bot.controlPanelState.power_and_reset = true;
|
||||
bot.hardware.configuration.disable_factory_reset = false;
|
||||
const wrapper = mount(<PowerAndReset bot={bot} dispatch={jest.fn()} />);
|
||||
const p = fakeProps();
|
||||
p.controlPanelState.power_and_reset = true;
|
||||
const wrapper = mount(<PowerAndReset {...p} />);
|
||||
wrapper.find("button").at(3).simulate("click");
|
||||
expect(mockDevice.updateConfig)
|
||||
.toHaveBeenCalledWith({ disable_factory_reset: true });
|
||||
|
|
|
@ -4,13 +4,11 @@ import { t } from "i18next";
|
|||
import { ToggleButton } from "../../../controls/toggle_button";
|
||||
import { Content } from "../../../constants";
|
||||
import { updateConfig } from "../../actions";
|
||||
import { noop } from "lodash";
|
||||
import { ColWidth } from "../farmbot_os_settings";
|
||||
|
||||
interface AutoSyncRowProps { currentValue: boolean; }
|
||||
import { AutoSyncRowProps } from "./interfaces";
|
||||
|
||||
export function AutoSyncRow(props: AutoSyncRowProps) {
|
||||
const auto_sync = !props.currentValue;
|
||||
const autoSync = props.sourceFbosConfig("auto_sync");
|
||||
return <Row>
|
||||
<Col xs={ColWidth.label}>
|
||||
<label>
|
||||
|
@ -23,9 +21,11 @@ export function AutoSyncRow(props: AutoSyncRowProps) {
|
|||
</p>
|
||||
</Col>
|
||||
<Col xs={ColWidth.button}>
|
||||
<ToggleButton toggleValue={props.currentValue}
|
||||
<ToggleButton
|
||||
toggleValue={autoSync.value}
|
||||
dim={!autoSync.consistent}
|
||||
toggleAction={() => {
|
||||
updateConfig({ auto_sync })(noop);
|
||||
props.dispatch(updateConfig({ auto_sync: !autoSync.value }));
|
||||
}} />
|
||||
</Col>
|
||||
</Row>;
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col } from "../../../ui/index";
|
||||
import { t } from "i18next";
|
||||
import { BotState } from "../../interfaces";
|
||||
import { ColWidth } from "../farmbot_os_settings";
|
||||
import { ToggleButton } from "../../../controls/toggle_button";
|
||||
import { updateConfig } from "../../actions";
|
||||
import { noop } from "lodash";
|
||||
import { Content } from "../../../constants";
|
||||
|
||||
interface AutoUpdateRowProps {
|
||||
bot: BotState;
|
||||
}
|
||||
import { AutoUpdateRowProps } from "./interfaces";
|
||||
|
||||
export function AutoUpdateRow(props: AutoUpdateRowProps) {
|
||||
const { os_auto_update } = props.bot.hardware.configuration;
|
||||
const osAutoUpdate = props.sourceFbosConfig("os_auto_update");
|
||||
return <Row>
|
||||
<Col xs={ColWidth.label}>
|
||||
<label>
|
||||
|
@ -26,10 +21,11 @@ export function AutoUpdateRow(props: AutoUpdateRowProps) {
|
|||
</p>
|
||||
</Col>
|
||||
<Col xs={ColWidth.button}>
|
||||
<ToggleButton toggleValue={os_auto_update}
|
||||
<ToggleButton toggleValue={osAutoUpdate.value}
|
||||
dim={!osAutoUpdate.consistent}
|
||||
toggleAction={() => {
|
||||
const newOsAutoUpdateNum = !os_auto_update ? 1 : 0;
|
||||
updateConfig({ os_auto_update: newOsAutoUpdateNum })(noop);
|
||||
const newOsAutoUpdateNum = !osAutoUpdate.value ? 1 : 0;
|
||||
props.dispatch(updateConfig({ os_auto_update: newOsAutoUpdateNum }));
|
||||
}} />
|
||||
</Col>
|
||||
</Row>;
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col, DropDownItem, FBSelect } from "../../../ui/index";
|
||||
import { t } from "i18next";
|
||||
import { getDevice } from "../../../device";
|
||||
import { info, error } from "farmbot-toastr";
|
||||
import { info } from "farmbot-toastr";
|
||||
import { FirmwareHardware } from "farmbot";
|
||||
import { ColWidth } from "../farmbot_os_settings";
|
||||
|
||||
export interface BoardTypeProps {
|
||||
firmwareVersion: string | undefined;
|
||||
}
|
||||
import { updateConfig } from "../../actions";
|
||||
import { BoardTypeProps } from "./interfaces";
|
||||
|
||||
const FIRMWARE_CHOICES = [
|
||||
{ label: "Arduino/RAMPS (Genesis v1.2)", value: "arduino" },
|
||||
|
@ -26,10 +23,23 @@ const FIRMWARE_CHOICES_DDI = {
|
|||
}
|
||||
};
|
||||
|
||||
export class BoardType
|
||||
extends React.Component<BoardTypeProps, {}> {
|
||||
interface BoardTypeState { boardType: string, sending: boolean }
|
||||
|
||||
getBoardType() {
|
||||
export class BoardType extends React.Component<BoardTypeProps, BoardTypeState> {
|
||||
state = {
|
||||
boardType: this.boardType,
|
||||
sending: this.sending
|
||||
};
|
||||
|
||||
componentWillReceiveProps() {
|
||||
this.setState({ sending: this.sending });
|
||||
}
|
||||
|
||||
get sending() {
|
||||
return !this.props.sourceFbosConfig("firmware_hardware").consistent;
|
||||
}
|
||||
|
||||
get boardType() {
|
||||
if (this.props.firmwareVersion) {
|
||||
const boardIdentifier = this.props.firmwareVersion.slice(-1);
|
||||
switch (boardIdentifier) {
|
||||
|
@ -47,9 +57,8 @@ export class BoardType
|
|||
}
|
||||
}
|
||||
|
||||
selectedBoard(): DropDownItem | undefined {
|
||||
const board = this.getBoardType();
|
||||
switch (board) {
|
||||
get selectedBoard(): DropDownItem | undefined {
|
||||
switch (this.state.boardType) {
|
||||
case "Arduino/RAMPS":
|
||||
case "Present":
|
||||
return FIRMWARE_CHOICES_DDI["arduino"];
|
||||
|
@ -60,19 +69,19 @@ export class BoardType
|
|||
}
|
||||
}
|
||||
|
||||
sendOffConfig = (selectedBoard: DropDownItem) => {
|
||||
sendOffConfig = (selectedItem: DropDownItem) => {
|
||||
// tslint:disable-next-line:no-any
|
||||
const isFwHardwareValue = (x?: any): x is FirmwareHardware => {
|
||||
const values: FirmwareHardware[] = ["arduino", "farmduino"];
|
||||
return !!values.includes(x as FirmwareHardware);
|
||||
};
|
||||
|
||||
const firmware_hardware = selectedBoard.value;
|
||||
if (selectedBoard && isFwHardwareValue(firmware_hardware)) {
|
||||
const firmware_hardware = selectedItem.value;
|
||||
if (selectedItem && isFwHardwareValue(firmware_hardware)) {
|
||||
info(t("Sending firmware configuration..."), t("Sending"));
|
||||
getDevice()
|
||||
.updateConfig({ firmware_hardware })
|
||||
.catch(() => error(t("An error occurred during configuration.")));
|
||||
this.props.dispatch(updateConfig({ firmware_hardware }));
|
||||
this.setState({ sending: true });
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,11 +95,10 @@ export class BoardType
|
|||
<Col xs={ColWidth.description}>
|
||||
<div>
|
||||
<FBSelect
|
||||
key={this.getBoardType()}
|
||||
allowEmpty={true}
|
||||
key={this.state.boardType}
|
||||
extraClass={this.state.sending ? "dim" : ""}
|
||||
list={FIRMWARE_CHOICES}
|
||||
selectedItem={this.selectedBoard()}
|
||||
placeholder={this.getBoardType()}
|
||||
selectedItem={this.selectedBoard}
|
||||
onChange={this.sendOffConfig} />
|
||||
</div>
|
||||
</Col>
|
||||
|
|
|
@ -4,14 +4,14 @@ import { t } from "i18next";
|
|||
import { Content } from "../../../constants";
|
||||
import { factoryReset, updateConfig } from "../../actions";
|
||||
import { ToggleButton } from "../../../controls/toggle_button";
|
||||
import { noop } from "lodash";
|
||||
import { BotConfigInputBox } from "../step_per_mm_box";
|
||||
import { PowerAndResetProps } from "./power_and_reset";
|
||||
import { BotConfigInputBox } from "../bot_config_input_box";
|
||||
import { FactoryResetRowProps } from "./interfaces";
|
||||
import { ColWidth } from "../farmbot_os_settings";
|
||||
|
||||
export function FactoryResetRow(props: PowerAndResetProps) {
|
||||
const { disable_factory_reset } = props.bot.hardware.configuration;
|
||||
const maybeDisableTimer = disable_factory_reset ? { color: "grey" } : {};
|
||||
export function FactoryResetRow(props: FactoryResetRowProps) {
|
||||
const { dispatch, sourceFbosConfig } = props;
|
||||
const diableFactoryReset = sourceFbosConfig("disable_factory_reset");
|
||||
const maybeDisableTimer = diableFactoryReset.value ? { color: "grey" } : {};
|
||||
return <div>
|
||||
<Row>
|
||||
<Col xs={ColWidth.label}>
|
||||
|
@ -45,11 +45,13 @@ export function FactoryResetRow(props: PowerAndResetProps) {
|
|||
</p>
|
||||
</Col>
|
||||
<Col xs={ColWidth.button}>
|
||||
<ToggleButton toggleValue={!disable_factory_reset}
|
||||
<ToggleButton
|
||||
toggleValue={diableFactoryReset.value}
|
||||
dim={!diableFactoryReset.consistent}
|
||||
toggleAction={() => {
|
||||
updateConfig({
|
||||
disable_factory_reset: !disable_factory_reset
|
||||
})(noop);
|
||||
dispatch(updateConfig({
|
||||
disable_factory_reset: !diableFactoryReset.value
|
||||
}));
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -67,9 +69,9 @@ export function FactoryResetRow(props: PowerAndResetProps) {
|
|||
<Col xs={ColWidth.button}>
|
||||
<BotConfigInputBox
|
||||
setting="network_not_found_timer"
|
||||
bot={props.bot}
|
||||
dispatch={props.dispatch}
|
||||
disabled={disable_factory_reset} />
|
||||
dispatch={dispatch}
|
||||
disabled={!!diableFactoryReset.value}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
</Col>
|
||||
</Row >
|
||||
</div >;
|
||||
|
|
|
@ -2,25 +2,20 @@ import * as React from "react";
|
|||
import { Row, Col, Markdown } from "../../../ui/index";
|
||||
import { t } from "i18next";
|
||||
import { OsUpdateButton } from "./os_update_button";
|
||||
import { BotState } from "../../interfaces";
|
||||
import { Popover, Position } from "@blueprintjs/core";
|
||||
import { ColWidth } from "../farmbot_os_settings";
|
||||
import { ToggleButton } from "../../../controls/toggle_button";
|
||||
import { updateConfig } from "../../actions";
|
||||
import { noop, last } from "lodash";
|
||||
import { last } from "lodash";
|
||||
import { Content } from "../../../constants";
|
||||
import { FbosDetailsProps, FarmbotOsRowProps } from "./interfaces";
|
||||
|
||||
interface FarmbotOsRowProps {
|
||||
controller_version: string | undefined;
|
||||
bot: BotState;
|
||||
osReleaseNotes: string;
|
||||
}
|
||||
|
||||
export function FbosDetails(bot: BotState) {
|
||||
export function FbosDetails(props: FbosDetailsProps) {
|
||||
const { dispatch, sourceFbosConfig } = props;
|
||||
const {
|
||||
env, commit, target, node_name, firmware_version, firmware_commit
|
||||
} = bot.hardware.informational_settings;
|
||||
const { beta_opt_in } = bot.hardware.configuration;
|
||||
} = props.bot.hardware.informational_settings;
|
||||
const betaOptIn = sourceFbosConfig("beta_opt_in");
|
||||
const shortenCommit = (longCommit: string) => (longCommit || "").slice(0, 8);
|
||||
return <div>
|
||||
<p><b>Environment: </b>{env}</p>
|
||||
|
@ -34,16 +29,20 @@ export function FbosDetails(bot: BotState) {
|
|||
{t("Beta release Opt-In")}
|
||||
</label>
|
||||
<ToggleButton
|
||||
toggleValue={beta_opt_in}
|
||||
toggleValue={betaOptIn.value}
|
||||
dim={!betaOptIn.consistent}
|
||||
toggleAction={() =>
|
||||
(beta_opt_in || confirm(Content.OS_BETA_RELEASES)) &&
|
||||
updateConfig({ beta_opt_in: !beta_opt_in })(noop)} />
|
||||
(betaOptIn.value || confirm(Content.OS_BETA_RELEASES)) &&
|
||||
dispatch(updateConfig({ beta_opt_in: !betaOptIn.value }))} />
|
||||
</fieldset>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export function FarmbotOsRow(props: FarmbotOsRowProps) {
|
||||
const version = props.controller_version || t(" unknown (offline)");
|
||||
const {
|
||||
controller_version, sourceFbosConfig, dispatch, bot, osReleaseNotes
|
||||
} = props;
|
||||
const version = controller_version || t(" unknown (offline)");
|
||||
return <Row>
|
||||
<Col xs={ColWidth.label}>
|
||||
<label>
|
||||
|
@ -55,7 +54,10 @@ export function FarmbotOsRow(props: FarmbotOsRowProps) {
|
|||
<p>
|
||||
{t("Version {{ version }}", { version })}
|
||||
</p>
|
||||
<FbosDetails {...props.bot} />
|
||||
<FbosDetails
|
||||
bot={bot}
|
||||
dispatch={dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
</Popover>
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
|
@ -66,13 +68,13 @@ export function FarmbotOsRow(props: FarmbotOsRowProps) {
|
|||
</p>
|
||||
<div className="release-notes">
|
||||
<Markdown>
|
||||
{props.osReleaseNotes}
|
||||
{osReleaseNotes}
|
||||
</Markdown>
|
||||
</div>
|
||||
</Popover>
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
<OsUpdateButton bot={props.bot} />
|
||||
<OsUpdateButton bot={bot} />
|
||||
</Col>
|
||||
</Row >;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import { SourceFbosConfig, BotState, ControlPanelState } from "../../interfaces";
|
||||
|
||||
export interface AutoSyncRowProps {
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface AutoUpdateRowProps {
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface BoardTypeProps {
|
||||
firmwareVersion: string | undefined;
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface PowerAndResetProps {
|
||||
controlPanelState: ControlPanelState;
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface FactoryResetRowProps {
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface FarmbotOsRowProps {
|
||||
controller_version: string | undefined;
|
||||
bot: BotState;
|
||||
osReleaseNotes: string;
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface FbosDetailsProps {
|
||||
bot: BotState;
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
|
@ -1,19 +1,14 @@
|
|||
import * as React from "react";
|
||||
import { Header } from "../hardware_settings/header";
|
||||
import { Collapse } from "@blueprintjs/core";
|
||||
import { BotState } from "../../interfaces";
|
||||
import { RestartRow } from "./restart_row";
|
||||
import { ShutdownRow } from "./shutdown_row";
|
||||
import { FactoryResetRow } from "./factory_reset_row";
|
||||
|
||||
export interface PowerAndResetProps {
|
||||
bot: BotState;
|
||||
dispatch: Function;
|
||||
}
|
||||
import { PowerAndResetProps } from "./interfaces";
|
||||
|
||||
export function PowerAndReset(props: PowerAndResetProps) {
|
||||
const { bot, dispatch } = props;
|
||||
const { power_and_reset } = bot.controlPanelState;
|
||||
const { dispatch, sourceFbosConfig } = props;
|
||||
const { power_and_reset } = props.controlPanelState;
|
||||
return <section>
|
||||
<div style={{ fontSize: "1px" }}>
|
||||
<Header
|
||||
|
@ -25,7 +20,9 @@ export function PowerAndReset(props: PowerAndResetProps) {
|
|||
<Collapse isOpen={!!power_and_reset}>
|
||||
<RestartRow />
|
||||
<ShutdownRow />
|
||||
<FactoryResetRow bot={bot} dispatch={dispatch} />
|
||||
<FactoryResetRow
|
||||
dispatch={dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
</Collapse>
|
||||
</section>;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ export class HardwareSettings extends
|
|||
React.Component<HardwareSettingsProps, {}> {
|
||||
|
||||
render() {
|
||||
const { bot, dispatch } = this.props;
|
||||
const { bot, dispatch, sourceFbosConfig } = this.props;
|
||||
const { sync_status } = this.props.bot.hardware.informational_settings;
|
||||
return <Widget className="hardware-widget">
|
||||
<WidgetHeader title="Hardware" helpText={ToolTips.HW_SETTINGS}>
|
||||
|
@ -58,7 +58,8 @@ export class HardwareSettings extends
|
|||
bot={bot} />
|
||||
<Motors
|
||||
dispatch={dispatch}
|
||||
bot={bot} />
|
||||
bot={bot}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
<EncodersAndEndStops
|
||||
dispatch={dispatch}
|
||||
bot={bot} />
|
||||
|
|
|
@ -17,9 +17,18 @@ describe("<Motors/>", () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const fakeProps = (): MotorsProps => {
|
||||
return {
|
||||
dispatch: jest.fn(),
|
||||
bot,
|
||||
sourceFbosConfig: (x) => {
|
||||
return { value: bot.hardware.configuration[x], consistent: true };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
it("renders the base case", () => {
|
||||
const props: MotorsProps = { dispatch: jest.fn(), bot };
|
||||
const el = render(<Motors {...props} />);
|
||||
const el = render(<Motors {...fakeProps() } />);
|
||||
const txt = el.text();
|
||||
[ // Not a whole lot to test here....
|
||||
"Enable 2nd X Motor",
|
||||
|
@ -30,26 +39,26 @@ describe("<Motors/>", () => {
|
|||
});
|
||||
|
||||
it("doesn't render homing speed", () => {
|
||||
const props: MotorsProps = { dispatch: jest.fn(), bot };
|
||||
props.bot.hardware.informational_settings.firmware_version = "4.0.0R";
|
||||
const wrapper = render(<Motors {...props} />);
|
||||
const p = fakeProps();
|
||||
p.bot.hardware.informational_settings.firmware_version = "4.0.0R";
|
||||
const wrapper = render(<Motors {...p} />);
|
||||
expect(wrapper.text()).not.toContain("Homing Speed");
|
||||
});
|
||||
|
||||
it("renders homing speed", () => {
|
||||
const props: MotorsProps = { dispatch: jest.fn(), bot };
|
||||
props.bot.hardware.informational_settings.firmware_version = "5.1.0R";
|
||||
const wrapper = render(<Motors {...props} />);
|
||||
const p = fakeProps();
|
||||
p.bot.hardware.informational_settings.firmware_version = "5.1.0R";
|
||||
const wrapper = render(<Motors {...p} />);
|
||||
expect(wrapper.text()).toContain("Homing Speed");
|
||||
});
|
||||
|
||||
function testParamToggle(
|
||||
description: string, parameter: McuParamName, position: number) {
|
||||
it(description, () => {
|
||||
bot.controlPanelState.motors = true;
|
||||
bot.hardware.mcu_params[parameter] = 1;
|
||||
const props: MotorsProps = { dispatch: jest.fn(), bot };
|
||||
const wrapper = mount(<Motors {...props} />);
|
||||
const p = fakeProps();
|
||||
p.bot.controlPanelState.motors = true;
|
||||
p.bot.hardware.mcu_params[parameter] = 1;
|
||||
const wrapper = mount(<Motors {...p} />);
|
||||
wrapper.find("button").at(position).simulate("click");
|
||||
expect(mockDevice.updateMcu)
|
||||
.toHaveBeenCalledWith({ [parameter]: 0 });
|
||||
|
@ -61,10 +70,18 @@ describe("<Motors/>", () => {
|
|||
});
|
||||
|
||||
describe("<StepsPerMmSettings/>", () => {
|
||||
const fakeProps = (): MotorsProps => {
|
||||
return {
|
||||
dispatch: jest.fn(),
|
||||
bot,
|
||||
sourceFbosConfig: jest.fn()
|
||||
};
|
||||
};
|
||||
|
||||
it("renders OS settings", () => {
|
||||
const props: MotorsProps = { dispatch: jest.fn(), bot };
|
||||
props.bot.hardware.informational_settings.firmware_version = "4.0.0R";
|
||||
const wrapper = shallow(<StepsPerMmSettings {...props} />);
|
||||
const p = fakeProps();
|
||||
p.bot.hardware.informational_settings.firmware_version = "4.0.0R";
|
||||
const wrapper = shallow(<StepsPerMmSettings {...p} />);
|
||||
const firstInputProps = wrapper.find("BotConfigInputBox")
|
||||
// tslint:disable-next-line:no-any
|
||||
.first().props() as any;
|
||||
|
@ -72,9 +89,9 @@ describe("<StepsPerMmSettings/>", () => {
|
|||
});
|
||||
|
||||
it("renders mcu settings", () => {
|
||||
const props: MotorsProps = { dispatch: jest.fn(), bot };
|
||||
props.bot.hardware.informational_settings.firmware_version = "5.0.5R";
|
||||
const wrapper = shallow(<StepsPerMmSettings {...props} />);
|
||||
const p = fakeProps();
|
||||
p.bot.hardware.informational_settings.firmware_version = "5.0.5R";
|
||||
const wrapper = shallow(<StepsPerMmSettings {...p} />);
|
||||
const firstInputProps = wrapper.find("NumericMCUInputGroup")
|
||||
.first().props();
|
||||
expect(firstInputProps.x).toBe("movement_step_per_mm_x");
|
||||
|
|
|
@ -6,7 +6,7 @@ import { SpacePanelToolTip } from "../space_panel_tool_tip";
|
|||
import { ToggleButton } from "../../../controls/toggle_button";
|
||||
import { settingToggle } from "../../actions";
|
||||
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
|
||||
import { BotConfigInputBox } from "../step_per_mm_box";
|
||||
import { BotConfigInputBox } from "../bot_config_input_box";
|
||||
import { MotorsProps } from "../interfaces";
|
||||
import { Row, Col } from "../../../ui/index";
|
||||
import { Header } from "./header";
|
||||
|
@ -14,7 +14,8 @@ import { Collapse } from "@blueprintjs/core";
|
|||
import { McuInputBox } from "../mcu_input_box";
|
||||
import { minFwVersionCheck } from "../../../util";
|
||||
|
||||
export function StepsPerMmSettings({ dispatch, bot }: MotorsProps) {
|
||||
export function StepsPerMmSettings(props: MotorsProps) {
|
||||
const { dispatch, bot, sourceFbosConfig } = props;
|
||||
const { firmware_version } = bot.hardware.informational_settings;
|
||||
if (minFwVersionCheck(firmware_version, "5.0.5")) {
|
||||
return <NumericMCUInputGroup
|
||||
|
@ -36,27 +37,27 @@ export function StepsPerMmSettings({ dispatch, bot }: MotorsProps) {
|
|||
<Col xs={2}>
|
||||
<BotConfigInputBox
|
||||
setting="steps_per_mm_x"
|
||||
bot={bot}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
dispatch={dispatch} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<BotConfigInputBox
|
||||
setting="steps_per_mm_y"
|
||||
bot={bot}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
dispatch={dispatch} />
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
<BotConfigInputBox
|
||||
setting="steps_per_mm_z"
|
||||
bot={bot}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
dispatch={dispatch} />
|
||||
</Col>
|
||||
</Row>;
|
||||
}
|
||||
}
|
||||
|
||||
export function Motors({ dispatch, bot }: MotorsProps) {
|
||||
|
||||
export function Motors(props: MotorsProps) {
|
||||
const { dispatch, bot, sourceFbosConfig } = props;
|
||||
const { mcu_params } = bot.hardware;
|
||||
const { motors } = bot.controlPanelState;
|
||||
const { firmware_version } = bot.hardware.informational_settings;
|
||||
|
@ -131,7 +132,8 @@ export function Motors({ dispatch, bot }: MotorsProps) {
|
|||
dispatch={dispatch} />
|
||||
<StepsPerMmSettings
|
||||
dispatch={dispatch}
|
||||
bot={bot} />
|
||||
bot={bot}
|
||||
sourceFbosConfig={sourceFbosConfig} />
|
||||
<BooleanMCUInputGroup
|
||||
name={t("Always Power Motors")}
|
||||
tooltip={t(ToolTips.ALWAYS_POWER_MOTORS)}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { BotState } from "../interfaces";
|
||||
import { BotState, SourceFbosConfig } from "../interfaces";
|
||||
import { McuParamName, McuParams } from "farmbot/dist";
|
||||
import { IntegerSize } from "../../util";
|
||||
|
||||
|
@ -58,6 +58,7 @@ export interface PinGuardProps {
|
|||
export interface MotorsProps {
|
||||
dispatch: Function;
|
||||
bot: BotState;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface EncodersProps {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { FbosConfig } from "../../config_storage/fbos_configs";
|
||||
import { Configuration, ConfigurationName } from "farmbot";
|
||||
import { SourceFbosConfig } from "../interfaces";
|
||||
|
||||
export const sourceFbosConfigValue =
|
||||
(apiConfig: FbosConfig | undefined, botConfig: Configuration
|
||||
): SourceFbosConfig =>
|
||||
(setting: ConfigurationName) => {
|
||||
const apiValue = apiConfig && apiConfig[setting as keyof FbosConfig];
|
||||
const botValue = botConfig[setting];
|
||||
return {
|
||||
value: apiConfig ? apiValue : botValue,
|
||||
consistent: apiConfig ? apiValue === botValue : true
|
||||
};
|
||||
};
|
|
@ -64,7 +64,8 @@ export class Devices extends React.Component<Props, {}> {
|
|||
dispatch={this.props.dispatch}
|
||||
bot={this.props.bot}
|
||||
botToMqttLastSeen={botToMqttLastSeen}
|
||||
botToMqttStatus={botToMqttStatus} />
|
||||
botToMqttStatus={botToMqttStatus}
|
||||
sourceFbosConfig={this.props.sourceFbosConfig} />
|
||||
<ConnectivityPanel
|
||||
status={this.props.deviceAccount.specialStatus}
|
||||
onRefresh={this.refresh}
|
||||
|
@ -82,7 +83,8 @@ export class Devices extends React.Component<Props, {}> {
|
|||
controlPanelState={this.props.bot.controlPanelState}
|
||||
dispatch={this.props.dispatch}
|
||||
bot={this.props.bot}
|
||||
botToMqttStatus={botToMqttStatus} />
|
||||
botToMqttStatus={botToMqttStatus}
|
||||
sourceFbosConfig={this.props.sourceFbosConfig} />
|
||||
{this.props.bot.hardware.gpio_registry &&
|
||||
<PinBindings
|
||||
dispatch={this.props.dispatch}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { BotStateTree } from "farmbot";
|
||||
import { BotStateTree, ConfigurationName } from "farmbot";
|
||||
import {
|
||||
McuParamName,
|
||||
ConfigurationName,
|
||||
Dictionary,
|
||||
SyncStatus,
|
||||
FarmwareManifest,
|
||||
|
@ -30,8 +29,15 @@ export interface Props {
|
|||
images: TaggedImage[];
|
||||
dispatch: Function;
|
||||
resources: ResourceIndex;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export type SourceFbosConfig = (config: ConfigurationName) =>
|
||||
{
|
||||
value: boolean | number | string | undefined,
|
||||
consistent: boolean
|
||||
};
|
||||
|
||||
/** How the device is stored in the API side.
|
||||
* This is what comes back from the API as JSON.
|
||||
*/
|
||||
|
@ -105,6 +111,7 @@ export interface FarmbotOsProps {
|
|||
botToMqttStatus: NetworkState;
|
||||
botToMqttLastSeen: string;
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface FarmbotOsState {
|
||||
|
@ -119,13 +126,6 @@ export interface CameraSelectionState {
|
|||
cameraStatus: "" | "sending" | "done" | "error";
|
||||
}
|
||||
|
||||
export interface StepsPerMMBoxProps {
|
||||
bot: BotState;
|
||||
setting: ConfigurationName;
|
||||
dispatch: Function;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface McuInputBoxProps {
|
||||
bot: BotState;
|
||||
setting: McuParamName;
|
||||
|
@ -166,6 +166,7 @@ export interface HardwareSettingsProps {
|
|||
dispatch: Function;
|
||||
botToMqttStatus: NetworkState;
|
||||
bot: BotState;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface ControlPanelState {
|
||||
|
|
|
@ -2,10 +2,16 @@ import { Everything } from "../interfaces";
|
|||
import { Props } from "./interfaces";
|
||||
import {
|
||||
selectAllImages,
|
||||
getDeviceAccountSettings
|
||||
getDeviceAccountSettings,
|
||||
getFbosConfig
|
||||
} from "../resources/selectors";
|
||||
import { sourceFbosConfigValue } from "./components/source_fbos_config_value";
|
||||
|
||||
export function mapStateToProps(props: Everything): Props {
|
||||
const conf = getFbosConfig(props.resources.index);
|
||||
const { hardware } = props.bot;
|
||||
const fbosConfig = (conf && conf.body && conf.body.api_migrated)
|
||||
? conf.body : undefined;
|
||||
return {
|
||||
userToApi: props.bot.connectivity["user.api"],
|
||||
userToMqtt: props.bot.connectivity["user.mqtt"],
|
||||
|
@ -16,5 +22,6 @@ export function mapStateToProps(props: Everything): Props {
|
|||
dispatch: props.dispatch,
|
||||
images: selectAllImages(props.resources.index),
|
||||
resources: props.resources.index,
|
||||
sourceFbosConfig: sourceFbosConfigValue(fbosConfig, hardware.configuration),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -65,8 +65,18 @@ describe("<Logs />", () => {
|
|||
});
|
||||
}
|
||||
|
||||
const fakeProps = () => {
|
||||
return {
|
||||
logs: fakeLogs(),
|
||||
bot,
|
||||
timeOffset: 0,
|
||||
dispatch: jest.fn(),
|
||||
sourceFbosConfig: jest.fn()
|
||||
};
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<Logs logs={fakeLogs()} bot={bot} timeOffset={0} />);
|
||||
const wrapper = mount(<Logs {...fakeProps() } />);
|
||||
["Logs", ToolTips.LOGS, "Type", "Message", "Time", "Info",
|
||||
"Fake log message 1", "Success", "Fake log message 2"]
|
||||
.map(string =>
|
||||
|
@ -77,7 +87,7 @@ describe("<Logs />", () => {
|
|||
});
|
||||
|
||||
it("filters logs", () => {
|
||||
const wrapper = mount(<Logs logs={fakeLogs()} bot={bot} timeOffset={0} />);
|
||||
const wrapper = mount(<Logs {...fakeProps() } />);
|
||||
wrapper.setState({ info: 0 });
|
||||
expect(wrapper.text()).not.toContain("Fake log message 1");
|
||||
const filterBtn = wrapper.find("button").first();
|
||||
|
@ -86,31 +96,31 @@ describe("<Logs />", () => {
|
|||
});
|
||||
|
||||
it("shows position", () => {
|
||||
const logs = fakeLogs();
|
||||
logs[0].body.meta.x = 100;
|
||||
logs[1].body.meta.x = 0;
|
||||
logs[1].body.meta.y = 1;
|
||||
logs[1].body.meta.z = 2;
|
||||
const wrapper = mount(<Logs logs={logs} bot={bot} timeOffset={0} />);
|
||||
const p = fakeProps();
|
||||
p.logs[0].body.meta.x = 100;
|
||||
p.logs[1].body.meta.x = 0;
|
||||
p.logs[1].body.meta.y = 1;
|
||||
p.logs[1].body.meta.z = 2;
|
||||
const wrapper = mount(<Logs {...p} />);
|
||||
expect(wrapper.text()).toContain("Unknown");
|
||||
expect(wrapper.text()).toContain("0, 1, 2");
|
||||
});
|
||||
|
||||
it("shows verbosity", () => {
|
||||
const logs = fakeLogs();
|
||||
logs[0].body.meta.verbosity = -999;
|
||||
const wrapper = mount(<Logs logs={logs} bot={bot} timeOffset={0} />);
|
||||
const p = fakeProps();
|
||||
p.logs[0].body.meta.verbosity = -999;
|
||||
const wrapper = mount(<Logs {...p} />);
|
||||
expect(wrapper.text()).toContain(-999);
|
||||
});
|
||||
|
||||
it("loads filter setting", () => {
|
||||
mockStorj[NumericSetting.warn_log] = 3;
|
||||
const wrapper = mount(<Logs logs={fakeLogs()} bot={bot} timeOffset={0} />);
|
||||
const wrapper = mount(<Logs {...fakeProps() } />);
|
||||
expect(wrapper.state().warn).toEqual(3);
|
||||
});
|
||||
|
||||
it("shows overall filter status", () => {
|
||||
const wrapper = mount(<Logs logs={fakeLogs()} bot={bot} timeOffset={0} />);
|
||||
const wrapper = mount(<Logs {...fakeProps() } />);
|
||||
wrapper.setState({
|
||||
success: 3, busy: 3, warn: 3, error: 3, info: 3, fun: 3, debug: 3
|
||||
});
|
||||
|
@ -121,7 +131,7 @@ describe("<Logs />", () => {
|
|||
|
||||
it("toggles filter", () => {
|
||||
mockStorj[NumericSetting.warn_log] = 3;
|
||||
const wrapper = mount(<Logs logs={fakeLogs()} bot={bot} timeOffset={0} />);
|
||||
const wrapper = mount(<Logs {...fakeProps() } />);
|
||||
// tslint:disable-next-line:no-any
|
||||
const instance = wrapper.instance() as any;
|
||||
expect(wrapper.state().warn).toEqual(3);
|
||||
|
@ -133,7 +143,7 @@ describe("<Logs />", () => {
|
|||
|
||||
it("sets filter", () => {
|
||||
mockStorj[NumericSetting.warn_log] = 3;
|
||||
const wrapper = mount(<Logs logs={fakeLogs()} bot={bot} timeOffset={0} />);
|
||||
const wrapper = mount(<Logs {...fakeProps() } />);
|
||||
// tslint:disable-next-line:no-any
|
||||
const instance = wrapper.instance() as any;
|
||||
expect(wrapper.state().warn).toEqual(3);
|
||||
|
|
|
@ -5,6 +5,7 @@ import { TaggedLog, SpecialStatus } from "../../resources/tagged_resources";
|
|||
import { Log } from "../../interfaces";
|
||||
import { generateUuid } from "../../resources/util";
|
||||
import { times } from "lodash";
|
||||
import { fakeFbosConfig } from "../../__test_support__/fake_state/resources";
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
function fakeLogs(count: number): TaggedLog[] {
|
||||
|
@ -33,4 +34,30 @@ describe("mapStateToProps()", () => {
|
|||
const props = mapStateToProps(state);
|
||||
expect(props.logs.length).toEqual(250);
|
||||
});
|
||||
|
||||
it("API source of FBOS settings", () => {
|
||||
const state = fakeState();
|
||||
state.bot.hardware.configuration.sequence_init_log = false;
|
||||
const fakeApiConfig = fakeFbosConfig();
|
||||
fakeApiConfig.body.sequence_init_log = true;
|
||||
fakeApiConfig.body.api_migrated = true;
|
||||
state.resources = buildResourceIndex([fakeApiConfig]);
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.sourceFbosConfig("sequence_init_log")).toEqual({
|
||||
value: true, consistent: false
|
||||
});
|
||||
});
|
||||
|
||||
it("bot source of FBOS settings", () => {
|
||||
const state = fakeState();
|
||||
state.bot.hardware.configuration.sequence_init_log = false;
|
||||
const fakeApiConfig = fakeFbosConfig();
|
||||
fakeApiConfig.body.sequence_init_log = true;
|
||||
fakeApiConfig.body.api_migrated = false;
|
||||
state.resources = buildResourceIndex([fakeApiConfig]);
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.sourceFbosConfig("sequence_init_log")).toEqual({
|
||||
value: false, consistent: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -33,15 +33,25 @@ import { LogsSettingsMenu } from "../settings_menu";
|
|||
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||
import { ConfigurationName, Dictionary } from "farmbot";
|
||||
import { NumericSetting } from "../../../session_keys";
|
||||
import { LogsSettingsMenuProps } from "../../interfaces";
|
||||
|
||||
describe("<LogsSettingsMenu />", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const fakeProps = (): LogsSettingsMenuProps => {
|
||||
return {
|
||||
setFilterLevel: () => jest.fn(),
|
||||
dispatch: jest.fn(x => x()),
|
||||
sourceFbosConfig: (x) => {
|
||||
return { value: bot.hardware.configuration[x], consistent: true };
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<LogsSettingsMenu
|
||||
bot={bot} setFilterLevel={() => jest.fn()} />);
|
||||
const wrapper = mount(<LogsSettingsMenu {...fakeProps() } />);
|
||||
["begin", "steps", "complete"].map(string =>
|
||||
expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
});
|
||||
|
@ -49,8 +59,7 @@ describe("<LogsSettingsMenu />", () => {
|
|||
function testSettingToggle(setting: ConfigurationName, position: number) {
|
||||
it("toggles setting", () => {
|
||||
bot.hardware.configuration[setting] = false;
|
||||
const wrapper = mount(<LogsSettingsMenu
|
||||
bot={bot} setFilterLevel={() => jest.fn()} />);
|
||||
const wrapper = mount(<LogsSettingsMenu {...fakeProps() } />);
|
||||
wrapper.find("button").at(position).simulate("click");
|
||||
expect(mockDevice.updateConfig)
|
||||
.toHaveBeenCalledWith({ [setting]: true });
|
||||
|
@ -64,9 +73,10 @@ describe("<LogsSettingsMenu />", () => {
|
|||
testSettingToggle("arduino_debug_messages", 5);
|
||||
|
||||
it("conditionally increases filter level", () => {
|
||||
const p = fakeProps();
|
||||
const setFilterLevel = jest.fn();
|
||||
const wrapper = mount(<LogsSettingsMenu
|
||||
bot={bot} setFilterLevel={() => setFilterLevel} />);
|
||||
p.setFilterLevel = () => setFilterLevel;
|
||||
const wrapper = mount(<LogsSettingsMenu {...p} />);
|
||||
mockStorj[NumericSetting.busy_log] = 0;
|
||||
wrapper.find("button").at(0).simulate("click");
|
||||
expect(setFilterLevel).toHaveBeenCalledWith(2);
|
||||
|
|
|
@ -4,27 +4,73 @@ import { Help } from "../../ui/index";
|
|||
import { ToolTips } from "../../constants";
|
||||
import { ToggleButton } from "../../controls/toggle_button";
|
||||
import { updateConfig } from "../../devices/actions";
|
||||
import { noop } from "lodash";
|
||||
import {
|
||||
LogSettingProps, LogsSettingsMenuProps, LogsState
|
||||
} from "../interfaces";
|
||||
import { Session, safeNumericSetting } from "../../session";
|
||||
import { ConfigurationName } from "farmbot";
|
||||
|
||||
interface LogSettingRecord {
|
||||
label: string;
|
||||
setting: ConfigurationName;
|
||||
tooltip: string;
|
||||
}
|
||||
|
||||
const SEQUENCE_LOG_SETTINGS: LogSettingRecord[] = [
|
||||
{
|
||||
label: "Begin",
|
||||
setting: "sequence_init_log",
|
||||
tooltip: ToolTips.SEQUENCE_LOG_BEGIN
|
||||
},
|
||||
{
|
||||
label: "Steps",
|
||||
setting: "sequence_body_log",
|
||||
tooltip: ToolTips.SEQUENCE_LOG_STEP
|
||||
},
|
||||
{
|
||||
label: "Complete",
|
||||
setting: "sequence_complete_log",
|
||||
tooltip: ToolTips.SEQUENCE_LOG_END
|
||||
}
|
||||
];
|
||||
|
||||
const FIRMWARE_LOG_SETTINGS: LogSettingRecord[] = [
|
||||
{
|
||||
label: "Sent",
|
||||
setting: "firmware_output_log",
|
||||
tooltip: ToolTips.FIRMWARE_LOG_SENT
|
||||
},
|
||||
{
|
||||
label: "Received",
|
||||
setting: "firmware_input_log",
|
||||
tooltip: ToolTips.FIRMWARE_LOG_RECEIVED
|
||||
},
|
||||
{
|
||||
label: "Debug",
|
||||
setting: "arduino_debug_messages",
|
||||
tooltip: ToolTips.FIRMWARE_DEBUG_MESSAGES
|
||||
},
|
||||
];
|
||||
|
||||
const LogSetting = (props: LogSettingProps) => {
|
||||
const { label, setting, toolTip, value, setFilterLevel } = props;
|
||||
const { label, setting, toolTip, setFilterLevel, sourceFbosConfig } = props;
|
||||
const updateMinFilterLevel = (name: keyof LogsState, level: number) => {
|
||||
const currentLevel = Session.deprecatedGetNum(safeNumericSetting(name + "_log")) || 0;
|
||||
const currentLevel =
|
||||
Session.deprecatedGetNum(safeNumericSetting(name + "_log")) || 0;
|
||||
if (currentLevel < level) { setFilterLevel(name)(level); }
|
||||
};
|
||||
const config = sourceFbosConfig(setting);
|
||||
return <fieldset>
|
||||
<label>
|
||||
{t(label)}
|
||||
</label>
|
||||
<Help text={t(toolTip)} />
|
||||
<ToggleButton toggleValue={value}
|
||||
<ToggleButton
|
||||
toggleValue={config.value}
|
||||
dim={!config.consistent}
|
||||
toggleAction={() => {
|
||||
updateConfig({ [setting]: !value })(noop);
|
||||
if (!value === true) {
|
||||
props.dispatch(updateConfig({ [setting]: !config.value }));
|
||||
if (!config.value === true) {
|
||||
switch (setting) {
|
||||
case "firmware_output_log":
|
||||
case "firmware_input_log":
|
||||
|
@ -47,46 +93,21 @@ const LogSetting = (props: LogSettingProps) => {
|
|||
};
|
||||
|
||||
export const LogsSettingsMenu = (props: LogsSettingsMenuProps) => {
|
||||
const { bot, setFilterLevel } = props;
|
||||
const { configuration } = bot.hardware;
|
||||
const { setFilterLevel, sourceFbosConfig } = props;
|
||||
const LogSettingRow = (settingProps: LogSettingRecord) => {
|
||||
const { label, setting, tooltip } = settingProps;
|
||||
return <LogSetting
|
||||
label={label}
|
||||
setting={setting}
|
||||
toolTip={tooltip}
|
||||
setFilterLevel={setFilterLevel}
|
||||
dispatch={props.dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig} />;
|
||||
};
|
||||
return <div className={"logs-settings-menu"}>
|
||||
{t("Create logs for sequence:")}
|
||||
<LogSetting
|
||||
label={"Begin"}
|
||||
setting={"sequence_init_log"}
|
||||
toolTip={ToolTips.SEQUENCE_LOG_BEGIN}
|
||||
value={configuration.sequence_init_log}
|
||||
setFilterLevel={setFilterLevel} />
|
||||
<LogSetting
|
||||
label={"Steps"}
|
||||
setting={"sequence_body_log"}
|
||||
toolTip={ToolTips.SEQUENCE_LOG_STEP}
|
||||
value={configuration.sequence_body_log}
|
||||
setFilterLevel={setFilterLevel} />
|
||||
<LogSetting
|
||||
label={"Complete"}
|
||||
setting={"sequence_complete_log"}
|
||||
toolTip={ToolTips.SEQUENCE_LOG_END}
|
||||
value={configuration.sequence_complete_log}
|
||||
setFilterLevel={setFilterLevel} />
|
||||
{SEQUENCE_LOG_SETTINGS.map(p => <LogSettingRow key={p.setting} {...p} />)}
|
||||
{t("Firmware Logs:")}
|
||||
<LogSetting
|
||||
label={"Sent"}
|
||||
setting={"firmware_output_log"}
|
||||
toolTip={ToolTips.FIRMWARE_LOG_SENT}
|
||||
value={configuration.firmware_output_log}
|
||||
setFilterLevel={setFilterLevel} />
|
||||
<LogSetting
|
||||
label={"Received"}
|
||||
setting={"firmware_input_log"}
|
||||
toolTip={ToolTips.FIRMWARE_LOG_RECEIVED}
|
||||
value={configuration.firmware_input_log}
|
||||
setFilterLevel={setFilterLevel} />
|
||||
<LogSetting
|
||||
label={"Debug"}
|
||||
setting={"arduino_debug_messages"}
|
||||
toolTip={ToolTips.FIRMWARE_DEBUG_MESSAGES}
|
||||
value={configuration.arduino_debug_messages}
|
||||
setFilterLevel={setFilterLevel} />
|
||||
{FIRMWARE_LOG_SETTINGS.map(p => <LogSettingRow key={p.setting} {...p} />)}
|
||||
</div>;
|
||||
};
|
||||
|
|
|
@ -89,7 +89,9 @@ export class Logs extends React.Component<LogsProps, Partial<LogsState>> {
|
|||
<Popover position={Position.BOTTOM_RIGHT}>
|
||||
<i className="fa fa-gear" />
|
||||
<LogsSettingsMenu
|
||||
setFilterLevel={this.setFilterLevel} bot={this.props.bot} />
|
||||
setFilterLevel={this.setFilterLevel}
|
||||
dispatch={this.props.dispatch}
|
||||
sourceFbosConfig={this.props.sourceFbosConfig} />
|
||||
</Popover>
|
||||
</div>
|
||||
<div className={"settings-menu-button"}>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { TaggedLog } from "../resources/tagged_resources";
|
||||
import { BotState } from "../devices/interfaces";
|
||||
import { BotState, SourceFbosConfig } from "../devices/interfaces";
|
||||
import { ConfigurationName } from "farmbot";
|
||||
|
||||
export interface LogsProps {
|
||||
logs: TaggedLog[];
|
||||
bot: BotState;
|
||||
timeOffset: number;
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface Filters {
|
||||
|
@ -41,11 +43,13 @@ export interface LogSettingProps {
|
|||
label: string;
|
||||
setting: ConfigurationName;
|
||||
toolTip: string;
|
||||
value: boolean | number | undefined;
|
||||
setFilterLevel: SetNumSetting;
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
||||
export interface LogsSettingsMenuProps {
|
||||
bot: BotState;
|
||||
setFilterLevel: SetNumSetting;
|
||||
dispatch: Function;
|
||||
sourceFbosConfig: SourceFbosConfig;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
import { Everything } from "../interfaces";
|
||||
import { selectAllLogs, maybeGetTimeOffset } from "../resources/selectors";
|
||||
import {
|
||||
selectAllLogs, maybeGetTimeOffset, getFbosConfig
|
||||
} from "../resources/selectors";
|
||||
import * as _ from "lodash";
|
||||
import { LogsProps } from "./interfaces";
|
||||
import {
|
||||
sourceFbosConfigValue
|
||||
} from "../devices/components/source_fbos_config_value";
|
||||
|
||||
export function mapStateToProps(props: Everything): LogsProps {
|
||||
const { hardware } = props.bot;
|
||||
const conf = getFbosConfig(props.resources.index);
|
||||
const fbosConfig = (conf && conf.body && conf.body.api_migrated)
|
||||
? conf.body : undefined;
|
||||
return {
|
||||
dispatch: props.dispatch,
|
||||
sourceFbosConfig: sourceFbosConfigValue(fbosConfig, hardware.configuration),
|
||||
logs: _(selectAllLogs(props.resources.index))
|
||||
.sortBy("body.created_at")
|
||||
.reverse()
|
||||
|
|
|
@ -127,6 +127,7 @@ export let resourceReducer = generateReducer
|
|||
case "User":
|
||||
case "WebcamFeed":
|
||||
case "WebAppConfig":
|
||||
case "FbosConfig":
|
||||
reindexResource(s.index, resource);
|
||||
dontTouchThis(resource);
|
||||
s.index.references[resource.uuid] = resource;
|
||||
|
@ -154,6 +155,7 @@ export let resourceReducer = generateReducer
|
|||
case "User":
|
||||
case "WebcamFeed":
|
||||
case "WebAppConfig":
|
||||
case "FbosConfig":
|
||||
case "Image":
|
||||
removeFromIndex(s.index, resource);
|
||||
break;
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
TaggedUser,
|
||||
TaggedWebcamFeed,
|
||||
TaggedDevice,
|
||||
TaggedFbosConfig,
|
||||
TaggedWebAppConfig
|
||||
} from "./tagged_resources";
|
||||
import { CowardlyDictionary, betterCompact, sortResourcesById } from "../util";
|
||||
|
@ -556,3 +557,10 @@ export function getWebAppConfig(i: ResourceIndex): TaggedWebAppConfig | undefine
|
|||
return conf;
|
||||
}
|
||||
}
|
||||
|
||||
export function getFbosConfig(i: ResourceIndex): TaggedFbosConfig | undefined {
|
||||
const conf = i.references[i.byKind.FbosConfig[0] || "NO"];
|
||||
if (conf && conf.kind === "FbosConfig") {
|
||||
return conf;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ export type TaggedResource =
|
|||
| TaggedTool
|
||||
| TaggedUser
|
||||
| TaggedWebcamFeed
|
||||
| TaggedFbosConfig
|
||||
| TaggedWebAppConfig;
|
||||
|
||||
export type TaggedRegimen = Resource<"Regimen", Regimen>;
|
||||
|
|
|
@ -14,6 +14,7 @@ import { HttpData } from "../util";
|
|||
import { WebcamFeed } from "../controls/interfaces";
|
||||
import { WebAppConfig } from "../config_storage/web_app_configs";
|
||||
import { Session } from "../session";
|
||||
import { FbosConfig } from "../config_storage/fbos_configs";
|
||||
|
||||
export interface ResourceReadyPayl {
|
||||
name: ResourceName;
|
||||
|
@ -40,6 +41,7 @@ export function fetchSyncData(dispatch: Function) {
|
|||
fetch<User>("User", API.current.usersPath);
|
||||
fetch<DeviceAccountSettings>("Device", API.current.devicePath);
|
||||
fetch<WebcamFeed>("WebcamFeed", API.current.webcamFeedPath);
|
||||
fetch<FbosConfig>("FbosConfig", API.current.fbosConfigPath);
|
||||
fetch<WebAppConfig>("WebAppConfig", API.current.webAppConfigPath);
|
||||
fetch<FarmEvent[]>("FarmEvent", API.current.farmEventsPath);
|
||||
fetch<Image[]>("Image", API.current.imagesPath);
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { FBSelect, FBSelectProps } from "../new_fb_select";
|
||||
|
||||
describe("<FBSelect />", () => {
|
||||
const fakeProps = (): FBSelectProps => {
|
||||
return {
|
||||
selectedItem: undefined,
|
||||
onChange: jest.fn(),
|
||||
list: [{ value: "item", label: "Item" }]
|
||||
};
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const p = fakeProps();
|
||||
const wrapper = mount(<FBSelect {...p} />);
|
||||
expect(wrapper.text()).toEqual("None");
|
||||
});
|
||||
|
||||
it("renders item", () => {
|
||||
const p = fakeProps();
|
||||
p.selectedItem = { value: "item", label: "Item" };
|
||||
const wrapper = mount(<FBSelect {...p} />);
|
||||
expect(wrapper.text()).toEqual("Item");
|
||||
});
|
||||
|
||||
it("allows empty", () => {
|
||||
const p = fakeProps();
|
||||
p.allowEmpty = true;
|
||||
const wrapper = shallow(<FBSelect {...p} />);
|
||||
// tslint:disable-next-line:no-any
|
||||
expect((wrapper.find("FilterSearch").props() as any).items)
|
||||
.toEqual([
|
||||
{ label: "Item", value: "item" },
|
||||
{ label: "None", value: "" }]);
|
||||
});
|
||||
|
||||
it("doesn't allow empty", () => {
|
||||
const wrapper = shallow(<FBSelect {...fakeProps() } />);
|
||||
// tslint:disable-next-line:no-any
|
||||
expect((wrapper.find("FilterSearch").props() as any).items)
|
||||
.toEqual([{ label: "Item", value: "item" }]);
|
||||
});
|
||||
|
||||
it("has extra class", () => {
|
||||
const p = fakeProps();
|
||||
p.extraClass = "extra";
|
||||
const wrapper = mount(<FBSelect {...p} />);
|
||||
expect(wrapper.find("div").first().hasClass("extra")).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -14,6 +14,8 @@ export interface FBSelectProps {
|
|||
allowEmpty?: boolean;
|
||||
/** Text shown before user selection. */
|
||||
placeholder?: string | undefined;
|
||||
/** Extra class names to add. */
|
||||
extraClass?: string;
|
||||
}
|
||||
|
||||
export class FBSelect extends React.Component<FBSelectProps, {}> {
|
||||
|
@ -32,7 +34,8 @@ export class FBSelect extends React.Component<FBSelectProps, {}> {
|
|||
}
|
||||
|
||||
render() {
|
||||
return <div className="filter-search">
|
||||
const { extraClass } = this.props;
|
||||
return <div className={`filter-search ${extraClass ? extraClass : ""}`}>
|
||||
<FilterSearch
|
||||
selectedItem={this.item}
|
||||
items={this.list}
|
||||
|
|
Loading…
Reference in New Issue