settings refactoring
parent
11f349ac89
commit
b1c2b36a37
|
@ -26,11 +26,11 @@ import { getFirmwareConfig, getFbosConfig } from "./resources/getters";
|
||||||
import { intersection } from "lodash";
|
import { intersection } from "lodash";
|
||||||
import { t } from "./i18next_wrapper";
|
import { t } from "./i18next_wrapper";
|
||||||
import { ResourceIndex } from "./resources/interfaces";
|
import { ResourceIndex } from "./resources/interfaces";
|
||||||
import { isBotOnline } from "./devices/must_be_online";
|
import { isBotOnlineFromState } from "./devices/must_be_online";
|
||||||
import { getStatus } from "./connectivity/reducer_support";
|
|
||||||
import { getAllAlerts } from "./messages/state_to_props";
|
import { getAllAlerts } from "./messages/state_to_props";
|
||||||
import { PingDictionary } from "./devices/connectivity/qos";
|
import { PingDictionary } from "./devices/connectivity/qos";
|
||||||
import { getEnv, getShouldDisplayFn } from "./farmware/state_to_props";
|
import { getEnv, getShouldDisplayFn } from "./farmware/state_to_props";
|
||||||
|
import { filterAlerts } from "./messages/alerts";
|
||||||
|
|
||||||
/** For the logger module */
|
/** For the logger module */
|
||||||
init();
|
init();
|
||||||
|
@ -81,7 +81,7 @@ export function mapStateToProps(props: Everything): AppProps {
|
||||||
tour: props.resources.consumers.help.currentTour,
|
tour: props.resources.consumers.help.currentTour,
|
||||||
resources: props.resources.index,
|
resources: props.resources.index,
|
||||||
autoSync: !!(fbosConfig && fbosConfig.auto_sync),
|
autoSync: !!(fbosConfig && fbosConfig.auto_sync),
|
||||||
alertCount: getAllAlerts(props.resources).length,
|
alertCount: getAllAlerts(props.resources).filter(filterAlerts).length,
|
||||||
pings: props.bot.connectivity.pings,
|
pings: props.bot.connectivity.pings,
|
||||||
env,
|
env,
|
||||||
};
|
};
|
||||||
|
@ -124,8 +124,6 @@ export class RawApp extends React.Component<AppProps, {}> {
|
||||||
const syncLoaded = this.isLoaded;
|
const syncLoaded = this.isLoaded;
|
||||||
const currentPage = getPathArray()[2];
|
const currentPage = getPathArray()[2];
|
||||||
const { location_data, mcu_params } = this.props.bot.hardware;
|
const { location_data, mcu_params } = this.props.bot.hardware;
|
||||||
const { sync_status } = this.props.bot.hardware.informational_settings;
|
|
||||||
const bot2mqtt = this.props.bot.connectivity.uptime["bot.mqtt"];
|
|
||||||
return <div className="app">
|
return <div className="app">
|
||||||
{!syncLoaded && <LoadingPlant animate={this.props.animate} />}
|
{!syncLoaded && <LoadingPlant animate={this.props.animate} />}
|
||||||
<HotKeys dispatch={this.props.dispatch} />
|
<HotKeys dispatch={this.props.dispatch} />
|
||||||
|
@ -151,7 +149,7 @@ export class RawApp extends React.Component<AppProps, {}> {
|
||||||
firmwareSettings={this.props.firmwareConfig || mcu_params}
|
firmwareSettings={this.props.firmwareConfig || mcu_params}
|
||||||
xySwap={this.props.xySwap}
|
xySwap={this.props.xySwap}
|
||||||
arduinoBusy={!!this.props.bot.hardware.informational_settings.busy}
|
arduinoBusy={!!this.props.bot.hardware.informational_settings.busy}
|
||||||
botOnline={isBotOnline(sync_status, getStatus(bot2mqtt))}
|
botOnline={isBotOnlineFromState(this.props.bot)}
|
||||||
env={this.props.env}
|
env={this.props.env}
|
||||||
stepSize={this.props.bot.stepSize} />}
|
stepSize={this.props.bot.stepSize} />}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -3,8 +3,8 @@ jest.mock("axios", () => ({
|
||||||
response: { use: jest.fn() },
|
response: { use: jest.fn() },
|
||||||
request: { use: jest.fn() }
|
request: { use: jest.fn() }
|
||||||
},
|
},
|
||||||
post: jest.fn(() => { return Promise.resolve({ data: { foo: "bar" } }); }),
|
post: jest.fn(() => Promise.resolve({ data: { foo: "bar" } })),
|
||||||
get: jest.fn(() => { return Promise.resolve({ data: { foo: "bar" } }); }),
|
get: jest.fn(() => Promise.resolve({ data: { foo: "bar" } })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../../api/api", () => ({
|
jest.mock("../../api/api", () => ({
|
||||||
|
@ -22,6 +22,7 @@ jest.mock("../../devices/actions", () => ({
|
||||||
fetchReleases: jest.fn(),
|
fetchReleases: jest.fn(),
|
||||||
fetchLatestGHBetaRelease: jest.fn(),
|
fetchLatestGHBetaRelease: jest.fn(),
|
||||||
fetchMinOsFeatureData: jest.fn(),
|
fetchMinOsFeatureData: jest.fn(),
|
||||||
|
fetchOsReleaseNotes: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { didLogin } from "../actions";
|
import { didLogin } from "../actions";
|
||||||
|
|
|
@ -2,6 +2,7 @@ import axios from "axios";
|
||||||
import {
|
import {
|
||||||
fetchReleases, fetchMinOsFeatureData,
|
fetchReleases, fetchMinOsFeatureData,
|
||||||
fetchLatestGHBetaRelease,
|
fetchLatestGHBetaRelease,
|
||||||
|
fetchOsReleaseNotes,
|
||||||
} from "../devices/actions";
|
} from "../devices/actions";
|
||||||
import { AuthState } from "./interfaces";
|
import { AuthState } from "./interfaces";
|
||||||
import { ReduxAction } from "../redux/interfaces";
|
import { ReduxAction } from "../redux/interfaces";
|
||||||
|
@ -16,7 +17,6 @@ import { Actions } from "../constants";
|
||||||
import { connectDevice } from "../connectivity/connect_device";
|
import { connectDevice } from "../connectivity/connect_device";
|
||||||
import { getFirstPartyFarmwareList } from "../farmware/actions";
|
import { getFirstPartyFarmwareList } from "../farmware/actions";
|
||||||
import { readOnlyInterceptor } from "../read_only_mode";
|
import { readOnlyInterceptor } from "../read_only_mode";
|
||||||
import { ExternalUrl } from "../external_urls";
|
|
||||||
|
|
||||||
export function didLogin(authState: AuthState, dispatch: Function) {
|
export function didLogin(authState: AuthState, dispatch: Function) {
|
||||||
API.setBaseUrl(authState.token.unencoded.iss);
|
API.setBaseUrl(authState.token.unencoded.iss);
|
||||||
|
@ -25,7 +25,8 @@ export function didLogin(authState: AuthState, dispatch: Function) {
|
||||||
beta_os_update_server && beta_os_update_server != "NOT_SET" &&
|
beta_os_update_server && beta_os_update_server != "NOT_SET" &&
|
||||||
dispatch(fetchLatestGHBetaRelease(beta_os_update_server));
|
dispatch(fetchLatestGHBetaRelease(beta_os_update_server));
|
||||||
dispatch(getFirstPartyFarmwareList());
|
dispatch(getFirstPartyFarmwareList());
|
||||||
dispatch(fetchMinOsFeatureData(ExternalUrl.featureMinVersions));
|
dispatch(fetchMinOsFeatureData());
|
||||||
|
dispatch(fetchOsReleaseNotes());
|
||||||
dispatch(setToken(authState));
|
dispatch(setToken(authState));
|
||||||
Sync.fetchSyncData(dispatch);
|
Sync.fetchSyncData(dispatch);
|
||||||
dispatch(connectDevice(authState));
|
dispatch(connectDevice(authState));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ConnectionStatus } from "./interfaces";
|
import { ConnectionStatus } from "./interfaces";
|
||||||
|
|
||||||
export function getStatus(cs: ConnectionStatus | undefined): "up" | "down" {
|
export function getStatus(cs: ConnectionStatus | undefined): "up" | "down" {
|
||||||
return (cs && cs.state) || "down";
|
return cs?.state || "down";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1127,6 +1127,8 @@ export enum Actions {
|
||||||
FETCH_BETA_OS_UPDATE_INFO_ERROR = "FETCH_BETA_OS_UPDATE_INFO_ERROR",
|
FETCH_BETA_OS_UPDATE_INFO_ERROR = "FETCH_BETA_OS_UPDATE_INFO_ERROR",
|
||||||
FETCH_MIN_OS_FEATURE_INFO_OK = "FETCH_MIN_OS_FEATURE_INFO_OK",
|
FETCH_MIN_OS_FEATURE_INFO_OK = "FETCH_MIN_OS_FEATURE_INFO_OK",
|
||||||
FETCH_MIN_OS_FEATURE_INFO_ERROR = "FETCH_MIN_OS_FEATURE_INFO_ERROR",
|
FETCH_MIN_OS_FEATURE_INFO_ERROR = "FETCH_MIN_OS_FEATURE_INFO_ERROR",
|
||||||
|
FETCH_OS_RELEASE_NOTES_OK = "FETCH_OS_RELEASE_NOTES_OK",
|
||||||
|
FETCH_OS_RELEASE_NOTES_ERROR = "FETCH_OS_RELEASE_NOTES_ERROR",
|
||||||
INVERT_JOG_BUTTON = "INVERT_JOG_BUTTON",
|
INVERT_JOG_BUTTON = "INVERT_JOG_BUTTON",
|
||||||
DISPLAY_ENCODER_DATA = "DISPLAY_ENCODER_DATA",
|
DISPLAY_ENCODER_DATA = "DISPLAY_ENCODER_DATA",
|
||||||
STASH_STATUS = "STASH_STATUS",
|
STASH_STATUS = "STASH_STATUS",
|
||||||
|
|
|
@ -18,10 +18,9 @@ describe("<Controls />", () => {
|
||||||
feeds: [fakeWebcamFeed()],
|
feeds: [fakeWebcamFeed()],
|
||||||
peripherals: [fakePeripheral()],
|
peripherals: [fakePeripheral()],
|
||||||
sensors: [fakeSensor()],
|
sensors: [fakeSensor()],
|
||||||
botToMqttStatus: "up",
|
|
||||||
firmwareSettings: bot.hardware.mcu_params,
|
firmwareSettings: bot.hardware.mcu_params,
|
||||||
shouldDisplay: () => true,
|
shouldDisplay: () => true,
|
||||||
getWebAppConfigVal: jest.fn((key) => (mockConfig[key])),
|
getWebAppConfigVal: jest.fn(key => mockConfig[key]),
|
||||||
sensorReadings: [],
|
sensorReadings: [],
|
||||||
timeSettings: fakeTimeSettings(),
|
timeSettings: fakeTimeSettings(),
|
||||||
env: {},
|
env: {},
|
||||||
|
@ -65,6 +64,17 @@ describe("<Controls />", () => {
|
||||||
.map(string => expect(txt).not.toContain(string));
|
.map(string => expect(txt).not.toContain(string));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("hides sensors widget based on model", () => {
|
||||||
|
mockConfig.hide_sensors = false;
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = "express_k10";
|
||||||
|
const wrapper = mount(<Controls {...p} />);
|
||||||
|
const txt = wrapper.text().toLowerCase();
|
||||||
|
["move", "peripherals"]
|
||||||
|
.map(string => expect(txt).toContain(string));
|
||||||
|
["sensors"].map(string => expect(txt).not.toContain(string));
|
||||||
|
});
|
||||||
|
|
||||||
it("doesn't show sensor readings widget", () => {
|
it("doesn't show sensor readings widget", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
mockConfig.hide_sensors = true;
|
mockConfig.hide_sensors = true;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Props } from "./interfaces";
|
||||||
import { Move } from "./move/move";
|
import { Move } from "./move/move";
|
||||||
import { BooleanSetting } from "../session_keys";
|
import { BooleanSetting } from "../session_keys";
|
||||||
import { SensorReadings } from "./sensor_readings/sensor_readings";
|
import { SensorReadings } from "./sensor_readings/sensor_readings";
|
||||||
import { isBotOnline } from "../devices/must_be_online";
|
import { isBotOnlineFromState } from "../devices/must_be_online";
|
||||||
import { hasSensors } from "../devices/components/firmware_hardware_support";
|
import { hasSensors } from "../devices/components/firmware_hardware_support";
|
||||||
|
|
||||||
/** Controls page. */
|
/** Controls page. */
|
||||||
|
@ -19,9 +19,7 @@ export class RawControls extends React.Component<Props, {}> {
|
||||||
}
|
}
|
||||||
|
|
||||||
get botOnline() {
|
get botOnline() {
|
||||||
return isBotOnline(
|
return isBotOnlineFromState(this.props.bot);
|
||||||
this.props.bot.hardware.informational_settings.sync_status,
|
|
||||||
this.props.botToMqttStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get hideSensors() {
|
get hideSensors() {
|
||||||
|
@ -34,7 +32,6 @@ export class RawControls extends React.Component<Props, {}> {
|
||||||
env={this.props.env}
|
env={this.props.env}
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
arduinoBusy={this.arduinoBusy}
|
arduinoBusy={this.arduinoBusy}
|
||||||
botToMqttStatus={this.props.botToMqttStatus}
|
|
||||||
firmwareSettings={this.props.firmwareSettings}
|
firmwareSettings={this.props.firmwareSettings}
|
||||||
firmwareHardware={this.props.firmwareHardware}
|
firmwareHardware={this.props.firmwareHardware}
|
||||||
getWebAppConfigVal={this.props.getWebAppConfigVal} />
|
getWebAppConfigVal={this.props.getWebAppConfigVal} />
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
TaggedSensor,
|
TaggedSensor,
|
||||||
TaggedSensorReading,
|
TaggedSensorReading,
|
||||||
} from "farmbot";
|
} from "farmbot";
|
||||||
import { NetworkState } from "../connectivity/interfaces";
|
|
||||||
import { GetWebAppConfigValue } from "../config_storage/actions";
|
import { GetWebAppConfigValue } from "../config_storage/actions";
|
||||||
import { TimeSettings } from "../interfaces";
|
import { TimeSettings } from "../interfaces";
|
||||||
|
|
||||||
|
@ -18,7 +17,6 @@ export interface Props {
|
||||||
feeds: TaggedWebcamFeed[];
|
feeds: TaggedWebcamFeed[];
|
||||||
peripherals: TaggedPeripheral[];
|
peripherals: TaggedPeripheral[];
|
||||||
sensors: TaggedSensor[];
|
sensors: TaggedSensor[];
|
||||||
botToMqttStatus: NetworkState;
|
|
||||||
firmwareSettings: McuParams;
|
firmwareSettings: McuParams;
|
||||||
shouldDisplay: ShouldDisplay;
|
shouldDisplay: ShouldDisplay;
|
||||||
getWebAppConfigVal: GetWebAppConfigValue;
|
getWebAppConfigVal: GetWebAppConfigValue;
|
||||||
|
@ -60,4 +58,5 @@ export interface ToggleButtonProps {
|
||||||
dim?: boolean;
|
dim?: boolean;
|
||||||
grayscale?: boolean;
|
grayscale?: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ describe("<Move />", () => {
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
bot: bot,
|
bot: bot,
|
||||||
arduinoBusy: false,
|
arduinoBusy: false,
|
||||||
botToMqttStatus: "up",
|
|
||||||
firmwareSettings: bot.hardware.mcu_params,
|
firmwareSettings: bot.hardware.mcu_params,
|
||||||
getWebAppConfigVal: jest.fn((key) => (mockConfig[key])),
|
getWebAppConfigVal: jest.fn((key) => (mockConfig[key])),
|
||||||
env: {},
|
env: {},
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { BotPosition, BotState, UserEnv } from "../../devices/interfaces";
|
import { BotPosition, BotState, UserEnv } from "../../devices/interfaces";
|
||||||
import { McuParams, Xyz, FirmwareHardware } from "farmbot";
|
import { McuParams, Xyz, FirmwareHardware } from "farmbot";
|
||||||
import { NetworkState } from "../../connectivity/interfaces";
|
|
||||||
import { GetWebAppConfigValue } from "../../config_storage/actions";
|
import { GetWebAppConfigValue } from "../../config_storage/actions";
|
||||||
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||||
|
|
||||||
|
@ -11,7 +10,6 @@ export interface MoveProps {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
bot: BotState;
|
bot: BotState;
|
||||||
arduinoBusy: boolean;
|
arduinoBusy: boolean;
|
||||||
botToMqttStatus: NetworkState;
|
|
||||||
firmwareSettings: McuParams;
|
firmwareSettings: McuParams;
|
||||||
getWebAppConfigVal: GetWebAppConfigValue;
|
getWebAppConfigVal: GetWebAppConfigValue;
|
||||||
env: UserEnv;
|
env: UserEnv;
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { MotorPositionPlot } from "./motor_position_plot";
|
||||||
import { Popover, Position } from "@blueprintjs/core";
|
import { Popover, Position } from "@blueprintjs/core";
|
||||||
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
|
import { getStatus } from "../../connectivity/reducer_support";
|
||||||
|
|
||||||
export class Move extends React.Component<MoveProps, {}> {
|
export class Move extends React.Component<MoveProps, {}> {
|
||||||
|
|
||||||
|
@ -23,7 +24,8 @@ export class Move extends React.Component<MoveProps, {}> {
|
||||||
!!this.props.getWebAppConfigVal(BooleanSetting[key]);
|
!!this.props.getWebAppConfigVal(BooleanSetting[key]);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { location_data, informational_settings } = this.props.bot.hardware;
|
const { bot } = this.props;
|
||||||
|
const { location_data, informational_settings } = bot.hardware;
|
||||||
const locationData = validBotLocationData(location_data);
|
const locationData = validBotLocationData(location_data);
|
||||||
return <Widget className="move-widget">
|
return <Widget className="move-widget">
|
||||||
<WidgetHeader
|
<WidgetHeader
|
||||||
|
@ -40,11 +42,11 @@ export class Move extends React.Component<MoveProps, {}> {
|
||||||
<WidgetBody>
|
<WidgetBody>
|
||||||
<MustBeOnline
|
<MustBeOnline
|
||||||
lockOpen={process.env.NODE_ENV !== "production"}
|
lockOpen={process.env.NODE_ENV !== "production"}
|
||||||
networkState={this.props.botToMqttStatus}
|
networkState={getStatus(bot.connectivity.uptime["bot.mqtt"])}
|
||||||
syncStatus={informational_settings.sync_status}>
|
syncStatus={informational_settings.sync_status}>
|
||||||
<JogControlsGroup
|
<JogControlsGroup
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
stepSize={this.props.bot.stepSize}
|
stepSize={bot.stepSize}
|
||||||
botPosition={locationData.position}
|
botPosition={locationData.position}
|
||||||
getValue={this.getValue}
|
getValue={this.getValue}
|
||||||
arduinoBusy={this.props.arduinoBusy}
|
arduinoBusy={this.props.arduinoBusy}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { validFwConfig, validFbosConfig } from "../util";
|
||||||
import { getWebAppConfigValue } from "../config_storage/actions";
|
import { getWebAppConfigValue } from "../config_storage/actions";
|
||||||
import { getFirmwareConfig, getFbosConfig } from "../resources/getters";
|
import { getFirmwareConfig, getFbosConfig } from "../resources/getters";
|
||||||
import { uniq } from "lodash";
|
import { uniq } from "lodash";
|
||||||
import { getStatus } from "../connectivity/reducer_support";
|
|
||||||
import { getEnv, getShouldDisplayFn } from "../farmware/state_to_props";
|
import { getEnv, getShouldDisplayFn } from "../farmware/state_to_props";
|
||||||
import { sourceFbosConfigValue } from "../devices/components/source_config_value";
|
import { sourceFbosConfigValue } from "../devices/components/source_config_value";
|
||||||
import { isFwHardwareValue } from "../devices/components/firmware_hardware_support";
|
import { isFwHardwareValue } from "../devices/components/firmware_hardware_support";
|
||||||
|
@ -35,7 +34,6 @@ export function mapStateToProps(props: Everything): Props {
|
||||||
bot: props.bot,
|
bot: props.bot,
|
||||||
peripherals: uniq(selectAllPeripherals(props.resources.index)),
|
peripherals: uniq(selectAllPeripherals(props.resources.index)),
|
||||||
sensors: uniq(selectAllSensors(props.resources.index)),
|
sensors: uniq(selectAllSensors(props.resources.index)),
|
||||||
botToMqttStatus: getStatus(props.bot.connectivity.uptime["bot.mqtt"]),
|
|
||||||
firmwareSettings: fwConfig || mcu_params,
|
firmwareSettings: fwConfig || mcu_params,
|
||||||
getWebAppConfigVal: getWebAppConfigValue(() => props),
|
getWebAppConfigVal: getWebAppConfigValue(() => props),
|
||||||
shouldDisplay,
|
shouldDisplay,
|
||||||
|
|
|
@ -40,12 +40,16 @@ export class ToggleButton extends React.Component<ToggleButtonProps, {}> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const addCss = (this.props.dim ? " dim" : "")
|
const allCss = [
|
||||||
+ (this.props.grayscale ? " grayscale" : "");
|
this.css(),
|
||||||
|
this.props.className,
|
||||||
|
this.props.dim ? "dim" : "",
|
||||||
|
this.props.grayscale ? "grayscale" : "",
|
||||||
|
].join(" ");
|
||||||
const cb = () => !this.props.disabled && this.props.toggleAction();
|
const cb = () => !this.props.disabled && this.props.toggleAction();
|
||||||
return <button
|
return <button
|
||||||
disabled={!!this.props.disabled}
|
disabled={!!this.props.disabled}
|
||||||
className={this.css() + addCss}
|
className={allCss}
|
||||||
title={this.props.title || ""}
|
title={this.props.title || ""}
|
||||||
onClick={cb}>
|
onClick={cb}>
|
||||||
{t(this.caption())}
|
{t(this.caption())}
|
||||||
|
|
|
@ -361,6 +361,17 @@ a {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.centered-button-div {
|
||||||
|
.fb-button {
|
||||||
|
float: none !important;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.hardware-widget,
|
.hardware-widget,
|
||||||
.device-widget {
|
.device-widget {
|
||||||
.row {
|
.row {
|
||||||
|
@ -369,15 +380,11 @@ a {
|
||||||
label {
|
label {
|
||||||
padding: 0.5rem 0 0 0.5rem;
|
padding: 0.5rem 0 0 0.5rem;
|
||||||
}
|
}
|
||||||
.centered-button-div {
|
h4 {
|
||||||
.fb-button {
|
margin-bottom: 0;
|
||||||
float: none !important;
|
}
|
||||||
}
|
p {
|
||||||
label {
|
margin-bottom: 1rem !important;
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
text-align: center;
|
|
||||||
padding: 0.25rem;
|
|
||||||
}
|
}
|
||||||
.widget-body-tooltips {
|
.widget-body-tooltips {
|
||||||
.bp3-popover-wrapper {
|
.bp3-popover-wrapper {
|
||||||
|
@ -1649,11 +1656,9 @@ textarea:focus {
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
.bp3-collapse {
|
||||||
|
padding-top: 1rem;
|
||||||
.highlight,
|
}
|
||||||
.unhighlight {
|
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight {
|
.highlight {
|
||||||
|
|
|
@ -407,14 +407,15 @@ describe("fetchLatestGHBetaRelease()", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("fetchMinOsFeatureData()", () => {
|
describe("fetchMinOsFeatureData()", () => {
|
||||||
|
const EXPECTED_URL = expect.stringContaining("FEATURE_MIN_VERSIONS.json");
|
||||||
afterEach(() =>
|
afterEach(() =>
|
||||||
jest.restoreAllMocks());
|
jest.restoreAllMocks());
|
||||||
|
|
||||||
it("fetches min OS feature data: empty", async () => {
|
it("fetches min OS feature data: empty", async () => {
|
||||||
mockGetRelease = Promise.resolve({ data: {} });
|
mockGetRelease = Promise.resolve({ data: {} });
|
||||||
const dispatch = jest.fn();
|
const dispatch = jest.fn();
|
||||||
await actions.fetchMinOsFeatureData("url")(dispatch);
|
await actions.fetchMinOsFeatureData()(dispatch);
|
||||||
expect(axios.get).toHaveBeenCalledWith("url");
|
expect(axios.get).toHaveBeenCalledWith(EXPECTED_URL);
|
||||||
expect(dispatch).toHaveBeenCalledWith({
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
payload: {},
|
payload: {},
|
||||||
type: Actions.FETCH_MIN_OS_FEATURE_INFO_OK
|
type: Actions.FETCH_MIN_OS_FEATURE_INFO_OK
|
||||||
|
@ -426,8 +427,8 @@ describe("fetchMinOsFeatureData()", () => {
|
||||||
data: { "a_feature": "1.0.0", "b_feature": "2.0.0" }
|
data: { "a_feature": "1.0.0", "b_feature": "2.0.0" }
|
||||||
});
|
});
|
||||||
const dispatch = jest.fn();
|
const dispatch = jest.fn();
|
||||||
await actions.fetchMinOsFeatureData("url")(dispatch);
|
await actions.fetchMinOsFeatureData()(dispatch);
|
||||||
expect(axios.get).toHaveBeenCalledWith("url");
|
expect(axios.get).toHaveBeenCalledWith(EXPECTED_URL);
|
||||||
expect(dispatch).toHaveBeenCalledWith({
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
payload: { a_feature: "1.0.0", b_feature: "2.0.0" },
|
payload: { a_feature: "1.0.0", b_feature: "2.0.0" },
|
||||||
type: Actions.FETCH_MIN_OS_FEATURE_INFO_OK
|
type: Actions.FETCH_MIN_OS_FEATURE_INFO_OK
|
||||||
|
@ -438,8 +439,8 @@ describe("fetchMinOsFeatureData()", () => {
|
||||||
mockGetRelease = Promise.resolve({ data: "bad" });
|
mockGetRelease = Promise.resolve({ data: "bad" });
|
||||||
const dispatch = jest.fn();
|
const dispatch = jest.fn();
|
||||||
const mockConsole = jest.spyOn(console, "log").mockImplementation(() => { });
|
const mockConsole = jest.spyOn(console, "log").mockImplementation(() => { });
|
||||||
await actions.fetchMinOsFeatureData("url")(dispatch);
|
await actions.fetchMinOsFeatureData()(dispatch);
|
||||||
expect(axios.get).toHaveBeenCalledWith("url");
|
expect(axios.get).toHaveBeenCalledWith(EXPECTED_URL);
|
||||||
expect(dispatch).not.toHaveBeenCalled();
|
expect(dispatch).not.toHaveBeenCalled();
|
||||||
expect(mockConsole).toHaveBeenCalledWith(
|
expect(mockConsole).toHaveBeenCalledWith(
|
||||||
expect.stringContaining("\"bad\""));
|
expect.stringContaining("\"bad\""));
|
||||||
|
@ -449,8 +450,8 @@ describe("fetchMinOsFeatureData()", () => {
|
||||||
mockGetRelease = Promise.resolve({ data: { a: "0", b: 0 } });
|
mockGetRelease = Promise.resolve({ data: { a: "0", b: 0 } });
|
||||||
const dispatch = jest.fn();
|
const dispatch = jest.fn();
|
||||||
const mockConsole = jest.spyOn(console, "log").mockImplementation(() => { });
|
const mockConsole = jest.spyOn(console, "log").mockImplementation(() => { });
|
||||||
await actions.fetchMinOsFeatureData("url")(dispatch);
|
await actions.fetchMinOsFeatureData()(dispatch);
|
||||||
expect(axios.get).toHaveBeenCalledWith("url");
|
expect(axios.get).toHaveBeenCalledWith(EXPECTED_URL);
|
||||||
expect(dispatch).not.toHaveBeenCalled();
|
expect(dispatch).not.toHaveBeenCalled();
|
||||||
expect(mockConsole).toHaveBeenCalledWith(
|
expect(mockConsole).toHaveBeenCalledWith(
|
||||||
expect.stringContaining("{\"a\":\"0\",\"b\":0}"));
|
expect.stringContaining("{\"a\":\"0\",\"b\":0}"));
|
||||||
|
@ -459,8 +460,8 @@ describe("fetchMinOsFeatureData()", () => {
|
||||||
it("fails to fetch min OS feature data", async () => {
|
it("fails to fetch min OS feature data", async () => {
|
||||||
mockGetRelease = Promise.reject("error");
|
mockGetRelease = Promise.reject("error");
|
||||||
const dispatch = jest.fn();
|
const dispatch = jest.fn();
|
||||||
await actions.fetchMinOsFeatureData("url")(dispatch);
|
await actions.fetchMinOsFeatureData()(dispatch);
|
||||||
await expect(axios.get).toHaveBeenCalledWith("url");
|
await expect(axios.get).toHaveBeenCalledWith(EXPECTED_URL);
|
||||||
expect(error).not.toHaveBeenCalled();
|
expect(error).not.toHaveBeenCalled();
|
||||||
expect(dispatch).toHaveBeenCalledWith({
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
payload: "error",
|
payload: "error",
|
||||||
|
@ -469,6 +470,34 @@ describe("fetchMinOsFeatureData()", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("fetchOsReleaseNotes()", () => {
|
||||||
|
const EXPECTED_URL = expect.stringContaining("RELEASE_NOTES.md");
|
||||||
|
|
||||||
|
it("fetches OS release notes", async () => {
|
||||||
|
mockGetRelease = Promise.resolve({
|
||||||
|
data: "intro\n\n# v6\n\n* note"
|
||||||
|
});
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
await actions.fetchOsReleaseNotes()(dispatch);
|
||||||
|
await expect(axios.get).toHaveBeenCalledWith(EXPECTED_URL);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
payload: "intro\n\n# v6\n\n* note",
|
||||||
|
type: Actions.FETCH_OS_RELEASE_NOTES_OK
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("errors while fetching OS release notes", async () => {
|
||||||
|
mockGetRelease = Promise.reject({ error: "" });
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
await actions.fetchOsReleaseNotes()(dispatch);
|
||||||
|
await expect(axios.get).toHaveBeenCalledWith(EXPECTED_URL);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({
|
||||||
|
payload: { error: "" },
|
||||||
|
type: Actions.FETCH_OS_RELEASE_NOTES_ERROR
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("updateConfig()", () => {
|
describe("updateConfig()", () => {
|
||||||
it("updates config: FbosConfig", () => {
|
it("updates config: FbosConfig", () => {
|
||||||
const state = fakeState();
|
const state = fakeState();
|
||||||
|
|
|
@ -12,15 +12,9 @@ import {
|
||||||
import { FarmbotOsSettings } from "../components/farmbot_os_settings";
|
import { FarmbotOsSettings } from "../components/farmbot_os_settings";
|
||||||
import { fakeTimeSettings } from "../../__test_support__/fake_time_settings";
|
import { fakeTimeSettings } from "../../__test_support__/fake_time_settings";
|
||||||
import { HardwareSettings } from "../components/hardware_settings";
|
import { HardwareSettings } from "../components/hardware_settings";
|
||||||
import { DeepPartial } from "redux";
|
|
||||||
import { save } from "../../api/crud";
|
|
||||||
import { fakeWebAppConfig } from "../../__test_support__/fake_state/resources";
|
|
||||||
|
|
||||||
describe("<Devices/>", () => {
|
describe("<Devices/>", () => {
|
||||||
const fakeProps = (): Props => ({
|
const fakeProps = (): Props => ({
|
||||||
userToApi: undefined,
|
|
||||||
userToMqtt: undefined,
|
|
||||||
botToMqtt: undefined,
|
|
||||||
auth: auth,
|
auth: auth,
|
||||||
bot: bot,
|
bot: bot,
|
||||||
deviceAccount: fakeDevice(),
|
deviceAccount: fakeDevice(),
|
||||||
|
@ -31,12 +25,10 @@ describe("<Devices/>", () => {
|
||||||
sourceFwConfig: jest.fn(),
|
sourceFwConfig: jest.fn(),
|
||||||
shouldDisplay: jest.fn(),
|
shouldDisplay: jest.fn(),
|
||||||
firmwareConfig: undefined,
|
firmwareConfig: undefined,
|
||||||
isValidFbosConfig: false,
|
|
||||||
env: {},
|
env: {},
|
||||||
saveFarmwareEnv: jest.fn(),
|
saveFarmwareEnv: jest.fn(),
|
||||||
timeSettings: fakeTimeSettings(),
|
timeSettings: fakeTimeSettings(),
|
||||||
alerts: [],
|
alerts: [],
|
||||||
webAppConfig: fakeWebAppConfig()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders relevant panels", () => {
|
it("renders relevant panels", () => {
|
||||||
|
@ -50,14 +42,6 @@ describe("<Devices/>", () => {
|
||||||
expect(() => render(<Devices {...p} />)).toThrow();
|
expect(() => render(<Devices {...p} />)).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has correct connection status", () => {
|
|
||||||
const p = fakeProps();
|
|
||||||
p.botToMqtt = { at: 123, state: "up" };
|
|
||||||
const wrapper = shallow(<Devices {...p} />);
|
|
||||||
expect(wrapper.find(FarmbotOsSettings).props().botToMqttLastSeen)
|
|
||||||
.toEqual(123);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("provides correct firmwareHardware value", () => {
|
it("provides correct firmwareHardware value", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.sourceFbosConfig = () => ({ value: "arduino", consistent: true });
|
p.sourceFbosConfig = () => ({ value: "arduino", consistent: true });
|
||||||
|
@ -65,17 +49,4 @@ describe("<Devices/>", () => {
|
||||||
expect(wrapper.find(HardwareSettings).props().firmwareHardware)
|
expect(wrapper.find(HardwareSettings).props().firmwareHardware)
|
||||||
.toEqual("arduino");
|
.toEqual("arduino");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("triggers a save", () => {
|
|
||||||
type P = FarmbotOsSettings["props"];
|
|
||||||
type DPP = DeepPartial<P>;
|
|
||||||
const props: DPP = {
|
|
||||||
deviceAccount: { uuid: "a.b.c" },
|
|
||||||
dispatch: jest.fn()
|
|
||||||
};
|
|
||||||
const el = new FarmbotOsSettings(props as P);
|
|
||||||
el.updateBot();
|
|
||||||
expect(save)
|
|
||||||
.toHaveBeenCalledWith(props.deviceAccount && props.deviceAccount.uuid);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { defensiveClone } from "../../util";
|
||||||
import { stash } from "../../connectivity/data_consistency";
|
import { stash } from "../../connectivity/data_consistency";
|
||||||
import { incomingStatus } from "../../connectivity/connect_device";
|
import { incomingStatus } from "../../connectivity/connect_device";
|
||||||
import { Vector3, uuid } from "farmbot";
|
import { Vector3, uuid } from "farmbot";
|
||||||
import { values, omit } from "lodash";
|
|
||||||
import { now } from "../connectivity/qos";
|
import { now } from "../connectivity/qos";
|
||||||
|
|
||||||
const statusOf = (state: BotState) => {
|
const statusOf = (state: BotState) => {
|
||||||
|
@ -46,15 +45,28 @@ describe("botReducer", () => {
|
||||||
.toBe(!initialState().controlPanelState.danger_zone);
|
.toBe(!initialState().controlPanelState.danger_zone);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("bulk toggles control panel options", () => {
|
it("bulk toggles firmware control panel options", () => {
|
||||||
const state = botReducer(initialState(), {
|
const state = botReducer(initialState(), {
|
||||||
type: Actions.BULK_TOGGLE_CONTROL_PANEL,
|
type: Actions.BULK_TOGGLE_CONTROL_PANEL,
|
||||||
payload: true
|
payload: { open: true, all: false }
|
||||||
});
|
});
|
||||||
|
|
||||||
const bulkToggable =
|
const bulkToggable =
|
||||||
omit(state.controlPanelState, "power_and_reset");
|
Object.entries(state.controlPanelState).filter(([k, _]) => ![
|
||||||
values(bulkToggable).map(value => {
|
"power_and_reset", "farmbot_os", "farm_designer", "firmware",
|
||||||
|
].includes(k));
|
||||||
|
Object.values(bulkToggable).map(value => {
|
||||||
|
expect(value).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("bulk toggles all control panel options", () => {
|
||||||
|
const state = botReducer(initialState(), {
|
||||||
|
type: Actions.BULK_TOGGLE_CONTROL_PANEL,
|
||||||
|
payload: { open: true, all: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.values(state.controlPanelState).map(value => {
|
||||||
expect(value).toBeTruthy();
|
expect(value).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -83,6 +95,14 @@ describe("botReducer", () => {
|
||||||
expect(r).toEqual({});
|
expect(r).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("fetches OS release notes", () => {
|
||||||
|
const r = botReducer(initialState(), {
|
||||||
|
type: Actions.FETCH_OS_RELEASE_NOTES_OK,
|
||||||
|
payload: "notes"
|
||||||
|
}).osReleaseNotes;
|
||||||
|
expect(r).toEqual("notes");
|
||||||
|
});
|
||||||
|
|
||||||
it("Handles status_v8 info", () => {
|
it("Handles status_v8 info", () => {
|
||||||
const n = () => Math.round(Math.random() * 1000);
|
const n = () => Math.round(Math.random() * 1000);
|
||||||
const position: Vector3 = { x: n(), y: n(), z: n() };
|
const position: Vector3 = { x: n(), y: n(), z: n() };
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
||||||
import { getFirmwareConfig, getFbosConfig } from "../resources/getters";
|
import { getFirmwareConfig, getFbosConfig } from "../resources/getters";
|
||||||
import { isObject, isString, get, noop } from "lodash";
|
import { isObject, isString, get, noop } from "lodash";
|
||||||
import { t } from "../i18next_wrapper";
|
import { t } from "../i18next_wrapper";
|
||||||
|
import { ExternalUrl } from "../external_urls";
|
||||||
|
|
||||||
const ON = 1, OFF = 0;
|
const ON = 1, OFF = 0;
|
||||||
export type ConfigKey = keyof McuParams;
|
export type ConfigKey = keyof McuParams;
|
||||||
|
@ -236,12 +237,11 @@ function validMinOsFeatureLookup(x: MinOsFeatureLookup): boolean {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch and save minimum FBOS version data for UI feature display.
|
* Fetch and save minimum FBOS version data for UI feature display.
|
||||||
* @param url location of data
|
|
||||||
*/
|
*/
|
||||||
export const fetchMinOsFeatureData = (url: string) =>
|
export const fetchMinOsFeatureData = () =>
|
||||||
(dispatch: Function) => {
|
(dispatch: Function) => {
|
||||||
axios
|
axios
|
||||||
.get<MinOsFeatureLookup>(url)
|
.get<MinOsFeatureLookup>(ExternalUrl.featureMinVersions)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
const data = resp.data;
|
const data = resp.data;
|
||||||
if (validMinOsFeatureLookup(data)) {
|
if (validMinOsFeatureLookup(data)) {
|
||||||
|
@ -262,6 +262,27 @@ export const fetchMinOsFeatureData = (url: string) =>
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch and save FBOS release notes.
|
||||||
|
*/
|
||||||
|
export const fetchOsReleaseNotes = () =>
|
||||||
|
(dispatch: Function) => {
|
||||||
|
axios
|
||||||
|
.get<string>(ExternalUrl.osReleaseNotes)
|
||||||
|
.then(resp => {
|
||||||
|
dispatch({
|
||||||
|
type: Actions.FETCH_OS_RELEASE_NOTES_OK,
|
||||||
|
payload: resp.data
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((ferror) => {
|
||||||
|
dispatch({
|
||||||
|
type: Actions.FETCH_OS_RELEASE_NOTES_ERROR,
|
||||||
|
payload: ferror
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles visibility of individual sections in the giant controls panel
|
* Toggles visibility of individual sections in the giant controls panel
|
||||||
* found on the Devices page.
|
* found on the Devices page.
|
||||||
|
@ -271,8 +292,11 @@ export function toggleControlPanel(payload: keyof ControlPanelState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Toggle visibility of all hardware control panel sections. */
|
/** Toggle visibility of all hardware control panel sections. */
|
||||||
export function bulkToggleControlPanel(payload: boolean) {
|
export function bulkToggleControlPanel(open: boolean, all = false) {
|
||||||
return { type: Actions.BULK_TOGGLE_CONTROL_PANEL, payload };
|
return {
|
||||||
|
type: Actions.BULK_TOGGLE_CONTROL_PANEL,
|
||||||
|
payload: { open, all },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Factory reset all firmware settings. */
|
/** Factory reset all firmware settings. */
|
||||||
|
|
|
@ -1,29 +1,23 @@
|
||||||
let mockReleaseNoteResponse = Promise.resolve({ data: "" });
|
|
||||||
jest.mock("axios", () => ({
|
|
||||||
get: jest.fn(() => mockReleaseNoteResponse)
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock("../../../api/crud", () => ({
|
|
||||||
edit: jest.fn(),
|
|
||||||
save: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock("../fbos_settings/boot_sequence_selector", () => ({
|
jest.mock("../fbos_settings/boot_sequence_selector", () => ({
|
||||||
BootSequenceSelector: () => <div />
|
BootSequenceSelector: () => <div />
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let mockDev = false;
|
||||||
|
jest.mock("../../../account/dev/dev_support", () => ({
|
||||||
|
DevSettings: {
|
||||||
|
futureFeaturesEnabled: () => mockDev,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { FarmbotOsSettings } from "../farmbot_os_settings";
|
import { FarmbotOsSettings, FarmBotSettings } from "../farmbot_os_settings";
|
||||||
import { mount, shallow } from "enzyme";
|
import { mount, shallow } from "enzyme";
|
||||||
import { bot } from "../../../__test_support__/fake_state/bot";
|
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||||
import { fakeResource } from "../../../__test_support__/fake_resource";
|
import {
|
||||||
import { FarmbotOsProps } from "../../interfaces";
|
FarmbotOsProps, FarmbotSettingsProps, ControlPanelState,
|
||||||
import axios from "axios";
|
} from "../../interfaces";
|
||||||
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
|
||||||
import { edit } from "../../../api/crud";
|
import { fakeDevice } from "../../../__test_support__/resource_index_builder";
|
||||||
import { fakeWebAppConfig } from "../../../__test_support__/fake_state/resources";
|
|
||||||
import { formEvent } from "../../../__test_support__/fake_html_events";
|
|
||||||
import { Content } from "../../../constants";
|
|
||||||
|
|
||||||
describe("<FarmbotOsSettings />", () => {
|
describe("<FarmbotOsSettings />", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -31,104 +25,59 @@ describe("<FarmbotOsSettings />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const fakeProps = (): FarmbotOsProps => ({
|
const fakeProps = (): FarmbotOsProps => ({
|
||||||
deviceAccount: fakeResource("Device", {
|
deviceAccount: fakeDevice(),
|
||||||
id: 0,
|
|
||||||
name: "",
|
|
||||||
ota_hour: 3,
|
|
||||||
tz_offset_hrs: 0
|
|
||||||
}),
|
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
bot,
|
bot,
|
||||||
alerts: [],
|
alerts: [],
|
||||||
botToMqttLastSeen: 0,
|
|
||||||
botToMqttStatus: "up",
|
|
||||||
sourceFbosConfig: x =>
|
sourceFbosConfig: x =>
|
||||||
({ value: bot.hardware.configuration[x], consistent: true }),
|
({ value: bot.hardware.configuration[x], consistent: true }),
|
||||||
shouldDisplay: jest.fn(() => true),
|
shouldDisplay: jest.fn(() => true),
|
||||||
isValidFbosConfig: false,
|
|
||||||
env: {},
|
env: {},
|
||||||
saveFarmwareEnv: jest.fn(),
|
saveFarmwareEnv: jest.fn(),
|
||||||
timeSettings: fakeTimeSettings(),
|
timeSettings: fakeTimeSettings(),
|
||||||
webAppConfig: fakeWebAppConfig()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders settings", () => {
|
it("renders settings", () => {
|
||||||
const osSettings = mount(<FarmbotOsSettings {...fakeProps()} />);
|
const p = fakeProps();
|
||||||
|
p.bot.controlPanelState.farmbot_os = true;
|
||||||
|
const osSettings = mount(<FarmbotOsSettings {...p} />);
|
||||||
expect(osSettings.find("input").length).toBe(1);
|
expect(osSettings.find("input").length).toBe(1);
|
||||||
expect(osSettings.find("button").length).toBe(7);
|
expect(osSettings.find("button").length).toBe(6);
|
||||||
["name", "time zone", "farmbot os", "camera", "firmware"]
|
["name", "time zone", "farmbot os", "camera"]
|
||||||
.map(string => expect(osSettings.text().toLowerCase()).toContain(string));
|
.map(string => expect(osSettings.text().toLowerCase()).toContain(string));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fetches OS release notes", async () => {
|
it("renders expanded", () => {
|
||||||
mockReleaseNoteResponse = Promise.resolve({
|
mockDev = true;
|
||||||
data: "intro\n\n# v6\n\n* note"
|
|
||||||
});
|
|
||||||
const osSettings = await mount<FarmbotOsSettings>(<FarmbotOsSettings
|
|
||||||
{...fakeProps()} />);
|
|
||||||
await expect(axios.get).toHaveBeenCalledWith(
|
|
||||||
expect.stringContaining("RELEASE_NOTES.md"));
|
|
||||||
expect(osSettings.instance().osReleaseNotes.heading)
|
|
||||||
.toEqual("FarmBot OS v6");
|
|
||||||
expect(osSettings.instance().osReleaseNotes.notes)
|
|
||||||
.toEqual("* note");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("doesn't fetch OS release notes", async () => {
|
|
||||||
mockReleaseNoteResponse = Promise.resolve({ data: "" });
|
|
||||||
const osSettings = await mount<FarmbotOsSettings>(<FarmbotOsSettings
|
|
||||||
{...fakeProps()} />);
|
|
||||||
await expect(axios.get).toHaveBeenCalledWith(
|
|
||||||
expect.stringContaining("RELEASE_NOTES.md"));
|
|
||||||
expect(osSettings.instance().state.allOsReleaseNotes)
|
|
||||||
.toEqual("");
|
|
||||||
expect(osSettings.instance().osReleaseNotes.heading)
|
|
||||||
.toEqual("FarmBot OS v6");
|
|
||||||
expect(osSettings.instance().osReleaseNotes.notes)
|
|
||||||
.toEqual("Could not get release notes.");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("errors while fetching OS release notes", async () => {
|
|
||||||
mockReleaseNoteResponse = Promise.reject({ error: "" });
|
|
||||||
const osSettings = await mount<FarmbotOsSettings>(<FarmbotOsSettings
|
|
||||||
{...fakeProps()} />);
|
|
||||||
await expect(axios.get).toHaveBeenCalledWith(
|
|
||||||
expect.stringContaining("RELEASE_NOTES.md"));
|
|
||||||
expect(osSettings.instance().state.allOsReleaseNotes)
|
|
||||||
.toEqual("");
|
|
||||||
expect(osSettings.instance().osReleaseNotes.heading)
|
|
||||||
.toEqual("FarmBot OS v6");
|
|
||||||
expect(osSettings.instance().osReleaseNotes.notes)
|
|
||||||
.toEqual("Could not get release notes.");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("changes bot name", () => {
|
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
const newName = "new bot name";
|
Object.keys(p.bot.controlPanelState).map((panel: keyof ControlPanelState) => {
|
||||||
const osSettings = shallow(<FarmbotOsSettings {...p} />);
|
p.bot.controlPanelState[panel] = true;
|
||||||
osSettings.find("input")
|
});
|
||||||
.simulate("change", { currentTarget: { value: newName } });
|
const wrapper = mount(<FarmbotOsSettings {...p} />);
|
||||||
expect(edit).toHaveBeenCalledWith(p.deviceAccount, { name: newName });
|
["camera", "name"].map(string =>
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain(string));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("<FarmBotSettings />", () => {
|
||||||
|
const fakeProps = (): FarmbotSettingsProps => ({
|
||||||
|
device: fakeDevice(),
|
||||||
|
dispatch: jest.fn(),
|
||||||
|
bot,
|
||||||
|
alerts: [],
|
||||||
|
botOnline: true,
|
||||||
|
sourceFbosConfig: x =>
|
||||||
|
({ value: bot.hardware.configuration[x], consistent: true }),
|
||||||
|
shouldDisplay: jest.fn(() => true),
|
||||||
|
env: {},
|
||||||
|
saveFarmwareEnv: jest.fn(),
|
||||||
|
timeSettings: fakeTimeSettings(),
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays boot sequence selector", () => {
|
it("displays boot sequence selector", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.shouldDisplay = () => true;
|
p.shouldDisplay = () => true;
|
||||||
const osSettings = shallow(<FarmbotOsSettings {...p} />);
|
const osSettings = shallow(<FarmBotSettings {...p} />);
|
||||||
expect(osSettings.find("BootSequenceSelector").length).toEqual(1);
|
expect(osSettings.find("BootSequenceSelector").length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prevents default form submit action", () => {
|
|
||||||
const osSettings = shallow(<FarmbotOsSettings {...fakeProps()} />);
|
|
||||||
const e = formEvent();
|
|
||||||
osSettings.find("form").simulate("submit", e);
|
|
||||||
expect(e.preventDefault).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("warns about timezone mismatch", () => {
|
|
||||||
const p = fakeProps();
|
|
||||||
p.deviceAccount.body.timezone = "different";
|
|
||||||
const osSettings = mount(<FarmbotOsSettings {...p} />);
|
|
||||||
expect(osSettings.text()).toContain(Content.DIFFERENT_TZ_WARNING);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
|
let mockDev = false;
|
||||||
|
jest.mock("../../../account/dev/dev_support", () => ({
|
||||||
|
DevSettings: {
|
||||||
|
futureFeaturesEnabled: () => mockDev,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { mount, shallow } from "enzyme";
|
import { mount, shallow } from "enzyme";
|
||||||
import { HardwareSettings } from "../hardware_settings";
|
import { HardwareSettings } from "../hardware_settings";
|
||||||
import { HardwareSettingsProps } from "../../interfaces";
|
import { HardwareSettingsProps, ControlPanelState } from "../../interfaces";
|
||||||
import { Actions } from "../../../constants";
|
import { Actions } from "../../../constants";
|
||||||
import { bot } from "../../../__test_support__/fake_state/bot";
|
import { bot } from "../../../__test_support__/fake_state/bot";
|
||||||
import { panelState } from "../../../__test_support__/control_panel_state";
|
import { panelState } from "../../../__test_support__/control_panel_state";
|
||||||
|
@ -19,7 +26,6 @@ describe("<HardwareSettings />", () => {
|
||||||
const fakeProps = (): HardwareSettingsProps => ({
|
const fakeProps = (): HardwareSettingsProps => ({
|
||||||
bot,
|
bot,
|
||||||
controlPanelState: panelState(),
|
controlPanelState: panelState(),
|
||||||
botToMqttStatus: "up",
|
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
sourceFwConfig: x =>
|
sourceFwConfig: x =>
|
||||||
({ value: fakeFirmwareConfig().body[x], consistent: true }),
|
({ value: fakeFirmwareConfig().body[x], consistent: true }),
|
||||||
|
@ -35,12 +41,23 @@ describe("<HardwareSettings />", () => {
|
||||||
expect(wrapper.text().toLowerCase()).toContain(string));
|
expect(wrapper.text().toLowerCase()).toContain(string));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders expanded", () => {
|
||||||
|
mockDev = true;
|
||||||
|
const p = fakeProps();
|
||||||
|
Object.keys(p.controlPanelState).map((panel: keyof ControlPanelState) => {
|
||||||
|
p.controlPanelState[panel] = true;
|
||||||
|
});
|
||||||
|
const wrapper = mount(<HardwareSettings {...p} />);
|
||||||
|
["steps", "mm"].map(string =>
|
||||||
|
expect(wrapper.text().toLowerCase()).toContain(string));
|
||||||
|
});
|
||||||
|
|
||||||
function checkDispatch(
|
function checkDispatch(
|
||||||
buttonElement: string,
|
buttonElement: string,
|
||||||
buttonIndex: number,
|
buttonIndex: number,
|
||||||
buttonText: string,
|
buttonText: string,
|
||||||
type: string,
|
type: string,
|
||||||
payload: boolean | string) {
|
payload: { open: boolean, all: boolean } | string) {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
const wrapper = mount(<HardwareSettings {...p} />);
|
const wrapper = mount(<HardwareSettings {...p} />);
|
||||||
clickButton(wrapper, buttonIndex, buttonText, {
|
clickButton(wrapper, buttonIndex, buttonText, {
|
||||||
|
@ -51,12 +68,12 @@ describe("<HardwareSettings />", () => {
|
||||||
|
|
||||||
it("expands all", () => {
|
it("expands all", () => {
|
||||||
checkDispatch("button", 0, "expand all",
|
checkDispatch("button", 0, "expand all",
|
||||||
Actions.BULK_TOGGLE_CONTROL_PANEL, true);
|
Actions.BULK_TOGGLE_CONTROL_PANEL, { open: true, all: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("collapses all", () => {
|
it("collapses all", () => {
|
||||||
checkDispatch("button", 1, "collapse all",
|
checkDispatch("button", 1, "collapse all",
|
||||||
Actions.BULK_TOGGLE_CONTROL_PANEL, false);
|
Actions.BULK_TOGGLE_CONTROL_PANEL, { open: false, all: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("toggles motor category", () => {
|
it("toggles motor category", () => {
|
||||||
|
|
|
@ -1,18 +1,26 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { LockableButton } from "../lockable_button";
|
import { LockableButton, LockableButtonProps } from "../lockable_button";
|
||||||
import { mount } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
|
|
||||||
describe("<LockableButton/>", () => {
|
describe("<LockableButton />", () => {
|
||||||
it("does not trigger callback when clicked and disabled", () => {
|
const fakeProps = (): LockableButtonProps => ({
|
||||||
const fakeCB = jest.fn();
|
disabled: false,
|
||||||
const btn = mount(<LockableButton disabled={true} onClick={fakeCB} />);
|
onClick: jest.fn(),
|
||||||
btn.simulate("click");
|
|
||||||
expect(fakeCB).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
it("does trigger callback when clicked and enabled", () => {
|
|
||||||
const fakeCB = jest.fn();
|
it("does not trigger callback when clicked and disabled", () => {
|
||||||
const btn = mount(<LockableButton disabled={false} onClick={fakeCB} />);
|
const p = fakeProps();
|
||||||
|
p.disabled = true;
|
||||||
|
const btn = shallow(<LockableButton {...p} />);
|
||||||
btn.simulate("click");
|
btn.simulate("click");
|
||||||
expect(fakeCB).toHaveBeenCalled();
|
expect(p.onClick).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does trigger callback when clicked and enabled", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.disabled = false;
|
||||||
|
const btn = shallow(<LockableButton {...p} />);
|
||||||
|
btn.simulate("click");
|
||||||
|
expect(p.onClick).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,15 +9,20 @@ import { updateMCU } from "../../actions";
|
||||||
import { warning } from "../../../toast/toast";
|
import { warning } from "../../../toast/toast";
|
||||||
|
|
||||||
describe("McuInputBox", () => {
|
describe("McuInputBox", () => {
|
||||||
const fakeProps = (): McuInputBoxProps => {
|
const fakeProps = (): McuInputBoxProps => ({
|
||||||
return {
|
sourceFwConfig: x =>
|
||||||
sourceFwConfig: (x) => {
|
({ value: bot.hardware.mcu_params[x], consistent: true }),
|
||||||
return { value: bot.hardware.mcu_params[x], consistent: true };
|
setting: "encoder_enabled_x",
|
||||||
},
|
dispatch: jest.fn()
|
||||||
setting: "encoder_enabled_x",
|
});
|
||||||
dispatch: jest.fn()
|
|
||||||
};
|
it("renders inconsistency", () => {
|
||||||
};
|
const p = fakeProps();
|
||||||
|
p.sourceFwConfig = x =>
|
||||||
|
({ value: bot.hardware.mcu_params[x], consistent: false });
|
||||||
|
const wrapper = shallow(<McuInputBox {...p} />);
|
||||||
|
expect(wrapper.find("BlurableInput").hasClass("dim")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it("clamps negative numbers", () => {
|
it("clamps negative numbers", () => {
|
||||||
const mib = new McuInputBox(fakeProps());
|
const mib = new McuInputBox(fakeProps());
|
||||||
|
|
|
@ -6,38 +6,24 @@ import { BooleanMCUInputGroupProps } from "./interfaces";
|
||||||
import { Position } from "@blueprintjs/core";
|
import { Position } from "@blueprintjs/core";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { Highlight } from "./maybe_highlight";
|
import { Highlight } from "./maybe_highlight";
|
||||||
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
|
|
||||||
export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
|
export class BooleanMCUInputGroup
|
||||||
|
extends React.Component<BooleanMCUInputGroupProps> {
|
||||||
|
|
||||||
const {
|
get newFormat() { return DevSettings.futureFeaturesEnabled(); }
|
||||||
tooltip,
|
|
||||||
label,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
z,
|
|
||||||
disable,
|
|
||||||
grayscale,
|
|
||||||
caution,
|
|
||||||
displayAlert,
|
|
||||||
sourceFwConfig,
|
|
||||||
dispatch,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const xParam = sourceFwConfig(x);
|
Toggles = () => {
|
||||||
const yParam = sourceFwConfig(y);
|
const {
|
||||||
const zParam = sourceFwConfig(z);
|
sourceFwConfig, dispatch, disable, grayscale, displayAlert,
|
||||||
|
x, y, z,
|
||||||
return <Row>
|
} = this.props;
|
||||||
<Highlight settingName={label}>
|
const xParam = sourceFwConfig(x);
|
||||||
<Col xs={6} className={"widget-body-tooltips"}>
|
const yParam = sourceFwConfig(y);
|
||||||
<label>
|
const zParam = sourceFwConfig(z);
|
||||||
{t(label)}
|
const width = this.newFormat ? 4 : 2;
|
||||||
{caution &&
|
return <div className={"mcu-inputs"}>
|
||||||
<i className="fa fa-exclamation-triangle caution-icon" />}
|
<Col xs={width} className={"centered-button-div"}>
|
||||||
</label>
|
|
||||||
<Help text={tooltip} requireClick={true} position={Position.RIGHT} />
|
|
||||||
</Col>
|
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
grayscale={grayscale?.x}
|
grayscale={grayscale?.x}
|
||||||
disabled={disable?.x}
|
disabled={disable?.x}
|
||||||
|
@ -46,7 +32,7 @@ export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
|
||||||
toggleAction={() =>
|
toggleAction={() =>
|
||||||
dispatch(settingToggle(x, sourceFwConfig, displayAlert))} />
|
dispatch(settingToggle(x, sourceFwConfig, displayAlert))} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
<Col xs={width} className={"centered-button-div"}>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
grayscale={grayscale?.y}
|
grayscale={grayscale?.y}
|
||||||
disabled={disable?.y}
|
disabled={disable?.y}
|
||||||
|
@ -55,7 +41,7 @@ export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
|
||||||
toggleAction={() =>
|
toggleAction={() =>
|
||||||
dispatch(settingToggle(y, sourceFwConfig, displayAlert))} />
|
dispatch(settingToggle(y, sourceFwConfig, displayAlert))} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
<Col xs={width} className={"centered-button-div"}>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
grayscale={grayscale?.z}
|
grayscale={grayscale?.z}
|
||||||
disabled={disable?.z}
|
disabled={disable?.z}
|
||||||
|
@ -64,6 +50,24 @@ export function BooleanMCUInputGroup(props: BooleanMCUInputGroupProps) {
|
||||||
toggleAction={() =>
|
toggleAction={() =>
|
||||||
dispatch(settingToggle(z, sourceFwConfig, displayAlert))} />
|
dispatch(settingToggle(z, sourceFwConfig, displayAlert))} />
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</div>;
|
||||||
</Row>;
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { tooltip, label, caution } = this.props;
|
||||||
|
return <Highlight settingName={label}>
|
||||||
|
<Row>
|
||||||
|
<Col xs={this.newFormat ? 12 : 6} className={"widget-body-tooltips"}>
|
||||||
|
<label>
|
||||||
|
{t(label)}
|
||||||
|
{caution &&
|
||||||
|
<i className="fa fa-exclamation-triangle caution-icon" />}
|
||||||
|
</label>
|
||||||
|
<Help text={tooltip} requireClick={true} position={Position.TOP_RIGHT} />
|
||||||
|
</Col>
|
||||||
|
{!this.newFormat && <this.Toggles />}
|
||||||
|
</Row>
|
||||||
|
{this.newFormat && <Row><this.Toggles /></Row>}
|
||||||
|
</Highlight>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import axios from "axios";
|
import { FarmbotOsProps, Feature, FarmbotSettingsProps } from "../interfaces";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { Widget, WidgetHeader, WidgetBody } from "../../ui";
|
||||||
import { FarmbotOsProps, FarmbotOsState, Feature } from "../interfaces";
|
import { isBotOnlineFromState } from "../must_be_online";
|
||||||
import { Widget, WidgetHeader, WidgetBody, Row, Col } from "../../ui";
|
|
||||||
import { save, edit } from "../../api/crud";
|
|
||||||
import { isBotOnline } from "../must_be_online";
|
|
||||||
import { Content, DeviceSetting } from "../../constants";
|
|
||||||
import { TimezoneSelector } from "../timezones/timezone_selector";
|
|
||||||
import { timezoneMismatch } from "../timezones/guess_timezone";
|
|
||||||
import { CameraSelection } from "./fbos_settings/camera_selection";
|
import { CameraSelection } from "./fbos_settings/camera_selection";
|
||||||
import { BoardType } from "./fbos_settings/board_type";
|
|
||||||
import { FarmbotOsRow } from "./fbos_settings/farmbot_os_row";
|
import { FarmbotOsRow } from "./fbos_settings/farmbot_os_row";
|
||||||
import { AutoUpdateRow } from "./fbos_settings/auto_update_row";
|
import { AutoUpdateRow } from "./fbos_settings/auto_update_row";
|
||||||
import { AutoSyncRow } from "./fbos_settings/auto_sync_row";
|
import { AutoSyncRow } from "./fbos_settings/auto_sync_row";
|
||||||
import { PowerAndReset } from "./fbos_settings/power_and_reset";
|
import { PowerAndReset } from "./fbos_settings/power_and_reset";
|
||||||
import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector";
|
import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector";
|
||||||
import { ExternalUrl } from "../../external_urls";
|
|
||||||
import { Highlight } from "./maybe_highlight";
|
|
||||||
import { OtaTimeSelectorRow } from "./fbos_settings/ota_time_selector";
|
import { OtaTimeSelectorRow } from "./fbos_settings/ota_time_selector";
|
||||||
|
import { NameRow } from "./fbos_settings/name_row";
|
||||||
|
import { TimezoneRow } from "./fbos_settings/timezone_row";
|
||||||
|
import { Firmware } from "./fbos_settings/firmware";
|
||||||
|
import { Highlight } from "./maybe_highlight";
|
||||||
|
import { Header } from "./hardware_settings/header";
|
||||||
|
import { DeviceSetting } from "../../constants";
|
||||||
|
import { Collapse } from "@blueprintjs/core";
|
||||||
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
|
|
||||||
export enum ColWidth {
|
export enum ColWidth {
|
||||||
label = 3,
|
label = 3,
|
||||||
|
@ -25,142 +24,82 @@ export enum ColWidth {
|
||||||
button = 2
|
button = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const FarmBotSettings = (props: FarmbotSettingsProps) => {
|
||||||
|
const {
|
||||||
|
dispatch, device, shouldDisplay, timeSettings, sourceFbosConfig,
|
||||||
|
botOnline,
|
||||||
|
} = props;
|
||||||
|
const commonProps = { dispatch };
|
||||||
|
return <Highlight className={"section"}
|
||||||
|
settingName={DeviceSetting.farmbot}>
|
||||||
|
{DevSettings.futureFeaturesEnabled() &&
|
||||||
|
<Header {...commonProps}
|
||||||
|
title={DeviceSetting.farmbot}
|
||||||
|
panel={"farmbot_os"}
|
||||||
|
dispatch={dispatch}
|
||||||
|
expanded={props.bot.controlPanelState.farmbot_os} />}
|
||||||
|
<Collapse isOpen={!!props.bot.controlPanelState.farmbot_os}>
|
||||||
|
<NameRow {...commonProps} device={device} />
|
||||||
|
<TimezoneRow {...commonProps} device={device} />
|
||||||
|
<CameraSelection {...commonProps}
|
||||||
|
env={props.env}
|
||||||
|
botOnline={botOnline}
|
||||||
|
saveFarmwareEnv={props.saveFarmwareEnv}
|
||||||
|
shouldDisplay={shouldDisplay} />
|
||||||
|
<OtaTimeSelectorRow {...commonProps}
|
||||||
|
timeSettings={timeSettings}
|
||||||
|
device={device}
|
||||||
|
sourceFbosConfig={sourceFbosConfig} />
|
||||||
|
<AutoUpdateRow {...commonProps}
|
||||||
|
sourceFbosConfig={sourceFbosConfig} />
|
||||||
|
<FarmbotOsRow {...commonProps}
|
||||||
|
bot={props.bot}
|
||||||
|
sourceFbosConfig={sourceFbosConfig}
|
||||||
|
shouldDisplay={shouldDisplay}
|
||||||
|
botOnline={botOnline}
|
||||||
|
timeSettings={timeSettings}
|
||||||
|
deviceAccount={device} />
|
||||||
|
<AutoSyncRow {...commonProps}
|
||||||
|
sourceFbosConfig={sourceFbosConfig} />
|
||||||
|
{shouldDisplay(Feature.boot_sequence) && <BootSequenceSelector />}
|
||||||
|
</Collapse>
|
||||||
|
</Highlight>;
|
||||||
|
};
|
||||||
|
|
||||||
export class FarmbotOsSettings
|
export class FarmbotOsSettings
|
||||||
extends React.Component<FarmbotOsProps, FarmbotOsState> {
|
extends React.Component<FarmbotOsProps, {}> {
|
||||||
state: FarmbotOsState = { allOsReleaseNotes: "" };
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.fetchReleaseNotes(ExternalUrl.osReleaseNotes);
|
|
||||||
}
|
|
||||||
|
|
||||||
get osMajorVersion() {
|
|
||||||
return (this.props.bot.hardware.informational_settings
|
|
||||||
.controller_version || "6").split(".")[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchReleaseNotes = (url: string) => {
|
|
||||||
axios
|
|
||||||
.get<string>(url)
|
|
||||||
.then(resp => this.setState({ allOsReleaseNotes: resp.data }))
|
|
||||||
.catch(() => this.setState({ allOsReleaseNotes: "" }));
|
|
||||||
}
|
|
||||||
|
|
||||||
get osReleaseNotes() {
|
|
||||||
const notes = (this.state.allOsReleaseNotes
|
|
||||||
.split("# v")
|
|
||||||
.filter(x => x.startsWith(this.osMajorVersion))[0] || "")
|
|
||||||
.split("\n\n").slice(1).join("\n") || t("Could not get release notes.");
|
|
||||||
const heading = "FarmBot OS v" + this.osMajorVersion;
|
|
||||||
return { heading, notes };
|
|
||||||
}
|
|
||||||
|
|
||||||
changeBot = (e: React.FormEvent<HTMLInputElement>) => {
|
|
||||||
const { deviceAccount, dispatch } = this.props;
|
|
||||||
dispatch(edit(deviceAccount, { name: e.currentTarget.value }));
|
|
||||||
}
|
|
||||||
|
|
||||||
updateBot = () => {
|
|
||||||
const { deviceAccount, dispatch } = this.props;
|
|
||||||
dispatch(save(deviceAccount.uuid));
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTimezone = (timezone: string) => {
|
|
||||||
const { deviceAccount, dispatch } = this.props;
|
|
||||||
dispatch(edit(deviceAccount, { timezone }));
|
|
||||||
dispatch(save(deviceAccount.uuid));
|
|
||||||
}
|
|
||||||
|
|
||||||
maybeWarnTz = () => {
|
|
||||||
const wrongTZ = timezoneMismatch(this.props.deviceAccount.body.timezone);
|
|
||||||
return wrongTZ ? t(Content.DIFFERENT_TZ_WARNING) : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { bot, sourceFbosConfig, botToMqttStatus } = this.props;
|
const { bot, sourceFbosConfig } = this.props;
|
||||||
const { sync_status } = bot.hardware.informational_settings;
|
const botOnline = isBotOnlineFromState(bot);
|
||||||
const botOnline = isBotOnline(sync_status, botToMqttStatus);
|
|
||||||
return <Widget className="device-widget">
|
return <Widget className="device-widget">
|
||||||
<form onSubmit={(e) => e.preventDefault()}>
|
<WidgetHeader title="Device">
|
||||||
<WidgetHeader title="Device">
|
</WidgetHeader>
|
||||||
</WidgetHeader>
|
<WidgetBody>
|
||||||
<WidgetBody>
|
<FarmBotSettings
|
||||||
<Row>
|
bot={bot}
|
||||||
<Highlight settingName={DeviceSetting.name}>
|
env={this.props.env}
|
||||||
<Col xs={ColWidth.label}>
|
alerts={this.props.alerts}
|
||||||
<label>
|
saveFarmwareEnv={this.props.saveFarmwareEnv}
|
||||||
{t(DeviceSetting.name)}
|
dispatch={this.props.dispatch}
|
||||||
</label>
|
sourceFbosConfig={sourceFbosConfig}
|
||||||
</Col>
|
shouldDisplay={this.props.shouldDisplay}
|
||||||
<Col xs={9}>
|
botOnline={botOnline}
|
||||||
<input name="name"
|
timeSettings={this.props.timeSettings}
|
||||||
onChange={this.changeBot}
|
device={this.props.deviceAccount} />
|
||||||
onBlur={this.updateBot}
|
<Firmware
|
||||||
value={this.props.deviceAccount.body.name} />
|
bot={this.props.bot}
|
||||||
</Col>
|
alerts={this.props.alerts}
|
||||||
</Highlight>
|
dispatch={this.props.dispatch}
|
||||||
</Row>
|
sourceFbosConfig={sourceFbosConfig}
|
||||||
<Row>
|
shouldDisplay={this.props.shouldDisplay}
|
||||||
<Highlight settingName={DeviceSetting.timezone}>
|
botOnline={botOnline}
|
||||||
<Col xs={ColWidth.label}>
|
timeSettings={this.props.timeSettings} />
|
||||||
<label>
|
<PowerAndReset
|
||||||
{t("TIME ZONE")}
|
controlPanelState={this.props.bot.controlPanelState}
|
||||||
</label>
|
dispatch={this.props.dispatch}
|
||||||
</Col>
|
sourceFbosConfig={sourceFbosConfig}
|
||||||
<Col xs={ColWidth.description}>
|
botOnline={botOnline} />
|
||||||
<div className="note">
|
</WidgetBody>
|
||||||
{this.maybeWarnTz()}
|
|
||||||
</div>
|
|
||||||
<TimezoneSelector
|
|
||||||
currentTimezone={this.props.deviceAccount.body.timezone}
|
|
||||||
onUpdate={this.handleTimezone} />
|
|
||||||
</Col>
|
|
||||||
</Highlight>
|
|
||||||
</Row>
|
|
||||||
<CameraSelection
|
|
||||||
env={this.props.env}
|
|
||||||
botOnline={botOnline}
|
|
||||||
saveFarmwareEnv={this.props.saveFarmwareEnv}
|
|
||||||
shouldDisplay={this.props.shouldDisplay}
|
|
||||||
dispatch={this.props.dispatch} />
|
|
||||||
<BoardType
|
|
||||||
botOnline={botOnline}
|
|
||||||
bot={bot}
|
|
||||||
alerts={this.props.alerts}
|
|
||||||
dispatch={this.props.dispatch}
|
|
||||||
shouldDisplay={this.props.shouldDisplay}
|
|
||||||
timeSettings={this.props.timeSettings}
|
|
||||||
sourceFbosConfig={sourceFbosConfig} />
|
|
||||||
<OtaTimeSelectorRow
|
|
||||||
timeSettings={this.props.timeSettings}
|
|
||||||
device={this.props.deviceAccount}
|
|
||||||
dispatch={this.props.dispatch}
|
|
||||||
sourceFbosConfig={sourceFbosConfig} />
|
|
||||||
<AutoUpdateRow
|
|
||||||
dispatch={this.props.dispatch}
|
|
||||||
sourceFbosConfig={sourceFbosConfig} />
|
|
||||||
<FarmbotOsRow
|
|
||||||
bot={this.props.bot}
|
|
||||||
osReleaseNotesHeading={this.osReleaseNotes.heading}
|
|
||||||
osReleaseNotes={this.osReleaseNotes.notes}
|
|
||||||
dispatch={this.props.dispatch}
|
|
||||||
sourceFbosConfig={sourceFbosConfig}
|
|
||||||
shouldDisplay={this.props.shouldDisplay}
|
|
||||||
botOnline={botOnline}
|
|
||||||
botToMqttLastSeen={new Date(this.props.botToMqttLastSeen).getTime()}
|
|
||||||
timeSettings={this.props.timeSettings}
|
|
||||||
deviceAccount={this.props.deviceAccount} />
|
|
||||||
<AutoSyncRow
|
|
||||||
dispatch={this.props.dispatch}
|
|
||||||
sourceFbosConfig={sourceFbosConfig} />
|
|
||||||
{this.props.shouldDisplay(Feature.boot_sequence) &&
|
|
||||||
<BootSequenceSelector />}
|
|
||||||
<PowerAndReset
|
|
||||||
controlPanelState={this.props.bot.controlPanelState}
|
|
||||||
dispatch={this.props.dispatch}
|
|
||||||
sourceFbosConfig={sourceFbosConfig}
|
|
||||||
botOnline={botOnline} />
|
|
||||||
</WidgetBody>
|
|
||||||
</form>
|
|
||||||
</Widget>;
|
</Widget>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,19 +33,32 @@ describe("<BoardType/>", () => {
|
||||||
shouldDisplay: () => false,
|
shouldDisplay: () => false,
|
||||||
botOnline: true,
|
botOnline: true,
|
||||||
timeSettings: fakeTimeSettings(),
|
timeSettings: fakeTimeSettings(),
|
||||||
|
firmwareHardware: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Disconnected with valid FirmwareConfig", () => {
|
it("renders with valid firmwareHardware", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.sourceFbosConfig = () => ({ value: "farmduino", consistent: false });
|
p.firmwareHardware = "farmduino";
|
||||||
const wrapper = mount(<BoardType {...p} />);
|
const wrapper = mount(<BoardType {...p} />);
|
||||||
expect(wrapper.text()).toContain("Farmduino");
|
expect(wrapper.text()).toContain("Farmduino");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sets sending status", () => {
|
||||||
|
const wrapper = mount<BoardType>(<BoardType {...fakeProps()} />);
|
||||||
|
expect(wrapper.state().sending).toBeFalsy();
|
||||||
|
const p = fakeProps();
|
||||||
|
p.sourceFbosConfig = () => ({ value: true, consistent: false });
|
||||||
|
wrapper.setProps(p);
|
||||||
|
wrapper.mount();
|
||||||
|
expect(wrapper.state().sending).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it("calls updateConfig", () => {
|
it("calls updateConfig", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
const wrapper = shallow(<BoardType {...p} />);
|
const wrapper = mount<BoardType>(<BoardType {...p} />);
|
||||||
wrapper.find("FBSelect").simulate("change",
|
const selection =
|
||||||
|
shallow(<div>{wrapper.instance().FirmwareSelection()}</div>);
|
||||||
|
selection.find("FBSelect").simulate("change",
|
||||||
{ label: "firmware_hardware", value: "farmduino" });
|
{ label: "firmware_hardware", value: "farmduino" });
|
||||||
expect(edit).toHaveBeenCalledWith(fakeConfig, {
|
expect(edit).toHaveBeenCalledWith(fakeConfig, {
|
||||||
firmware_hardware: "farmduino"
|
firmware_hardware: "farmduino"
|
||||||
|
@ -53,17 +66,19 @@ describe("<BoardType/>", () => {
|
||||||
expect(save).toHaveBeenCalledWith(fakeConfig.uuid);
|
expect(save).toHaveBeenCalledWith(fakeConfig.uuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("deosn't call updateConfig", () => {
|
it("doesn't call updateConfig", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
const wrapper = shallow(<BoardType {...p} />);
|
const wrapper = mount<BoardType>(<BoardType {...p} />);
|
||||||
wrapper.find("FBSelect").simulate("change",
|
const selection =
|
||||||
|
shallow(<div>{wrapper.instance().FirmwareSelection()}</div>);
|
||||||
|
selection.find("FBSelect").simulate("change",
|
||||||
{ label: "firmware_hardware", value: "unknown" });
|
{ label: "firmware_hardware", value: "unknown" });
|
||||||
expect(edit).not.toHaveBeenCalled();
|
expect(edit).not.toHaveBeenCalled();
|
||||||
expect(save).not.toHaveBeenCalled();
|
expect(save).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays standard boards", () => {
|
it("displays standard boards", () => {
|
||||||
const wrapper = shallow(<BoardType {...fakeProps()} />);
|
const wrapper = mount(<BoardType {...fakeProps()} />);
|
||||||
const { list } = wrapper.find("FBSelect").props();
|
const { list } = wrapper.find("FBSelect").props();
|
||||||
expect(list).toEqual([
|
expect(list).toEqual([
|
||||||
{ label: "Arduino/RAMPS (Genesis v1.2)", value: "arduino" },
|
{ label: "Arduino/RAMPS (Genesis v1.2)", value: "arduino" },
|
||||||
|
@ -78,7 +93,7 @@ describe("<BoardType/>", () => {
|
||||||
it("displays new boards", () => {
|
it("displays new boards", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.shouldDisplay = () => true;
|
p.shouldDisplay = () => true;
|
||||||
const wrapper = shallow(<BoardType {...p} />);
|
const wrapper = mount(<BoardType {...p} />);
|
||||||
const { list } = wrapper.find("FBSelect").props();
|
const { list } = wrapper.find("FBSelect").props();
|
||||||
expect(list).toEqual([
|
expect(list).toEqual([
|
||||||
{ label: "Arduino/RAMPS (Genesis v1.2)", value: "arduino" },
|
{ label: "Arduino/RAMPS (Genesis v1.2)", value: "arduino" },
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
let mockDev = false;
|
||||||
|
jest.mock("../../../../account/dev/dev_support", () => ({
|
||||||
|
DevSettings: {
|
||||||
|
futureFeaturesEnabled: () => mockDev,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
import {
|
import {
|
||||||
sequence2ddi, mapStateToProps, RawBootSequenceSelector,
|
sequence2ddi, mapStateToProps, RawBootSequenceSelector,
|
||||||
} from "../boot_sequence_selector";
|
} from "../boot_sequence_selector";
|
||||||
|
@ -11,9 +18,6 @@ import {
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
import { FBSelect } from "../../../../ui";
|
import { FBSelect } from "../../../../ui";
|
||||||
// import { mount } from "enzyme";
|
|
||||||
// import React from "react";
|
|
||||||
// import { FBSelect } from "../../../../ui";
|
|
||||||
|
|
||||||
describe("sequence2ddi", () => {
|
describe("sequence2ddi", () => {
|
||||||
it("converts TaggedSequences", () => {
|
it("converts TaggedSequences", () => {
|
||||||
|
@ -98,4 +102,10 @@ describe("RawBootSequenceSelector", () => {
|
||||||
const el = mount(<RawBootSequenceSelector {...props} />);
|
const el = mount(<RawBootSequenceSelector {...props} />);
|
||||||
expect(el.find(FBSelect).length).toEqual(1);
|
expect(el.find(FBSelect).length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders formatted", () => {
|
||||||
|
mockDev = true;
|
||||||
|
const el = mount(<RawBootSequenceSelector {...fakeProps()} />);
|
||||||
|
expect(el.find(FBSelect).length).toEqual(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { FarmbotOsRow } from "../farmbot_os_row";
|
import { FarmbotOsRow, getOsReleaseNotesForVersion } from "../farmbot_os_row";
|
||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||||
import { FarmbotOsRowProps } from "../interfaces";
|
import { FarmbotOsRowProps } from "../interfaces";
|
||||||
|
@ -10,15 +10,12 @@ import { fakeDevice } from "../../../../__test_support__/resource_index_builder"
|
||||||
describe("<FarmbotOsRow/>", () => {
|
describe("<FarmbotOsRow/>", () => {
|
||||||
const fakeProps = (): FarmbotOsRowProps => ({
|
const fakeProps = (): FarmbotOsRowProps => ({
|
||||||
bot,
|
bot,
|
||||||
osReleaseNotesHeading: "",
|
|
||||||
osReleaseNotes: "",
|
|
||||||
dispatch: jest.fn(x => x(jest.fn(), fakeState)),
|
dispatch: jest.fn(x => x(jest.fn(), fakeState)),
|
||||||
sourceFbosConfig: (x) => {
|
sourceFbosConfig: (x) => {
|
||||||
return { value: bot.hardware.configuration[x], consistent: true };
|
return { value: bot.hardware.configuration[x], consistent: true };
|
||||||
},
|
},
|
||||||
shouldDisplay: () => false,
|
shouldDisplay: () => false,
|
||||||
botOnline: false,
|
botOnline: false,
|
||||||
botToMqttLastSeen: 0,
|
|
||||||
deviceAccount: fakeDevice(),
|
deviceAccount: fakeDevice(),
|
||||||
timeSettings: fakeTimeSettings(),
|
timeSettings: fakeTimeSettings(),
|
||||||
});
|
});
|
||||||
|
@ -37,3 +34,12 @@ describe("<FarmbotOsRow/>", () => {
|
||||||
expect(wrapper.text().toLowerCase()).toContain("1.0.0-beta");
|
expect(wrapper.text().toLowerCase()).toContain("1.0.0-beta");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getOsReleaseNotesForVersion()", () => {
|
||||||
|
it("fetches OS release notes", () => {
|
||||||
|
const mockData = "intro\n\n# v6\n\n* note";
|
||||||
|
const result = getOsReleaseNotesForVersion(mockData, "6.0.0");
|
||||||
|
expect(result.heading).toEqual("FarmBot OS v6");
|
||||||
|
expect(result.notes).toEqual("* note");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -71,9 +71,11 @@ describe("<FbosDetails/>", () => {
|
||||||
expect(wrapper.text()).not.toContain("name@");
|
expect(wrapper.text()).not.toContain("name@");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles missing firmware version", () => {
|
it("handles missing data", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.botInfoSettings.firmware_version = undefined;
|
p.botInfoSettings.firmware_version = undefined;
|
||||||
|
p.botInfoSettings.node_name = "";
|
||||||
|
p.botInfoSettings.commit = "";
|
||||||
const wrapper = mount(<FbosDetails {...p} />);
|
const wrapper = mount(<FbosDetails {...p} />);
|
||||||
expect(wrapper.text()).toContain("---");
|
expect(wrapper.text()).toContain("---");
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,11 +8,8 @@ import {
|
||||||
FirmwareHardwareStatusDetailsProps, FirmwareHardwareStatusDetails,
|
FirmwareHardwareStatusDetailsProps, FirmwareHardwareStatusDetails,
|
||||||
FirmwareHardwareStatusIconProps, FirmwareHardwareStatusIcon,
|
FirmwareHardwareStatusIconProps, FirmwareHardwareStatusIcon,
|
||||||
FirmwareHardwareStatusProps, FirmwareHardwareStatus,
|
FirmwareHardwareStatusProps, FirmwareHardwareStatus,
|
||||||
FirmwareActions, FirmwareActionsProps,
|
|
||||||
} from "../firmware_hardware_status";
|
} from "../firmware_hardware_status";
|
||||||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||||
import { clickButton } from "../../../../__test_support__/helpers";
|
|
||||||
import { flashFirmware } from "../../../actions";
|
|
||||||
import { fakeTimeSettings } from "../../../../__test_support__/fake_time_settings";
|
import { fakeTimeSettings } from "../../../../__test_support__/fake_time_settings";
|
||||||
|
|
||||||
describe("<FirmwareHardwareStatusDetails />", () => {
|
describe("<FirmwareHardwareStatusDetails />", () => {
|
||||||
|
@ -96,16 +93,3 @@ describe("<FirmwareHardwareStatus />", () => {
|
||||||
expect(wrapper.find(FirmwareHardwareStatusIcon).props().status).toBeTruthy();
|
expect(wrapper.find(FirmwareHardwareStatusIcon).props().status).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("<FirmwareActions />", () => {
|
|
||||||
const fakeProps = (): FirmwareActionsProps => ({
|
|
||||||
botOnline: true,
|
|
||||||
apiFirmwareValue: "arduino",
|
|
||||||
});
|
|
||||||
|
|
||||||
it("flashes firmware", () => {
|
|
||||||
const wrapper = mount(<FirmwareActions {...fakeProps()} />);
|
|
||||||
clickButton(wrapper, 0, "flash firmware");
|
|
||||||
expect(flashFirmware).toHaveBeenCalledWith("arduino");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
const mockDevice = {
|
||||||
|
rebootFirmware: jest.fn(() => Promise.resolve()),
|
||||||
|
flashFirmware: jest.fn(() => Promise.resolve()),
|
||||||
|
};
|
||||||
|
jest.mock("../../../../device", () => ({ getDevice: () => mockDevice }));
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { mount } from "enzyme";
|
||||||
|
import { Firmware } from "../firmware";
|
||||||
|
import { FirmwareProps } from "../interfaces";
|
||||||
|
import { fakeState } from "../../../../__test_support__/fake_state";
|
||||||
|
import { clickButton } from "../../../../__test_support__/helpers";
|
||||||
|
import { fakeFbosConfig } from "../../../../__test_support__/fake_state/resources";
|
||||||
|
import {
|
||||||
|
buildResourceIndex,
|
||||||
|
} from "../../../../__test_support__/resource_index_builder";
|
||||||
|
import {
|
||||||
|
fakeTimeSettings,
|
||||||
|
} from "../../../../__test_support__/fake_time_settings";
|
||||||
|
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||||
|
|
||||||
|
describe("<Firmware />", () => {
|
||||||
|
const fakeConfig = fakeFbosConfig();
|
||||||
|
const state = fakeState();
|
||||||
|
state.resources = buildResourceIndex([fakeConfig]);
|
||||||
|
|
||||||
|
const fakeProps = (): FirmwareProps => ({
|
||||||
|
dispatch: jest.fn(x => x(jest.fn(), () => state)),
|
||||||
|
sourceFbosConfig: () => ({ value: true, consistent: true }),
|
||||||
|
botOnline: true,
|
||||||
|
bot: bot,
|
||||||
|
alerts: [],
|
||||||
|
shouldDisplay: jest.fn(),
|
||||||
|
timeSettings: fakeTimeSettings(),
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restarts firmware", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.bot.controlPanelState.firmware = true;
|
||||||
|
const wrapper = mount(<Firmware {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase())
|
||||||
|
.toContain("Restart Firmware".toLowerCase());
|
||||||
|
clickButton(wrapper, 1, "restart");
|
||||||
|
expect(mockDevice.rebootFirmware).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("flashes firmware", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.bot.controlPanelState.firmware = true;
|
||||||
|
p.sourceFbosConfig = () => ({ value: "arduino", consistent: true });
|
||||||
|
const wrapper = mount(<Firmware {...p} />);
|
||||||
|
expect(wrapper.text().toLowerCase())
|
||||||
|
.toContain("Flash Firmware".toLowerCase());
|
||||||
|
clickButton(wrapper, 2, "flash firmware");
|
||||||
|
expect(mockDevice.flashFirmware).toHaveBeenCalledWith("arduino");
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,43 +1,36 @@
|
||||||
jest.mock("../../../../api/crud", () => ({ refresh: jest.fn() }));
|
jest.mock("../../../../api/crud", () => ({ refresh: jest.fn() }));
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { fakeResource } from "../../../../__test_support__/fake_resource";
|
import { LastSeen, LastSeenProps, getLastSeenNumber } from "../last_seen_row";
|
||||||
import { LastSeen, LastSeenProps } from "../last_seen_row";
|
|
||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
import { SpecialStatus, TaggedDevice } from "farmbot";
|
import { SpecialStatus } from "farmbot";
|
||||||
import { fakeTimeSettings } from "../../../../__test_support__/fake_time_settings";
|
import { fakeTimeSettings } from "../../../../__test_support__/fake_time_settings";
|
||||||
import { refresh } from "../../../../api/crud";
|
import { refresh } from "../../../../api/crud";
|
||||||
|
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||||
|
import { fakeDevice } from "../../../../__test_support__/resource_index_builder";
|
||||||
|
|
||||||
describe("<LastSeen />", () => {
|
describe("<LastSeen />", () => {
|
||||||
const resource = (): TaggedDevice => fakeResource("Device", {
|
const fakeProps = (): LastSeenProps => ({
|
||||||
id: 1,
|
device: fakeDevice(),
|
||||||
name: "foo",
|
|
||||||
last_saw_api: "",
|
|
||||||
tz_offset_hrs: 0,
|
|
||||||
ota_hour: 3
|
|
||||||
});
|
|
||||||
|
|
||||||
const props = (): LastSeenProps => ({
|
|
||||||
device: resource(),
|
|
||||||
botToMqttLastSeen: 0,
|
botToMqttLastSeen: 0,
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
timeSettings: fakeTimeSettings(),
|
timeSettings: fakeTimeSettings(),
|
||||||
});
|
});
|
||||||
|
|
||||||
it("blinks when loading", () => {
|
it("blinks when loading", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.device.specialStatus = SpecialStatus.SAVING;
|
p.device.specialStatus = SpecialStatus.SAVING;
|
||||||
const wrapper = mount(<LastSeen {...p} />);
|
const wrapper = mount(<LastSeen {...p} />);
|
||||||
expect(wrapper.text()).toContain("Loading");
|
expect(wrapper.text()).toContain("Loading");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("tells you the device has never been seen", () => {
|
it("tells you the device has never been seen", () => {
|
||||||
const wrapper = mount(<LastSeen {...props()} />);
|
const wrapper = mount(<LastSeen {...fakeProps()} />);
|
||||||
expect(wrapper.text()).toContain("network connectivity issue");
|
expect(wrapper.text()).toContain("network connectivity issue");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("tells you when the device was last seen, no MQTT", () => {
|
it("tells you when the device was last seen, no MQTT", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.device.body.last_saw_api = "2017-08-07T19:40:01.487Z";
|
p.device.body.last_saw_api = "2017-08-07T19:40:01.487Z";
|
||||||
p.botToMqttLastSeen = 0;
|
p.botToMqttLastSeen = 0;
|
||||||
const wrapper = mount<LastSeen>(<LastSeen {...p} />);
|
const wrapper = mount<LastSeen>(<LastSeen {...p} />);
|
||||||
|
@ -45,7 +38,7 @@ describe("<LastSeen />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("tells you when the device was last seen, latest: API", () => {
|
it("tells you when the device was last seen, latest: API", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.device.body.last_saw_api = "2017-08-07T19:40:01.487Z";
|
p.device.body.last_saw_api = "2017-08-07T19:40:01.487Z";
|
||||||
p.botToMqttLastSeen = new Date("2016-08-07T19:40:01.487Z").getTime();
|
p.botToMqttLastSeen = new Date("2016-08-07T19:40:01.487Z").getTime();
|
||||||
const wrapper = mount<LastSeen>(<LastSeen {...p} />);
|
const wrapper = mount<LastSeen>(<LastSeen {...p} />);
|
||||||
|
@ -53,7 +46,7 @@ describe("<LastSeen />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("tells you when the device was last seen, latest: message broker", () => {
|
it("tells you when the device was last seen, latest: message broker", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
p.device.body.last_saw_api = "2017-08-07T19:40:01.487Z";
|
p.device.body.last_saw_api = "2017-08-07T19:40:01.487Z";
|
||||||
p.botToMqttLastSeen = new Date("2017-08-07T20:40:01.487Z").getTime();
|
p.botToMqttLastSeen = new Date("2017-08-07T20:40:01.487Z").getTime();
|
||||||
const wrapper = mount<LastSeen>(<LastSeen {...p} />);
|
const wrapper = mount<LastSeen>(<LastSeen {...p} />);
|
||||||
|
@ -62,9 +55,22 @@ describe("<LastSeen />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles a click", () => {
|
it("handles a click", () => {
|
||||||
const p = props();
|
const p = fakeProps();
|
||||||
const wrapper = mount(<LastSeen {...p} />);
|
const wrapper = mount(<LastSeen {...p} />);
|
||||||
wrapper.find("i").simulate("click");
|
wrapper.find("i").simulate("click");
|
||||||
expect(refresh).toHaveBeenCalled();
|
expect(refresh).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getLastSeenNumber()", () => {
|
||||||
|
it("returns number: unknown", () => {
|
||||||
|
const result = getLastSeenNumber(bot);
|
||||||
|
expect(result).toEqual(NaN);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns number: known", () => {
|
||||||
|
bot.connectivity.uptime["bot.mqtt"] = { state: "up", at: 0 };
|
||||||
|
const result = getLastSeenNumber(bot);
|
||||||
|
expect(result).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
jest.mock("../../../../api/crud", () => ({
|
||||||
|
edit: jest.fn(),
|
||||||
|
save: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { mount, shallow } from "enzyme";
|
||||||
|
import { NameRow } from "../name_row";
|
||||||
|
import { NameRowProps } from "../interfaces";
|
||||||
|
import { edit, save } from "../../../../api/crud";
|
||||||
|
import { fakeDevice } from "../../../../__test_support__/resource_index_builder";
|
||||||
|
|
||||||
|
describe("<NameRow />", () => {
|
||||||
|
const fakeProps = (): NameRowProps => ({
|
||||||
|
device: fakeDevice(),
|
||||||
|
dispatch: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes bot name", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const newName = "new bot name";
|
||||||
|
const osSettings = mount<NameRow>(<NameRow {...p} />);
|
||||||
|
shallow(osSettings.instance().NameInput())
|
||||||
|
.simulate("change", { currentTarget: { value: newName } });
|
||||||
|
expect(edit).toHaveBeenCalledWith(p.device, { name: newName });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("saves bot name", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const osSettings = mount<NameRow>(<NameRow {...p} />);
|
||||||
|
shallow(osSettings.instance().NameInput()).simulate("blur");
|
||||||
|
expect(save).toHaveBeenCalledWith(p.device.uuid);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,10 +1,16 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { OtaTimeSelector, changeOtaHour, assertIsHour } from "../ota_time_selector";
|
import {
|
||||||
import { shallow } from "enzyme";
|
OtaTimeSelector, changeOtaHour, assertIsHour, OtaTimeSelectorRow,
|
||||||
|
OtaTimeSelectorProps,
|
||||||
|
ASAP,
|
||||||
|
} from "../ota_time_selector";
|
||||||
|
import { shallow, mount } from "enzyme";
|
||||||
import { FBSelect } from "../../../../ui";
|
import { FBSelect } from "../../../../ui";
|
||||||
import { fakeDevice } from "../../../../__test_support__/resource_index_builder";
|
import { fakeDevice } from "../../../../__test_support__/resource_index_builder";
|
||||||
|
import { OtaTimeSelectorRowProps } from "../interfaces";
|
||||||
|
import { fakeTimeSettings } from "../../../../__test_support__/fake_time_settings";
|
||||||
|
|
||||||
describe("OTA time selector", () => {
|
describe("assertIsHour()", () => {
|
||||||
it("asserts that a variable is an HOUR", () => {
|
it("asserts that a variable is an HOUR", () => {
|
||||||
expect(assertIsHour(undefined)).toBe(undefined);
|
expect(assertIsHour(undefined)).toBe(undefined);
|
||||||
// tslint:disable-next-line:no-null-keyword
|
// tslint:disable-next-line:no-null-keyword
|
||||||
|
@ -14,29 +20,42 @@ describe("OTA time selector", () => {
|
||||||
expect(crashOn(-2)).toThrowError("Not an hour!");
|
expect(crashOn(-2)).toThrowError("Not an hour!");
|
||||||
expect(crashOn(24)).toThrowError("Not an hour!");
|
expect(crashOn(24)).toThrowError("Not an hour!");
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("<OtaTimeSelector />", () => {
|
||||||
|
const fakeProps = (): OtaTimeSelectorProps => ({
|
||||||
|
timeFormat: "12h",
|
||||||
|
disabled: false,
|
||||||
|
onChange: jest.fn(),
|
||||||
|
value: 3,
|
||||||
|
});
|
||||||
|
|
||||||
it("selects an OTA update time", () => {
|
it("selects an OTA update time", () => {
|
||||||
const onUpdate = jest.fn();
|
const p = fakeProps();
|
||||||
const el = shallow(<OtaTimeSelector
|
const el = shallow(<OtaTimeSelector {...p} />);
|
||||||
timeFormat={"12h"}
|
|
||||||
disabled={false}
|
|
||||||
onChange={onUpdate}
|
|
||||||
value={3} />);
|
|
||||||
el.find(FBSelect).simulate("change", { label: "at 5 PM", value: 17 });
|
el.find(FBSelect).simulate("change", { label: "at 5 PM", value: 17 });
|
||||||
expect(onUpdate).toHaveBeenCalledWith(17);
|
expect(p.onChange).toHaveBeenCalledWith(17);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("unselects an OTA update time", () => {
|
it("unselects an OTA update time", () => {
|
||||||
const onUpdate = jest.fn();
|
const p = fakeProps();
|
||||||
const el = shallow(<OtaTimeSelector
|
const el = shallow(<OtaTimeSelector {...p} />);
|
||||||
timeFormat={"12h"}
|
|
||||||
disabled={false}
|
|
||||||
onChange={onUpdate}
|
|
||||||
value={3} />);
|
|
||||||
el.find(FBSelect).simulate("change", { label: "no", value: -1 });
|
el.find(FBSelect).simulate("change", { label: "no", value: -1 });
|
||||||
// tslint:disable-next-line:no-null-keyword
|
// tslint:disable-next-line:no-null-keyword
|
||||||
expect(onUpdate).toHaveBeenCalledWith(null);
|
expect(p.onChange).toHaveBeenCalledWith(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("select a default value", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.value = undefined;
|
||||||
|
const el = shallow(<OtaTimeSelector {...p} />);
|
||||||
|
expect(el.find(FBSelect).props().selectedItem).toEqual({
|
||||||
|
label: ASAP(), value: -1
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("changeOtaHour()", () => {
|
||||||
it("changes the OTA hour", () => {
|
it("changes the OTA hour", () => {
|
||||||
const device = fakeDevice();
|
const device = fakeDevice();
|
||||||
const dispatch = jest.fn();
|
const dispatch = jest.fn();
|
||||||
|
@ -54,3 +73,28 @@ describe("OTA time selector", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("<OtaTimeSelectorRow />", () => {
|
||||||
|
const fakeProps = (): OtaTimeSelectorRowProps => ({
|
||||||
|
dispatch: jest.fn(),
|
||||||
|
sourceFbosConfig: () => ({ value: "", consistent: true }),
|
||||||
|
device: fakeDevice(),
|
||||||
|
timeSettings: fakeTimeSettings(),
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows 12h formatted times", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.timeSettings.hour24 = false;
|
||||||
|
const wrapper = mount(<OtaTimeSelectorRow {...p} />);
|
||||||
|
expect(wrapper.find(FBSelect).props().list)
|
||||||
|
.toContainEqual({ label: "8:00 PM", value: 20 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows 24h formatted times", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.timeSettings.hour24 = true;
|
||||||
|
const wrapper = mount(<OtaTimeSelectorRow {...p} />);
|
||||||
|
expect(wrapper.find(FBSelect).props().list)
|
||||||
|
.toContainEqual({ label: "20:00", value: 20 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -6,13 +6,6 @@ jest.mock("../../../../api/crud", () => ({
|
||||||
save: jest.fn(),
|
save: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let mockDev = false;
|
|
||||||
jest.mock("../../../../account/dev/dev_support", () => ({
|
|
||||||
DevSettings: {
|
|
||||||
futureFeaturesEnabled: () => mockDev,
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { PowerAndReset } from "../power_and_reset";
|
import { PowerAndReset } from "../power_and_reset";
|
||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
|
@ -28,10 +21,6 @@ import {
|
||||||
import { edit, save } from "../../../../api/crud";
|
import { edit, save } from "../../../../api/crud";
|
||||||
|
|
||||||
describe("<PowerAndReset/>", () => {
|
describe("<PowerAndReset/>", () => {
|
||||||
beforeEach(() => {
|
|
||||||
mockDev = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const fakeConfig = fakeFbosConfig();
|
const fakeConfig = fakeFbosConfig();
|
||||||
const state = fakeState();
|
const state = fakeState();
|
||||||
state.resources = buildResourceIndex([fakeConfig]);
|
state.resources = buildResourceIndex([fakeConfig]);
|
||||||
|
@ -52,17 +41,6 @@ describe("<PowerAndReset/>", () => {
|
||||||
"Connection Attempt Period", "Change Ownership"]
|
"Connection Attempt Period", "Change Ownership"]
|
||||||
.map(string => expect(wrapper.text().toLowerCase())
|
.map(string => expect(wrapper.text().toLowerCase())
|
||||||
.toContain(string.toLowerCase()));
|
.toContain(string.toLowerCase()));
|
||||||
expect(wrapper.text().toLowerCase())
|
|
||||||
.toContain("Restart Firmware".toLowerCase());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("doesn't render restart firmware", () => {
|
|
||||||
mockDev = true;
|
|
||||||
const p = fakeProps();
|
|
||||||
p.controlPanelState.power_and_reset = true;
|
|
||||||
const wrapper = mount(<PowerAndReset {...p} />);
|
|
||||||
expect(wrapper.text().toLowerCase())
|
|
||||||
.not.toContain("Restart Firmware".toLowerCase());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders as closed", () => {
|
it("renders as closed", () => {
|
||||||
|
@ -90,21 +68,11 @@ describe("<PowerAndReset/>", () => {
|
||||||
p.sourceFbosConfig = () => ({ value: false, consistent: true });
|
p.sourceFbosConfig = () => ({ value: false, consistent: true });
|
||||||
p.controlPanelState.power_and_reset = true;
|
p.controlPanelState.power_and_reset = true;
|
||||||
const wrapper = mount(<PowerAndReset {...p} />);
|
const wrapper = mount(<PowerAndReset {...p} />);
|
||||||
clickButton(wrapper, 4, "yes");
|
clickButton(wrapper, 3, "yes");
|
||||||
expect(edit).toHaveBeenCalledWith(fakeConfig, { disable_factory_reset: true });
|
expect(edit).toHaveBeenCalledWith(fakeConfig, { disable_factory_reset: true });
|
||||||
expect(save).toHaveBeenCalledWith(fakeConfig.uuid);
|
expect(save).toHaveBeenCalledWith(fakeConfig.uuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("restarts firmware", () => {
|
|
||||||
const p = fakeProps();
|
|
||||||
p.controlPanelState.power_and_reset = true;
|
|
||||||
const wrapper = mount(<PowerAndReset {...p} />);
|
|
||||||
expect(wrapper.text().toLowerCase())
|
|
||||||
.toContain("Restart Firmware".toLowerCase());
|
|
||||||
clickButton(wrapper, 2, "restart");
|
|
||||||
expect(mockDevice.rebootFirmware).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows change ownership button", () => {
|
it("shows change ownership button", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.controlPanelState.power_and_reset = true;
|
p.controlPanelState.power_and_reset = true;
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
jest.mock("../../../../api/crud", () => ({
|
||||||
|
edit: jest.fn(),
|
||||||
|
save: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { mount, shallow } from "enzyme";
|
||||||
|
import { TimezoneRow } from "../timezone_row";
|
||||||
|
import { TimezoneRowProps } from "../interfaces";
|
||||||
|
import { edit } from "../../../../api/crud";
|
||||||
|
import { Content } from "../../../../constants";
|
||||||
|
import { fakeDevice } from "../../../../__test_support__/resource_index_builder";
|
||||||
|
|
||||||
|
describe("<TimezoneRow />", () => {
|
||||||
|
const fakeProps = (): TimezoneRowProps => ({
|
||||||
|
device: fakeDevice(),
|
||||||
|
dispatch: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
it("warns about timezone mismatch", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.device.body.timezone = "different";
|
||||||
|
const osSettings = mount(<TimezoneRow {...p} />);
|
||||||
|
expect(osSettings.text()).toContain(Content.DIFFERENT_TZ_WARNING);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("select timezone", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
const osSettings = mount<TimezoneRow>(<TimezoneRow {...p} />);
|
||||||
|
const selector = shallow(<div>{osSettings.instance().Selector()}</div>);
|
||||||
|
selector.find("TimezoneSelector").simulate("update", "fake timezone");
|
||||||
|
expect(edit).toHaveBeenCalledWith(p.device, { timezone: "fake timezone" });
|
||||||
|
});
|
||||||
|
});
|
|
@ -7,22 +7,25 @@ import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { AutoSyncRowProps } from "./interfaces";
|
import { AutoSyncRowProps } from "./interfaces";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
export function AutoSyncRow(props: AutoSyncRowProps) {
|
export function AutoSyncRow(props: AutoSyncRowProps) {
|
||||||
const autoSync = props.sourceFbosConfig("auto_sync");
|
const autoSync = props.sourceFbosConfig("auto_sync");
|
||||||
return <Row>
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
<Highlight settingName={DeviceSetting.autoSync}>
|
return <Highlight settingName={DeviceSetting.autoSync}>
|
||||||
<Col xs={ColWidth.label}>
|
<Row>
|
||||||
|
<Col xs={newFormat ? 9 : ColWidth.label}>
|
||||||
<label>
|
<label>
|
||||||
{t("AUTO SYNC")}
|
{t("AUTO SYNC")}
|
||||||
</label>
|
</label>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={ColWidth.description}>
|
{!newFormat &&
|
||||||
<p>
|
<Col xs={ColWidth.description}>
|
||||||
{t(Content.AUTO_SYNC)}
|
<p>
|
||||||
</p>
|
{t(Content.AUTO_SYNC)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>}
|
||||||
|
<Col xs={newFormat ? 3 : ColWidth.button}>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
toggleValue={autoSync.value}
|
toggleValue={autoSync.value}
|
||||||
dim={!autoSync.consistent}
|
dim={!autoSync.consistent}
|
||||||
|
@ -30,6 +33,7 @@ export function AutoSyncRow(props: AutoSyncRowProps) {
|
||||||
props.dispatch(updateConfig({ auto_sync: !autoSync.value }));
|
props.dispatch(updateConfig({ auto_sync: !autoSync.value }));
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</Row>
|
||||||
</Row>;
|
{newFormat && <Row><p>{t(Content.AUTO_SYNC)}</p></Row>}
|
||||||
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,29 +7,32 @@ import { Content, DeviceSetting } from "../../../constants";
|
||||||
import { AutoUpdateRowProps } from "./interfaces";
|
import { AutoUpdateRowProps } from "./interfaces";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
export function AutoUpdateRow(props: AutoUpdateRowProps) {
|
export function AutoUpdateRow(props: AutoUpdateRowProps) {
|
||||||
const osAutoUpdate = props.sourceFbosConfig("os_auto_update");
|
const osAutoUpdate = props.sourceFbosConfig("os_auto_update");
|
||||||
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
return <Row>
|
return <Highlight settingName={DeviceSetting.farmbotOSAutoUpdate}>
|
||||||
<Highlight settingName={DeviceSetting.farmbotOSAutoUpdate}>
|
<Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Col xs={newFormat ? 9 : ColWidth.label}>
|
||||||
<label>
|
<label>
|
||||||
{t(DeviceSetting.farmbotOSAutoUpdate)}
|
{t(DeviceSetting.farmbotOSAutoUpdate)}
|
||||||
</label>
|
</label>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={ColWidth.description}>
|
{!newFormat &&
|
||||||
<p>
|
<Col xs={ColWidth.description}>
|
||||||
{t(Content.OS_AUTO_UPDATE)}
|
<p>
|
||||||
</p>
|
{t(Content.OS_AUTO_UPDATE)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>}
|
||||||
|
<Col xs={newFormat ? 3 : ColWidth.button}>
|
||||||
<ToggleButton toggleValue={osAutoUpdate.value}
|
<ToggleButton toggleValue={osAutoUpdate.value}
|
||||||
dim={!osAutoUpdate.consistent}
|
dim={!osAutoUpdate.consistent}
|
||||||
toggleAction={() => props.dispatch(updateConfig({
|
toggleAction={() => props.dispatch(updateConfig({
|
||||||
os_auto_update: !osAutoUpdate.value
|
os_auto_update: !osAutoUpdate.value
|
||||||
}))} />
|
}))} />
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</Row>
|
||||||
</Row>;
|
{newFormat && <Row><p>{t(Content.OS_AUTO_UPDATE)}</p></Row>}
|
||||||
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Row, Col, DropDownItem, FBSelect } from "../../../ui";
|
import { Row, Col, DropDownItem, FBSelect } from "../../../ui";
|
||||||
import { info } from "../../../toast/toast";
|
import { info } from "../../../toast/toast";
|
||||||
import { FirmwareHardware } from "farmbot";
|
|
||||||
import { ColWidth } from "../farmbot_os_settings";
|
import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { updateConfig } from "../../actions";
|
import { updateConfig } from "../../actions";
|
||||||
import { BoardTypeProps } from "./interfaces";
|
import { BoardTypeProps } from "./interfaces";
|
||||||
|
@ -12,6 +11,7 @@ import {
|
||||||
} from "../firmware_hardware_support";
|
} from "../firmware_hardware_support";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
import { DeviceSetting } from "../../../constants";
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
interface BoardTypeState { sending: boolean }
|
interface BoardTypeState { sending: boolean }
|
||||||
|
|
||||||
|
@ -28,13 +28,10 @@ export class BoardType extends React.Component<BoardTypeProps, BoardTypeState> {
|
||||||
return !this.props.sourceFbosConfig("firmware_hardware").consistent;
|
return !this.props.sourceFbosConfig("firmware_hardware").consistent;
|
||||||
}
|
}
|
||||||
|
|
||||||
get apiValue(): FirmwareHardware | undefined {
|
|
||||||
const { value } = this.props.sourceFbosConfig("firmware_hardware");
|
|
||||||
return isFwHardwareValue(value) ? value : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
get selectedBoard(): DropDownItem | undefined {
|
get selectedBoard(): DropDownItem | undefined {
|
||||||
return this.apiValue ? FIRMWARE_CHOICES_DDI[this.apiValue] : undefined;
|
return this.props.firmwareHardware
|
||||||
|
? FIRMWARE_CHOICES_DDI[this.props.firmwareHardware]
|
||||||
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendOffConfig = (selectedItem: DropDownItem) => {
|
sendOffConfig = (selectedItem: DropDownItem) => {
|
||||||
|
@ -47,32 +44,43 @@ export class BoardType extends React.Component<BoardTypeProps, BoardTypeState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FirmwareSelection = () =>
|
||||||
|
<FBSelect
|
||||||
|
key={this.props.firmwareHardware}
|
||||||
|
extraClass={this.state.sending ? "dim" : ""}
|
||||||
|
list={getFirmwareChoices()}
|
||||||
|
selectedItem={this.selectedBoard}
|
||||||
|
onChange={this.sendOffConfig} />
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Row>
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
<Highlight settingName={DeviceSetting.firmware}>
|
return <Highlight settingName={DeviceSetting.firmware}>
|
||||||
|
<Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Col xs={ColWidth.label}>
|
||||||
<label>
|
<label>
|
||||||
{t("FIRMWARE")}
|
{t("FIRMWARE")}
|
||||||
</label>
|
</label>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={ColWidth.description}>
|
{!newFormat &&
|
||||||
<FBSelect
|
<Col xs={ColWidth.description}>
|
||||||
key={this.apiValue}
|
<this.FirmwareSelection />
|
||||||
extraClass={this.state.sending ? "dim" : ""}
|
</Col>}
|
||||||
list={getFirmwareChoices()}
|
|
||||||
selectedItem={this.selectedBoard}
|
|
||||||
onChange={this.sendOffConfig} />
|
|
||||||
</Col>
|
|
||||||
<Col xs={ColWidth.button}>
|
<Col xs={ColWidth.button}>
|
||||||
<FirmwareHardwareStatus
|
<FirmwareHardwareStatus
|
||||||
botOnline={this.props.botOnline}
|
botOnline={this.props.botOnline}
|
||||||
apiFirmwareValue={this.apiValue}
|
apiFirmwareValue={this.props.firmwareHardware}
|
||||||
alerts={this.props.alerts}
|
alerts={this.props.alerts}
|
||||||
bot={this.props.bot}
|
bot={this.props.bot}
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
timeSettings={this.props.timeSettings} />
|
timeSettings={this.props.timeSettings} />
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</Row>
|
||||||
</Row>;
|
{newFormat &&
|
||||||
|
<Row>
|
||||||
|
<Col xs={12} className="no-pad">
|
||||||
|
<this.FirmwareSelection />
|
||||||
|
</Col>
|
||||||
|
</Row>}
|
||||||
|
</Highlight>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
import { DeviceSetting } from "../../../constants";
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
list: DropDownItem[];
|
list: DropDownItem[];
|
||||||
|
@ -56,23 +57,34 @@ export class RawBootSequenceSelector extends React.Component<Props, {}> {
|
||||||
this.props.dispatch(save(this.props.config.uuid));
|
this.props.dispatch(save(this.props.config.uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectionInput = () =>
|
||||||
|
<FBSelect
|
||||||
|
allowEmpty={true}
|
||||||
|
list={this.props.list}
|
||||||
|
selectedItem={this.props.selectedItem}
|
||||||
|
onChange={this.onChange} />
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Row>
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
<Highlight settingName={DeviceSetting.bootSequence}>
|
return <Highlight settingName={DeviceSetting.bootSequence}>
|
||||||
<Col xs={ColWidth.label}>
|
<Row>
|
||||||
|
<Col xs={newFormat ? 12 : ColWidth.label}>
|
||||||
<label>
|
<label>
|
||||||
{t("BOOT SEQUENCE")}
|
{t("BOOT SEQUENCE")}
|
||||||
</label>
|
</label>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={7}>
|
{!newFormat &&
|
||||||
<FBSelect
|
<Col xs={ColWidth.description}>
|
||||||
allowEmpty={true}
|
<this.SelectionInput />
|
||||||
list={this.props.list}
|
</Col>}
|
||||||
selectedItem={this.props.selectedItem}
|
</Row>
|
||||||
onChange={this.onChange} />
|
{newFormat &&
|
||||||
</Col>
|
<Row>
|
||||||
</Highlight>
|
<Col xs={12} className="no-pad">
|
||||||
</Row>;
|
<this.SelectionInput />
|
||||||
|
</Col>
|
||||||
|
</Row>}
|
||||||
|
</Highlight>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { Feature, UserEnv } from "../../interfaces";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Content, ToolTips, DeviceSetting } from "../../../constants";
|
import { Content, ToolTips, DeviceSetting } from "../../../constants";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
/** Check if the camera has been disabled. */
|
/** Check if the camera has been disabled. */
|
||||||
export const cameraDisabled = (env: UserEnv): boolean =>
|
export const cameraDisabled = (env: UserEnv): boolean =>
|
||||||
|
@ -84,9 +85,10 @@ export class CameraSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Row>
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
<Highlight settingName={DeviceSetting.camera}>
|
return <Highlight settingName={DeviceSetting.camera}>
|
||||||
<Col xs={ColWidth.label}>
|
<Row>
|
||||||
|
<Col xs={newFormat ? 5 : ColWidth.label}>
|
||||||
<label>
|
<label>
|
||||||
{t("CAMERA")}
|
{t("CAMERA")}
|
||||||
</label>
|
</label>
|
||||||
|
@ -99,7 +101,7 @@ export class CameraSelection
|
||||||
onChange={this.sendOffConfig}
|
onChange={this.sendOffConfig}
|
||||||
extraClass={this.props.botOnline ? "" : "disabled"} />
|
extraClass={this.props.botOnline ? "" : "disabled"} />
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</Row>
|
||||||
</Row>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,25 +8,28 @@ import { FactoryResetRowsProps } from "./interfaces";
|
||||||
import { ColWidth } from "../farmbot_os_settings";
|
import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
export function FactoryResetRows(props: FactoryResetRowsProps) {
|
export function FactoryResetRows(props: FactoryResetRowsProps) {
|
||||||
const { dispatch, sourceFbosConfig, botOnline } = props;
|
const { dispatch, sourceFbosConfig, botOnline } = props;
|
||||||
const disableFactoryReset = sourceFbosConfig("disable_factory_reset");
|
const disableFactoryReset = sourceFbosConfig("disable_factory_reset");
|
||||||
const maybeDisableTimer = disableFactoryReset.value ? { color: "grey" } : {};
|
const maybeDisableTimer = disableFactoryReset.value ? { color: "grey" } : {};
|
||||||
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
return <div className={"factory-reset-options"}>
|
return <div className={"factory-reset-options"}>
|
||||||
<Row>
|
<Highlight settingName={DeviceSetting.factoryReset}>
|
||||||
<Highlight settingName={DeviceSetting.factoryReset}>
|
<Row>
|
||||||
<Col xs={ColWidth.label}>
|
<Col xs={newFormat ? 6 : ColWidth.label}>
|
||||||
<label>
|
<label>
|
||||||
{t(DeviceSetting.factoryReset)}
|
{t(DeviceSetting.factoryReset)}
|
||||||
</label>
|
</label>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={ColWidth.description}>
|
{!newFormat &&
|
||||||
<p>
|
<Col xs={ColWidth.description}>
|
||||||
{t(Content.FACTORY_RESET_WARNING)}
|
<p>
|
||||||
</p>
|
{t(Content.FACTORY_RESET_WARNING)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>}
|
||||||
|
<Col xs={newFormat ? 6 : ColWidth.button}>
|
||||||
<button
|
<button
|
||||||
className="fb-button red"
|
className="fb-button red"
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -36,21 +39,23 @@ export function FactoryResetRows(props: FactoryResetRowsProps) {
|
||||||
{t("FACTORY RESET")}
|
{t("FACTORY RESET")}
|
||||||
</button>
|
</button>
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</Row>
|
||||||
</Row>
|
{newFormat && <Row><p>{t(Content.FACTORY_RESET_WARNING)}</p></Row>}
|
||||||
<Row>
|
</Highlight>
|
||||||
<Highlight settingName={DeviceSetting.autoFactoryReset}>
|
<Highlight settingName={DeviceSetting.autoFactoryReset}>
|
||||||
<Col xs={ColWidth.label}>
|
<Row>
|
||||||
|
<Col xs={newFormat ? 9 : ColWidth.label}>
|
||||||
<label>
|
<label>
|
||||||
{t(DeviceSetting.autoFactoryReset)}
|
{t(DeviceSetting.autoFactoryReset)}
|
||||||
</label>
|
</label>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={ColWidth.description}>
|
{!newFormat &&
|
||||||
<p>
|
<Col xs={ColWidth.description}>
|
||||||
{t(Content.AUTO_FACTORY_RESET)}
|
<p>
|
||||||
</p>
|
{t(Content.AUTO_FACTORY_RESET)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>}
|
||||||
|
<Col xs={newFormat ? 3 : ColWidth.button}>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
toggleValue={!disableFactoryReset.value}
|
toggleValue={!disableFactoryReset.value}
|
||||||
dim={!disableFactoryReset.consistent}
|
dim={!disableFactoryReset.consistent}
|
||||||
|
@ -60,28 +65,35 @@ export function FactoryResetRows(props: FactoryResetRowsProps) {
|
||||||
}));
|
}));
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</Row>
|
||||||
</Row>
|
{newFormat && <Row><p>{t(Content.AUTO_FACTORY_RESET)}</p></Row>}
|
||||||
<Row>
|
</Highlight>
|
||||||
<Highlight settingName={DeviceSetting.connectionAttemptPeriod}>
|
<Highlight settingName={DeviceSetting.connectionAttemptPeriod}>
|
||||||
<Col xs={ColWidth.label}>
|
<Row>
|
||||||
|
<Col xs={newFormat ? 12 : ColWidth.label}>
|
||||||
<label style={maybeDisableTimer}>
|
<label style={maybeDisableTimer}>
|
||||||
{t(DeviceSetting.connectionAttemptPeriod)}
|
{t(DeviceSetting.connectionAttemptPeriod)}
|
||||||
</label>
|
</label>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={ColWidth.description}>
|
{!newFormat &&
|
||||||
<p style={maybeDisableTimer}>
|
<Col xs={ColWidth.description}>
|
||||||
{t(Content.AUTO_FACTORY_RESET_PERIOD)}
|
<p style={maybeDisableTimer}>
|
||||||
</p>
|
{t(Content.AUTO_FACTORY_RESET_PERIOD)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>}
|
||||||
|
<Col xs={newFormat ? 12 : ColWidth.button}>
|
||||||
<BotConfigInputBox
|
<BotConfigInputBox
|
||||||
setting="network_not_found_timer"
|
setting="network_not_found_timer"
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
disabled={!!disableFactoryReset.value}
|
disabled={!!disableFactoryReset.value}
|
||||||
sourceFbosConfig={sourceFbosConfig} />
|
sourceFbosConfig={sourceFbosConfig} />
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</Row>
|
||||||
</Row>
|
{newFormat && <Row>
|
||||||
|
<p className="network-not-found-timer">
|
||||||
|
{t(Content.AUTO_FACTORY_RESET_PERIOD)}
|
||||||
|
</p>
|
||||||
|
</Row>}
|
||||||
|
</Highlight>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,23 @@ import { t } from "../../../i18next_wrapper";
|
||||||
import { ErrorBoundary } from "../../../error_boundary";
|
import { ErrorBoundary } from "../../../error_boundary";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
import { DeviceSetting } from "../../../constants";
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
import { getLastSeenNumber } from "./last_seen_row";
|
||||||
|
|
||||||
|
export const getOsReleaseNotesForVersion = (
|
||||||
|
osReleaseNotes: string | undefined,
|
||||||
|
version: string | undefined,
|
||||||
|
) => {
|
||||||
|
const fallback = globalConfig.FBOS_END_OF_LIFE_VERSION || "9";
|
||||||
|
const majorVersion = (version || fallback).split(".")[0];
|
||||||
|
const allReleaseNotes = osReleaseNotes || "";
|
||||||
|
const thisReleaseNotes = allReleaseNotes.split("# v")
|
||||||
|
.filter(x => x.startsWith(majorVersion))[0] || "";
|
||||||
|
const notes = thisReleaseNotes.split("\n\n").slice(1).join("\n")
|
||||||
|
|| t("Could not get release notes.");
|
||||||
|
const heading = "FarmBot OS v" + majorVersion;
|
||||||
|
return { heading, notes };
|
||||||
|
};
|
||||||
|
|
||||||
const getVersionString =
|
const getVersionString =
|
||||||
(fbosVersion: string | undefined, onBeta: boolean | undefined): string => {
|
(fbosVersion: string | undefined, onBeta: boolean | undefined): string => {
|
||||||
|
@ -17,56 +34,72 @@ const getVersionString =
|
||||||
return fbosVersion ? fbosVersion + extension : t(" unknown (offline)");
|
return fbosVersion ? fbosVersion + extension : t(" unknown (offline)");
|
||||||
};
|
};
|
||||||
|
|
||||||
export function FarmbotOsRow(props: FarmbotOsRowProps) {
|
export class FarmbotOsRow extends React.Component<FarmbotOsRowProps> {
|
||||||
const { sourceFbosConfig, dispatch, bot, osReleaseNotes, botOnline } = props;
|
|
||||||
const { controller_version, currently_on_beta
|
Version = () => {
|
||||||
} = bot.hardware.informational_settings;
|
const { controller_version, currently_on_beta } =
|
||||||
const version = getVersionString(controller_version, currently_on_beta);
|
this.props.bot.hardware.informational_settings;
|
||||||
return <Row>
|
const version = getVersionString(controller_version, currently_on_beta);
|
||||||
<Highlight settingName={DeviceSetting.farmbotOS}>
|
return <Popover position={Position.BOTTOM_LEFT}>
|
||||||
<Col xs={ColWidth.label}>
|
<p>
|
||||||
<label>
|
{t("Version {{ version }}", { version })}
|
||||||
{t(DeviceSetting.farmbotOS)}
|
</p>
|
||||||
</label>
|
<ErrorBoundary>
|
||||||
</Col>
|
<FbosDetails
|
||||||
<Col xs={3}>
|
botInfoSettings={this.props.bot.hardware.informational_settings}
|
||||||
<Popover position={Position.BOTTOM_LEFT}>
|
dispatch={this.props.dispatch}
|
||||||
<p>
|
shouldDisplay={this.props.shouldDisplay}
|
||||||
{t("Version {{ version }}", { version })}
|
sourceFbosConfig={this.props.sourceFbosConfig}
|
||||||
</p>
|
botToMqttLastSeen={getLastSeenNumber(this.props.bot)}
|
||||||
<ErrorBoundary>
|
timeSettings={this.props.timeSettings}
|
||||||
<FbosDetails
|
deviceAccount={this.props.deviceAccount} />
|
||||||
botInfoSettings={bot.hardware.informational_settings}
|
</ErrorBoundary>
|
||||||
dispatch={dispatch}
|
</Popover>;
|
||||||
shouldDisplay={props.shouldDisplay}
|
}
|
||||||
sourceFbosConfig={sourceFbosConfig}
|
|
||||||
botToMqttLastSeen={props.botToMqttLastSeen}
|
ReleaseNotes = () => {
|
||||||
timeSettings={props.timeSettings}
|
const { osReleaseNotes, hardware } = this.props.bot;
|
||||||
deviceAccount={props.deviceAccount} />
|
const { controller_version } = hardware.informational_settings;
|
||||||
</ErrorBoundary>
|
const releaseNotes =
|
||||||
</Popover>
|
getOsReleaseNotesForVersion(osReleaseNotes, controller_version);
|
||||||
</Col>
|
return <Popover position={Position.BOTTOM} className="release-notes-wrapper">
|
||||||
<Col xs={3}>
|
<p className="release-notes-button">
|
||||||
<Popover position={Position.BOTTOM}>
|
{t("Release Notes")}
|
||||||
<p className="release-notes-button">
|
|
||||||
{t("Release Notes")}
|
|
||||||
<i className="fa fa-caret-down" />
|
<i className="fa fa-caret-down" />
|
||||||
</p>
|
</p>
|
||||||
<div className="release-notes">
|
<div className="release-notes">
|
||||||
<h1>{props.osReleaseNotesHeading}</h1>
|
<h1>{releaseNotes.heading}</h1>
|
||||||
<Markdown>
|
<Markdown>
|
||||||
{osReleaseNotes}
|
{releaseNotes.notes}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>;
|
||||||
</Col>
|
}
|
||||||
<Col xs={3}>
|
|
||||||
<OsUpdateButton
|
render() {
|
||||||
bot={bot}
|
const { sourceFbosConfig, bot, botOnline } = this.props;
|
||||||
sourceFbosConfig={sourceFbosConfig}
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
shouldDisplay={props.shouldDisplay}
|
return <Highlight settingName={DeviceSetting.farmbotOS}>
|
||||||
botOnline={botOnline} />
|
<Row>
|
||||||
</Col>
|
<Col xs={newFormat ? 5 : ColWidth.label}>
|
||||||
</Highlight>
|
<label>
|
||||||
</Row>;
|
{t(DeviceSetting.farmbotOS)}
|
||||||
|
</label>
|
||||||
|
</Col>
|
||||||
|
{!newFormat && <Col xs={3}><this.Version /></Col>}
|
||||||
|
{!newFormat && <Col xs={3}><this.ReleaseNotes /></Col>}
|
||||||
|
<Col xs={newFormat ? 7 : 3}>
|
||||||
|
<OsUpdateButton
|
||||||
|
bot={bot}
|
||||||
|
sourceFbosConfig={sourceFbosConfig}
|
||||||
|
shouldDisplay={this.props.shouldDisplay}
|
||||||
|
botOnline={botOnline} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
{newFormat && <Col xs={7} className="no-pad"><this.Version /></Col>}
|
||||||
|
{newFormat && <Col xs={5} className="no-pad"><this.ReleaseNotes /></Col>}
|
||||||
|
</Row>
|
||||||
|
</Highlight>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
import { DeviceSetting } from "../../../constants";
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
export interface FbosButtonRowProps {
|
export interface FbosButtonRowProps {
|
||||||
botOnline: boolean;
|
botOnline: boolean;
|
||||||
|
@ -15,19 +16,21 @@ export interface FbosButtonRowProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FbosButtonRow = (props: FbosButtonRowProps) => {
|
export const FbosButtonRow = (props: FbosButtonRowProps) => {
|
||||||
return <Row>
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
<Highlight settingName={props.label}>
|
return <Highlight settingName={props.label}>
|
||||||
<Col xs={ColWidth.label}>
|
<Row>
|
||||||
|
<Col xs={newFormat ? 7 : ColWidth.label}>
|
||||||
<label>
|
<label>
|
||||||
{t(props.label)}
|
{t(props.label)}
|
||||||
</label>
|
</label>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={ColWidth.description}>
|
{!newFormat &&
|
||||||
<p>
|
<Col xs={ColWidth.description}>
|
||||||
{t(props.description)}
|
<p>
|
||||||
</p>
|
{t(props.description)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={ColWidth.button}>
|
</Col>}
|
||||||
|
<Col xs={newFormat ? 5 : ColWidth.button}>
|
||||||
<button
|
<button
|
||||||
className={`fb-button ${props.color}`}
|
className={`fb-button ${props.color}`}
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -37,6 +40,7 @@ export const FbosButtonRow = (props: FbosButtonRowProps) => {
|
||||||
{t(props.buttonText)}
|
{t(props.buttonText)}
|
||||||
</button>
|
</button>
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</Row>
|
||||||
</Row>;
|
{newFormat && <Row><p>{t(props.description)}</p></Row>}
|
||||||
|
</Highlight>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { Header } from "../hardware_settings/header";
|
||||||
|
import { Collapse } from "@blueprintjs/core";
|
||||||
|
import { FirmwareProps } from "./interfaces";
|
||||||
|
import { FbosButtonRow } from "./fbos_button_row";
|
||||||
|
import { Content, DeviceSetting } from "../../../constants";
|
||||||
|
import { restartFirmware } from "../../actions";
|
||||||
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { BoardType } from "./board_type";
|
||||||
|
import { isFwHardwareValue } from "../firmware_hardware_support";
|
||||||
|
import { FlashFirmwareRow } from "./flash_firmware_row";
|
||||||
|
|
||||||
|
export function Firmware(props: FirmwareProps) {
|
||||||
|
const { dispatch, sourceFbosConfig, botOnline } = props;
|
||||||
|
const { firmware } = props.bot.controlPanelState;
|
||||||
|
|
||||||
|
const { value } = props.sourceFbosConfig("firmware_hardware");
|
||||||
|
const firmwareHardware = isFwHardwareValue(value) ? value : undefined;
|
||||||
|
return <Highlight className={"section"}
|
||||||
|
settingName={DeviceSetting.firmwareSection}>
|
||||||
|
<Header
|
||||||
|
title={DeviceSetting.firmwareSection}
|
||||||
|
panel={"firmware"}
|
||||||
|
dispatch={dispatch}
|
||||||
|
expanded={firmware} />
|
||||||
|
<Collapse isOpen={!!firmware}>
|
||||||
|
<BoardType
|
||||||
|
botOnline={botOnline}
|
||||||
|
bot={props.bot}
|
||||||
|
alerts={props.alerts}
|
||||||
|
dispatch={props.dispatch}
|
||||||
|
shouldDisplay={props.shouldDisplay}
|
||||||
|
timeSettings={props.timeSettings}
|
||||||
|
firmwareHardware={firmwareHardware}
|
||||||
|
sourceFbosConfig={sourceFbosConfig} />
|
||||||
|
<FbosButtonRow
|
||||||
|
botOnline={botOnline}
|
||||||
|
label={DeviceSetting.restartFirmware}
|
||||||
|
description={Content.RESTART_FIRMWARE}
|
||||||
|
buttonText={t("RESTART")}
|
||||||
|
color={"yellow"}
|
||||||
|
action={restartFirmware} />
|
||||||
|
<FlashFirmwareRow
|
||||||
|
botOnline={botOnline}
|
||||||
|
firmwareHardware={firmwareHardware} />
|
||||||
|
</Collapse>
|
||||||
|
</Highlight>;
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ import { t } from "../../../i18next_wrapper";
|
||||||
import { BotState } from "../../interfaces";
|
import { BotState } from "../../interfaces";
|
||||||
import { FirmwareAlerts } from "../../../messages/alerts";
|
import { FirmwareAlerts } from "../../../messages/alerts";
|
||||||
import { TimeSettings } from "../../../interfaces";
|
import { TimeSettings } from "../../../interfaces";
|
||||||
import { trim } from "../../../util";
|
|
||||||
import { Alert } from "farmbot";
|
import { Alert } from "farmbot";
|
||||||
import { isFwHardwareValue, boardType } from "../firmware_hardware_support";
|
import { isFwHardwareValue, boardType } from "../firmware_hardware_support";
|
||||||
|
|
||||||
|
@ -56,22 +55,6 @@ export const FlashFirmwareBtn = (props: FlashFirmwareBtnProps) => {
|
||||||
</button>;
|
</button>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FirmwareActionsProps {
|
|
||||||
apiFirmwareValue: string | undefined;
|
|
||||||
botOnline: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FirmwareActions = (props: FirmwareActionsProps) => {
|
|
||||||
const { apiFirmwareValue } = props;
|
|
||||||
return <div className="firmware-actions">
|
|
||||||
<p>
|
|
||||||
{trim(`${t("Flash the")} ${lookup(apiFirmwareValue) || ""}
|
|
||||||
${t("firmware to your device")}:`)}
|
|
||||||
</p>
|
|
||||||
<FlashFirmwareBtn {...props} />
|
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FirmwareHardwareStatusDetails =
|
export const FirmwareHardwareStatusDetails =
|
||||||
(props: FirmwareHardwareStatusDetailsProps) => {
|
(props: FirmwareHardwareStatusDetailsProps) => {
|
||||||
return <div className="firmware-hardware-status-details">
|
return <div className="firmware-hardware-status-details">
|
||||||
|
@ -81,10 +64,6 @@ export const FirmwareHardwareStatusDetails =
|
||||||
<p>{lookup(props.botFirmwareValue) || t("unknown")}</p>
|
<p>{lookup(props.botFirmwareValue) || t("unknown")}</p>
|
||||||
<label>{t("Arduino/Farmduino")}</label>
|
<label>{t("Arduino/Farmduino")}</label>
|
||||||
<p>{lookup(props.mcuFirmwareValue) || t("unknown")}</p>
|
<p>{lookup(props.mcuFirmwareValue) || t("unknown")}</p>
|
||||||
<label>{t("Actions")}</label>
|
|
||||||
<FirmwareActions
|
|
||||||
apiFirmwareValue={props.apiFirmwareValue}
|
|
||||||
botOnline={props.botOnline} />
|
|
||||||
<FirmwareAlerts
|
<FirmwareAlerts
|
||||||
alerts={props.alerts}
|
alerts={props.alerts}
|
||||||
dispatch={props.dispatch}
|
dispatch={props.dispatch}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { Row, Col } from "../../../ui/index";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
import { ColWidth } from "../farmbot_os_settings";
|
||||||
|
import { FlashFirmwareRowProps } from "./interfaces";
|
||||||
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
import { trim } from "lodash";
|
||||||
|
import { FlashFirmwareBtn, lookup } from "./firmware_hardware_status";
|
||||||
|
|
||||||
|
export class FlashFirmwareRow extends React.Component<FlashFirmwareRowProps> {
|
||||||
|
|
||||||
|
Description = () =>
|
||||||
|
<p>
|
||||||
|
{trim(`${t("Flash the")} ${lookup(this.props.firmwareHardware) || ""}
|
||||||
|
${t("firmware to your device")}:`)}
|
||||||
|
</p>;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
|
|
||||||
|
return <Highlight settingName={DeviceSetting.flashFirmware}>
|
||||||
|
<Row>
|
||||||
|
<Col xs={newFormat ? 6 : ColWidth.label}>
|
||||||
|
<label>
|
||||||
|
{t(DeviceSetting.flashFirmware)}
|
||||||
|
</label>
|
||||||
|
</Col>
|
||||||
|
{!newFormat && <Col xs={ColWidth.description}>
|
||||||
|
<this.Description />
|
||||||
|
</Col>}
|
||||||
|
<Col xs={newFormat ? 6 : ColWidth.button}>
|
||||||
|
<FlashFirmwareBtn
|
||||||
|
apiFirmwareValue={this.props.firmwareHardware}
|
||||||
|
botOnline={this.props.botOnline} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{newFormat && <Row> <this.Description /> </Row>}
|
||||||
|
</Highlight>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,9 +10,21 @@ import {
|
||||||
Alert,
|
Alert,
|
||||||
InformationalSettings,
|
InformationalSettings,
|
||||||
TaggedDevice,
|
TaggedDevice,
|
||||||
|
FirmwareHardware,
|
||||||
} from "farmbot";
|
} from "farmbot";
|
||||||
import { TimeSettings } from "../../../interfaces";
|
import { TimeSettings } from "../../../interfaces";
|
||||||
|
|
||||||
|
export interface NameRowProps {
|
||||||
|
dispatch: Function;
|
||||||
|
device: TaggedDevice;
|
||||||
|
widget?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimezoneRowProps {
|
||||||
|
dispatch: Function;
|
||||||
|
device: TaggedDevice;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AutoSyncRowProps {
|
export interface AutoSyncRowProps {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
sourceFbosConfig: SourceFbosConfig;
|
sourceFbosConfig: SourceFbosConfig;
|
||||||
|
@ -50,6 +62,22 @@ export interface BoardTypeProps {
|
||||||
shouldDisplay: ShouldDisplay;
|
shouldDisplay: ShouldDisplay;
|
||||||
timeSettings: TimeSettings;
|
timeSettings: TimeSettings;
|
||||||
sourceFbosConfig: SourceFbosConfig;
|
sourceFbosConfig: SourceFbosConfig;
|
||||||
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FirmwareProps {
|
||||||
|
botOnline: boolean;
|
||||||
|
bot: BotState;
|
||||||
|
alerts: Alert[];
|
||||||
|
dispatch: Function;
|
||||||
|
shouldDisplay: ShouldDisplay;
|
||||||
|
timeSettings: TimeSettings;
|
||||||
|
sourceFbosConfig: SourceFbosConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FlashFirmwareRowProps {
|
||||||
|
botOnline: boolean;
|
||||||
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PowerAndResetProps {
|
export interface PowerAndResetProps {
|
||||||
|
@ -67,13 +95,10 @@ export interface FactoryResetRowsProps {
|
||||||
|
|
||||||
export interface FarmbotOsRowProps {
|
export interface FarmbotOsRowProps {
|
||||||
bot: BotState;
|
bot: BotState;
|
||||||
osReleaseNotesHeading: string;
|
|
||||||
osReleaseNotes: string;
|
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
sourceFbosConfig: SourceFbosConfig;
|
sourceFbosConfig: SourceFbosConfig;
|
||||||
shouldDisplay: ShouldDisplay;
|
shouldDisplay: ShouldDisplay;
|
||||||
botOnline: boolean;
|
botOnline: boolean;
|
||||||
botToMqttLastSeen: number;
|
|
||||||
timeSettings: TimeSettings;
|
timeSettings: TimeSettings;
|
||||||
deviceAccount: TaggedDevice;
|
deviceAccount: TaggedDevice;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,14 @@ import { t } from "../../../i18next_wrapper";
|
||||||
import { TimeSettings } from "../../../interfaces";
|
import { TimeSettings } from "../../../interfaces";
|
||||||
import { timeFormatString } from "../../../util";
|
import { timeFormatString } from "../../../util";
|
||||||
import { refresh } from "../../../api/crud";
|
import { refresh } from "../../../api/crud";
|
||||||
|
import { BotState } from "../../interfaces";
|
||||||
|
|
||||||
|
export const getLastSeenNumber = (bot: BotState): number => {
|
||||||
|
const { uptime } = bot.connectivity;
|
||||||
|
const bot2Mqtt = uptime["bot.mqtt"];
|
||||||
|
const botToMqttLastSeen = bot2Mqtt?.state === "up" ? bot2Mqtt.at : "";
|
||||||
|
return new Date(botToMqttLastSeen).getTime();
|
||||||
|
};
|
||||||
|
|
||||||
export interface LastSeenProps {
|
export interface LastSeenProps {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { Row, Col } from "../../../ui/index";
|
||||||
|
import { DeviceSetting } from "../../../constants";
|
||||||
|
import { ColWidth } from "../farmbot_os_settings";
|
||||||
|
import { NameRowProps } from "./interfaces";
|
||||||
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { edit, save } from "../../../api/crud";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
|
export class NameRow extends React.Component<NameRowProps> {
|
||||||
|
NameInput = () =>
|
||||||
|
<input name="name"
|
||||||
|
onChange={e => this.props.dispatch(edit(this.props.device, {
|
||||||
|
name: e.currentTarget.value
|
||||||
|
}))}
|
||||||
|
onBlur={() => this.props.dispatch(save(this.props.device.uuid))}
|
||||||
|
value={this.props.device.body.name} />;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
|
return <Highlight settingName={DeviceSetting.name}>
|
||||||
|
<Row>
|
||||||
|
<Col xs={newFormat ? 12 : ColWidth.label}>
|
||||||
|
<label>
|
||||||
|
{t(DeviceSetting.name)}
|
||||||
|
</label>
|
||||||
|
</Col>
|
||||||
|
{!newFormat && <Col xs={7}><this.NameInput /></Col>}
|
||||||
|
</Row>
|
||||||
|
{newFormat && <Row><this.NameInput /></Row>}
|
||||||
|
</Highlight>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import { ColWidth } from "../farmbot_os_settings";
|
||||||
import { DeviceSetting } from "../../../constants";
|
import { DeviceSetting } from "../../../constants";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
import { OtaTimeSelectorRowProps } from "./interfaces";
|
import { OtaTimeSelectorRowProps } from "./interfaces";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
// tslint:disable-next-line:no-null-keyword
|
// tslint:disable-next-line:no-null-keyword
|
||||||
const UNDEFINED = null as unknown as undefined;
|
const UNDEFINED = null as unknown as undefined;
|
||||||
|
@ -40,7 +41,7 @@ type HOUR =
|
||||||
| 23;
|
| 23;
|
||||||
type TimeTable = Record<HOUR, DropDownItem>;
|
type TimeTable = Record<HOUR, DropDownItem>;
|
||||||
type EveryTimeTable = Record<PreferredHourFormat, TimeTable>;
|
type EveryTimeTable = Record<PreferredHourFormat, TimeTable>;
|
||||||
const ASAP = () => t("As soon as possible");
|
export const ASAP = () => t("As soon as possible");
|
||||||
const TIME_TABLE_12H = (): TimeTable => ({
|
const TIME_TABLE_12H = (): TimeTable => ({
|
||||||
0: { label: t("Midnight"), value: 0 },
|
0: { label: t("Midnight"), value: 0 },
|
||||||
1: { label: "1:00 AM", value: 1 },
|
1: { label: "1:00 AM", value: 1 },
|
||||||
|
@ -102,7 +103,7 @@ const TIME_FORMATS = (): EveryTimeTable => ({
|
||||||
"24h": TIME_TABLE_24H()
|
"24h": TIME_TABLE_24H()
|
||||||
});
|
});
|
||||||
|
|
||||||
interface OtaTimeSelectorProps {
|
export interface OtaTimeSelectorProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
timeFormat: PreferredHourFormat;
|
timeFormat: PreferredHourFormat;
|
||||||
onChange(hour24: number | undefined): void;
|
onChange(hour24: number | undefined): void;
|
||||||
|
@ -116,7 +117,8 @@ export const changeOtaHour =
|
||||||
dispatch(save(device.uuid));
|
dispatch(save(device.uuid));
|
||||||
};
|
};
|
||||||
|
|
||||||
export function assertIsHour(val: number | undefined): asserts val is (HOUR | undefined) {
|
export function assertIsHour(
|
||||||
|
val: number | undefined): asserts val is (HOUR | undefined) {
|
||||||
if ((val === null) || (val === undefined)) {
|
if ((val === null) || (val === undefined)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -147,9 +149,10 @@ export const OtaTimeSelector = (props: OtaTimeSelectorProps): JSX.Element => {
|
||||||
.sort((_x, _y) => (_x.value > _y.value) ? 1 : -1);
|
.sort((_x, _y) => (_x.value > _y.value) ? 1 : -1);
|
||||||
const selectedItem = (typeof value == "number") ?
|
const selectedItem = (typeof value == "number") ?
|
||||||
theTimeTable[value as HOUR] : theTimeTable[DEFAULT_HOUR];
|
theTimeTable[value as HOUR] : theTimeTable[DEFAULT_HOUR];
|
||||||
return <Row>
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
<Highlight settingName={DeviceSetting.applySoftwareUpdates}>
|
return <Highlight settingName={DeviceSetting.applySoftwareUpdates}>
|
||||||
<Col xs={ColWidth.label}>
|
<Row>
|
||||||
|
<Col xs={newFormat ? 5 : ColWidth.label}>
|
||||||
<label>
|
<label>
|
||||||
{t(DeviceSetting.applySoftwareUpdates)}
|
{t(DeviceSetting.applySoftwareUpdates)}
|
||||||
</label>
|
</label>
|
||||||
|
@ -161,8 +164,8 @@ export const OtaTimeSelector = (props: OtaTimeSelectorProps): JSX.Element => {
|
||||||
list={list}
|
list={list}
|
||||||
extraClass={disabled ? "disabled" : ""} />
|
extraClass={disabled ? "disabled" : ""} />
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</Row>
|
||||||
</Row>;
|
</Highlight>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function OtaTimeSelectorRow(props: OtaTimeSelectorRowProps) {
|
export function OtaTimeSelectorRow(props: OtaTimeSelectorRowProps) {
|
||||||
|
|
|
@ -6,10 +6,9 @@ import { PowerAndResetProps } from "./interfaces";
|
||||||
import { ChangeOwnershipForm } from "./change_ownership_form";
|
import { ChangeOwnershipForm } from "./change_ownership_form";
|
||||||
import { FbosButtonRow } from "./fbos_button_row";
|
import { FbosButtonRow } from "./fbos_button_row";
|
||||||
import { Content, DeviceSetting } from "../../../constants";
|
import { Content, DeviceSetting } from "../../../constants";
|
||||||
import { reboot, powerOff, restartFirmware } from "../../actions";
|
import { reboot, powerOff } from "../../actions";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
import { DevSettings } from "../../../account/dev/dev_support";
|
|
||||||
|
|
||||||
export function PowerAndReset(props: PowerAndResetProps) {
|
export function PowerAndReset(props: PowerAndResetProps) {
|
||||||
const { dispatch, sourceFbosConfig, botOnline } = props;
|
const { dispatch, sourceFbosConfig, botOnline } = props;
|
||||||
|
@ -36,14 +35,6 @@ export function PowerAndReset(props: PowerAndResetProps) {
|
||||||
buttonText={t("SHUTDOWN")}
|
buttonText={t("SHUTDOWN")}
|
||||||
color={"red"}
|
color={"red"}
|
||||||
action={powerOff} />
|
action={powerOff} />
|
||||||
{!DevSettings.futureFeaturesEnabled() &&
|
|
||||||
<FbosButtonRow
|
|
||||||
botOnline={botOnline}
|
|
||||||
label={DeviceSetting.restartFirmware}
|
|
||||||
description={Content.RESTART_FIRMWARE}
|
|
||||||
buttonText={t("RESTART")}
|
|
||||||
color={"yellow"}
|
|
||||||
action={restartFirmware} />}
|
|
||||||
<FactoryResetRows
|
<FactoryResetRows
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
sourceFbosConfig={sourceFbosConfig}
|
sourceFbosConfig={sourceFbosConfig}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { Row, Col } from "../../../ui/index";
|
||||||
|
import { DeviceSetting, Content } from "../../../constants";
|
||||||
|
import { ColWidth } from "../farmbot_os_settings";
|
||||||
|
import { TimezoneRowProps } from "./interfaces";
|
||||||
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { edit, save } from "../../../api/crud";
|
||||||
|
import { timezoneMismatch } from "../../timezones/guess_timezone";
|
||||||
|
import { TimezoneSelector } from "../../timezones/timezone_selector";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
|
export class TimezoneRow extends React.Component<TimezoneRowProps> {
|
||||||
|
|
||||||
|
Note = () =>
|
||||||
|
<div className="note">
|
||||||
|
{timezoneMismatch(this.props.device.body.timezone)
|
||||||
|
? t(Content.DIFFERENT_TZ_WARNING) : ""}
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
Selector = () =>
|
||||||
|
<TimezoneSelector
|
||||||
|
currentTimezone={this.props.device.body.timezone}
|
||||||
|
onUpdate={timezone => {
|
||||||
|
this.props.dispatch(edit(this.props.device, { timezone }));
|
||||||
|
this.props.dispatch(save(this.props.device.uuid));
|
||||||
|
}} />;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
|
|
||||||
|
return <Highlight settingName={DeviceSetting.timezone}>
|
||||||
|
<Row>
|
||||||
|
<Col xs={newFormat ? 12 : ColWidth.label}>
|
||||||
|
<label>
|
||||||
|
{t("TIME ZONE")}
|
||||||
|
</label>
|
||||||
|
</Col>
|
||||||
|
{!newFormat &&
|
||||||
|
<Col xs={ColWidth.description}>
|
||||||
|
<this.Note />
|
||||||
|
<this.Selector />
|
||||||
|
</Col>}
|
||||||
|
</Row>
|
||||||
|
{newFormat && <Row>
|
||||||
|
<Col xs={12}><this.Note /></Col>
|
||||||
|
<Col xs={12} className="no-pad"><this.Selector /></Col>
|
||||||
|
</Row>}
|
||||||
|
</Highlight>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import * as React from "react";
|
||||||
import { MCUFactoryReset, bulkToggleControlPanel } from "../actions";
|
import { MCUFactoryReset, bulkToggleControlPanel } from "../actions";
|
||||||
import { Widget, WidgetHeader, WidgetBody, Color } from "../../ui/index";
|
import { Widget, WidgetHeader, WidgetBody, Color } from "../../ui/index";
|
||||||
import { HardwareSettingsProps, SourceFwConfig } from "../interfaces";
|
import { HardwareSettingsProps, SourceFwConfig } from "../interfaces";
|
||||||
import { isBotOnline } from "../must_be_online";
|
import { isBotOnlineFromState } from "../must_be_online";
|
||||||
import { ToolTips } from "../../constants";
|
import { ToolTips } from "../../constants";
|
||||||
import { DangerZone } from "./hardware_settings/danger_zone";
|
import { DangerZone } from "./hardware_settings/danger_zone";
|
||||||
import { PinGuard } from "./hardware_settings/pin_guard";
|
import { PinGuard } from "./hardware_settings/pin_guard";
|
||||||
|
@ -17,24 +17,18 @@ import { FwParamExportMenu } from "./hardware_settings/export_menu";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { PinBindings } from "./hardware_settings/pin_bindings";
|
import { PinBindings } from "./hardware_settings/pin_bindings";
|
||||||
import { ErrorHandling } from "./hardware_settings/error_handling";
|
import { ErrorHandling } from "./hardware_settings/error_handling";
|
||||||
import { maybeOpenPanel } from "./maybe_highlight";
|
|
||||||
import type { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
import type { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
||||||
import type { McuParamName } from "farmbot";
|
import type { McuParamName } from "farmbot";
|
||||||
|
|
||||||
export class HardwareSettings extends
|
export class HardwareSettings extends
|
||||||
React.Component<HardwareSettingsProps, {}> {
|
React.Component<HardwareSettingsProps, {}> {
|
||||||
|
|
||||||
componentDidMount = () =>
|
|
||||||
this.props.dispatch(maybeOpenPanel(this.props.controlPanelState));
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
bot, dispatch, sourceFwConfig, controlPanelState, firmwareConfig,
|
bot, dispatch, sourceFwConfig, controlPanelState, firmwareConfig,
|
||||||
botToMqttStatus, firmwareHardware, resources
|
firmwareHardware, resources
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { informational_settings } = this.props.bot.hardware;
|
const botOnline = !isBotOnlineFromState(bot);
|
||||||
const { sync_status } = informational_settings;
|
|
||||||
const botDisconnected = !isBotOnline(sync_status, botToMqttStatus);
|
|
||||||
const commonProps = { dispatch, controlPanelState };
|
const commonProps = { dispatch, controlPanelState };
|
||||||
return <Widget className="hardware-widget">
|
return <Widget className="hardware-widget">
|
||||||
<WidgetHeader title={t("Hardware")} helpText={ToolTips.HW_SETTINGS}>
|
<WidgetHeader title={t("Hardware")} helpText={ToolTips.HW_SETTINGS}>
|
||||||
|
@ -63,7 +57,7 @@ export class HardwareSettings extends
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
firmwareConfig={firmwareConfig}
|
firmwareConfig={firmwareConfig}
|
||||||
firmwareHardware={firmwareHardware}
|
firmwareHardware={firmwareHardware}
|
||||||
botDisconnected={botDisconnected} />
|
botOnline={botOnline} />
|
||||||
<Motors {...commonProps}
|
<Motors {...commonProps}
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
firmwareHardware={firmwareHardware} />
|
firmwareHardware={firmwareHardware} />
|
||||||
|
@ -82,7 +76,7 @@ export class HardwareSettings extends
|
||||||
sourceFwConfig={sourceFwConfig} />
|
sourceFwConfig={sourceFwConfig} />
|
||||||
<DangerZone {...commonProps}
|
<DangerZone {...commonProps}
|
||||||
onReset={MCUFactoryReset}
|
onReset={MCUFactoryReset}
|
||||||
botDisconnected={botDisconnected} />
|
botOnline={botOnline} />
|
||||||
</WidgetBody>
|
</WidgetBody>
|
||||||
</Widget>;
|
</Widget>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ describe("<CalibrationRow />", () => {
|
||||||
const fakeProps = (): CalibrationRowProps => ({
|
const fakeProps = (): CalibrationRowProps => ({
|
||||||
type: "calibrate",
|
type: "calibrate",
|
||||||
hardware: bot.hardware.mcu_params,
|
hardware: bot.hardware.mcu_params,
|
||||||
botDisconnected: false,
|
botOnline: true,
|
||||||
action: jest.fn(),
|
action: jest.fn(),
|
||||||
toolTip: "calibrate",
|
toolTip: "calibrate",
|
||||||
title: DeviceSetting.calibration,
|
title: DeviceSetting.calibration,
|
||||||
|
|
|
@ -33,7 +33,7 @@ describe("<HomingAndCalibration />", () => {
|
||||||
value: bot.hardware.mcu_params[x], consistent: true
|
value: bot.hardware.mcu_params[x], consistent: true
|
||||||
}),
|
}),
|
||||||
firmwareConfig: fakeFirmwareConfig().body,
|
firmwareConfig: fakeFirmwareConfig().body,
|
||||||
botDisconnected: false,
|
botOnline: true,
|
||||||
firmwareHardware: undefined,
|
firmwareHardware: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,33 +6,45 @@ import { CalibrationRowProps } from "../interfaces";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Position } from "@blueprintjs/core";
|
import { Position } from "@blueprintjs/core";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
export function CalibrationRow(props: CalibrationRowProps) {
|
export class CalibrationRow extends React.Component<CalibrationRowProps> {
|
||||||
|
|
||||||
const { hardware, botDisconnected } = props;
|
get newFormat() { return DevSettings.futureFeaturesEnabled(); }
|
||||||
|
|
||||||
return <Row>
|
Axes = () => {
|
||||||
<Highlight settingName={props.title}>
|
const { type, botOnline, axisTitle, hardware, action } = this.props;
|
||||||
<Col xs={6} className={"widget-body-tooltips"}>
|
return <div className="calibration-row-axes">
|
||||||
<label>
|
|
||||||
{t(props.title)}
|
|
||||||
</label>
|
|
||||||
<Help text={t(props.toolTip)}
|
|
||||||
requireClick={true} position={Position.RIGHT} />
|
|
||||||
</Col>
|
|
||||||
{axisTrackingStatus(hardware)
|
{axisTrackingStatus(hardware)
|
||||||
.map(row => {
|
.map(row => {
|
||||||
const { axis } = row;
|
const { axis } = row;
|
||||||
const hardwareDisabled = props.type == "zero" ? false : row.disabled;
|
const hardwareDisabled = type == "zero" ? false : row.disabled;
|
||||||
return <Col xs={2} key={axis} className={"centered-button-div"}>
|
return <Col xs={this.newFormat ? 4 : 2} key={axis}
|
||||||
|
className={"centered-button-div"}>
|
||||||
<LockableButton
|
<LockableButton
|
||||||
disabled={hardwareDisabled || botDisconnected}
|
disabled={hardwareDisabled || !botOnline}
|
||||||
title={t(props.axisTitle)}
|
title={t(axisTitle)}
|
||||||
onClick={() => props.action(axis)}>
|
onClick={() => action(axis)}>
|
||||||
{`${t(props.axisTitle)} ${axis}`}
|
{`${t(axisTitle)} ${axis}`}
|
||||||
</LockableButton>
|
</LockableButton>
|
||||||
</Col>;
|
</Col>;
|
||||||
})}
|
})}
|
||||||
</Highlight>
|
</div>;
|
||||||
</Row>;
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <Highlight settingName={this.props.title}>
|
||||||
|
<Row>
|
||||||
|
<Col xs={this.newFormat ? 12 : 6} className={"widget-body-tooltips"}>
|
||||||
|
<label>
|
||||||
|
{t(this.props.title)}
|
||||||
|
</label>
|
||||||
|
<Help text={t(this.props.toolTip)}
|
||||||
|
requireClick={true} position={Position.TOP_RIGHT} />
|
||||||
|
</Col>
|
||||||
|
{!this.newFormat && <this.Axes />}
|
||||||
|
</Row>
|
||||||
|
{this.newFormat && <Row><this.Axes /></Row>}
|
||||||
|
</Highlight>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,13 @@ import { Collapse } from "@blueprintjs/core";
|
||||||
import { Content, DeviceSetting } from "../../../constants";
|
import { Content, DeviceSetting } from "../../../constants";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
export function DangerZone(props: DangerZoneProps) {
|
export function DangerZone(props: DangerZoneProps) {
|
||||||
|
|
||||||
const { dispatch, onReset, botDisconnected } = props;
|
const { dispatch, onReset, botOnline } = props;
|
||||||
const { danger_zone } = props.controlPanelState;
|
const { danger_zone } = props.controlPanelState;
|
||||||
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
return <Highlight className={"section"}
|
return <Highlight className={"section"}
|
||||||
settingName={DeviceSetting.dangerZone}>
|
settingName={DeviceSetting.dangerZone}>
|
||||||
<Header
|
<Header
|
||||||
|
@ -20,29 +21,32 @@ export function DangerZone(props: DangerZoneProps) {
|
||||||
panel={"danger_zone"}
|
panel={"danger_zone"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!danger_zone}>
|
<Collapse isOpen={!!danger_zone}>
|
||||||
<Row>
|
<Highlight settingName={DeviceSetting.resetHardwareParams}>
|
||||||
<Highlight settingName={DeviceSetting.resetHardwareParams}>
|
<Row>
|
||||||
<Col xs={4}>
|
<Col xs={newFormat ? 8 : 4}>
|
||||||
<label>
|
<label>
|
||||||
{t(DeviceSetting.resetHardwareParams)}
|
{t(DeviceSetting.resetHardwareParams)}
|
||||||
</label>
|
</label>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={6}>
|
{!newFormat &&
|
||||||
<p>
|
<Col xs={6}>
|
||||||
{t(Content.RESTORE_DEFAULT_HARDWARE_SETTINGS)}
|
<p>
|
||||||
</p>
|
{t(Content.RESTORE_DEFAULT_HARDWARE_SETTINGS)}
|
||||||
</Col>
|
</p>
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
</Col>}
|
||||||
|
<Col xs={newFormat ? 4 : 2} className={"centered-button-div"}>
|
||||||
<button
|
<button
|
||||||
className="fb-button red"
|
className="fb-button red"
|
||||||
disabled={botDisconnected}
|
disabled={!botOnline}
|
||||||
title={t("RESET")}
|
title={t("RESET")}
|
||||||
onClick={onReset}>
|
onClick={onReset}>
|
||||||
{t("RESET")}
|
{t("RESET")}
|
||||||
</button>
|
</button>
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</Row>
|
||||||
</Row>
|
{newFormat &&
|
||||||
|
<Row><p>{t(Content.RESTORE_DEFAULT_HARDWARE_SETTINGS)}</p></Row>}
|
||||||
|
</Highlight>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</Highlight>;
|
</Highlight>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,9 +31,7 @@ export function Encoders(props: EncodersProps) {
|
||||||
panel={"encoders"}
|
panel={"encoders"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!encoders}>
|
<Collapse isOpen={!!encoders}>
|
||||||
<div className="label-headings">
|
<SpacePanelHeader />
|
||||||
<SpacePanelHeader />
|
|
||||||
</div>
|
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
label={!showEncoders
|
label={!showEncoders
|
||||||
? DeviceSetting.enableStallDetection
|
? DeviceSetting.enableStallDetection
|
||||||
|
|
|
@ -20,9 +20,7 @@ export function EndStops(props: EndStopsProps) {
|
||||||
panel={"endstops"}
|
panel={"endstops"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!endstops}>
|
<Collapse isOpen={!!endstops}>
|
||||||
<div className="label-headings">
|
<SpacePanelHeader />
|
||||||
<SpacePanelHeader />
|
|
||||||
</div>
|
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
label={DeviceSetting.enableEndstops}
|
label={DeviceSetting.enableEndstops}
|
||||||
tooltip={ToolTips.ENABLE_ENDSTOPS}
|
tooltip={ToolTips.ENABLE_ENDSTOPS}
|
||||||
|
|
|
@ -25,9 +25,7 @@ export function ErrorHandling(props: ErrorHandlingProps) {
|
||||||
panel={"error_handling"}
|
panel={"error_handling"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!error_handling}>
|
<Collapse isOpen={!!error_handling}>
|
||||||
<div className="label-headings">
|
<SpacePanelHeader />
|
||||||
<SpacePanelHeader />
|
|
||||||
</div>
|
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
label={DeviceSetting.timeoutAfter}
|
label={DeviceSetting.timeoutAfter}
|
||||||
tooltip={ToolTips.TIMEOUT_AFTER}
|
tooltip={ToolTips.TIMEOUT_AFTER}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { SpacePanelHeader } from "./space_panel_header";
|
||||||
export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dispatch, bot, sourceFwConfig, firmwareConfig, botDisconnected,
|
dispatch, bot, sourceFwConfig, firmwareConfig, botOnline,
|
||||||
firmwareHardware
|
firmwareHardware
|
||||||
} = props;
|
} = props;
|
||||||
const hardware = firmwareConfig ? firmwareConfig : bot.hardware.mcu_params;
|
const hardware = firmwareConfig ? firmwareConfig : bot.hardware.mcu_params;
|
||||||
|
@ -41,9 +41,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
expanded={homing_and_calibration} />
|
expanded={homing_and_calibration} />
|
||||||
<Collapse isOpen={!!homing_and_calibration}>
|
<Collapse isOpen={!!homing_and_calibration}>
|
||||||
<div className="label-headings">
|
<SpacePanelHeader />
|
||||||
<SpacePanelHeader />
|
|
||||||
</div>
|
|
||||||
<CalibrationRow
|
<CalibrationRow
|
||||||
type={"find_home"}
|
type={"find_home"}
|
||||||
title={DeviceSetting.homing}
|
title={DeviceSetting.homing}
|
||||||
|
@ -55,7 +53,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
.findHome({ speed: CONFIG_DEFAULTS.speed, axis })
|
.findHome({ speed: CONFIG_DEFAULTS.speed, axis })
|
||||||
.catch(commandErr("'Find Home' request"))}
|
.catch(commandErr("'Find Home' request"))}
|
||||||
hardware={hardware}
|
hardware={hardware}
|
||||||
botDisconnected={botDisconnected} />
|
botOnline={botOnline} />
|
||||||
<CalibrationRow
|
<CalibrationRow
|
||||||
type={"calibrate"}
|
type={"calibrate"}
|
||||||
title={DeviceSetting.calibration}
|
title={DeviceSetting.calibration}
|
||||||
|
@ -66,7 +64,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
action={axis => getDevice().calibrate({ axis })
|
action={axis => getDevice().calibrate({ axis })
|
||||||
.catch(commandErr("Calibration"))}
|
.catch(commandErr("Calibration"))}
|
||||||
hardware={hardware}
|
hardware={hardware}
|
||||||
botDisconnected={botDisconnected} />
|
botOnline={botOnline} />
|
||||||
<CalibrationRow
|
<CalibrationRow
|
||||||
type={"zero"}
|
type={"zero"}
|
||||||
title={DeviceSetting.setZeroPosition}
|
title={DeviceSetting.setZeroPosition}
|
||||||
|
@ -75,7 +73,7 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
|
||||||
action={axis => getDevice().setZero(axis)
|
action={axis => getDevice().setZero(axis)
|
||||||
.catch(commandErr("Zeroing"))}
|
.catch(commandErr("Zeroing"))}
|
||||||
hardware={hardware}
|
hardware={hardware}
|
||||||
botDisconnected={botDisconnected} />
|
botOnline={botOnline} />
|
||||||
<BooleanMCUInputGroup
|
<BooleanMCUInputGroup
|
||||||
label={DeviceSetting.findHomeOnBoot}
|
label={DeviceSetting.findHomeOnBoot}
|
||||||
tooltip={!hasEncoders(firmwareHardware)
|
tooltip={!hasEncoders(firmwareHardware)
|
||||||
|
|
|
@ -44,9 +44,7 @@ export function Motors(props: MotorsProps) {
|
||||||
panel={"motors"}
|
panel={"motors"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!controlPanelState.motors}>
|
<Collapse isOpen={!!controlPanelState.motors}>
|
||||||
<div className="label-headings">
|
<SpacePanelHeader />
|
||||||
<SpacePanelHeader />
|
|
||||||
</div>
|
|
||||||
<NumericMCUInputGroup
|
<NumericMCUInputGroup
|
||||||
label={DeviceSetting.maxSpeed}
|
label={DeviceSetting.maxSpeed}
|
||||||
tooltip={ToolTips.MAX_SPEED}
|
tooltip={ToolTips.MAX_SPEED}
|
||||||
|
@ -140,6 +138,7 @@ export function Motors(props: MotorsProps) {
|
||||||
label={DeviceSetting.enable2ndXMotor}
|
label={DeviceSetting.enable2ndXMotor}
|
||||||
tooltip={ToolTips.ENABLE_X2_MOTOR}>
|
tooltip={ToolTips.ENABLE_X2_MOTOR}>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
|
className={"no-float"}
|
||||||
toggleValue={enable2ndXMotor.value}
|
toggleValue={enable2ndXMotor.value}
|
||||||
dim={!enable2ndXMotor.consistent}
|
dim={!enable2ndXMotor.consistent}
|
||||||
toggleAction={() => dispatch(
|
toggleAction={() => dispatch(
|
||||||
|
@ -149,6 +148,7 @@ export function Motors(props: MotorsProps) {
|
||||||
label={DeviceSetting.invert2ndXMotor}
|
label={DeviceSetting.invert2ndXMotor}
|
||||||
tooltip={ToolTips.INVERT_MOTORS}>
|
tooltip={ToolTips.INVERT_MOTORS}>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
|
className={"no-float"}
|
||||||
grayscale={!enable2ndXMotor.value}
|
grayscale={!enable2ndXMotor.value}
|
||||||
toggleValue={invert2ndXMotor.value}
|
toggleValue={invert2ndXMotor.value}
|
||||||
dim={!invert2ndXMotor.consistent}
|
dim={!invert2ndXMotor.consistent}
|
||||||
|
|
|
@ -7,12 +7,13 @@ import { Row, Col, Help } from "../../../ui/index";
|
||||||
import { ToolTips, DeviceSetting } from "../../../constants";
|
import { ToolTips, DeviceSetting } from "../../../constants";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
export function PinGuard(props: PinGuardProps) {
|
export function PinGuard(props: PinGuardProps) {
|
||||||
|
|
||||||
const { pin_guard } = props.controlPanelState;
|
const { pin_guard } = props.controlPanelState;
|
||||||
const { dispatch, sourceFwConfig, resources } = props;
|
const { dispatch, sourceFwConfig, resources } = props;
|
||||||
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
return <Highlight className={"section"}
|
return <Highlight className={"section"}
|
||||||
settingName={DeviceSetting.pinGuard}>
|
settingName={DeviceSetting.pinGuard}>
|
||||||
<Header
|
<Header
|
||||||
|
@ -21,25 +22,27 @@ export function PinGuard(props: PinGuardProps) {
|
||||||
panel={"pin_guard"}
|
panel={"pin_guard"}
|
||||||
dispatch={dispatch} />
|
dispatch={dispatch} />
|
||||||
<Collapse isOpen={!!pin_guard}>
|
<Collapse isOpen={!!pin_guard}>
|
||||||
<Row>
|
{!newFormat &&
|
||||||
<Col xs={3} xsOffset={3} className={"widget-body-tooltips"}>
|
<Row>
|
||||||
<label>
|
<Col xs={3} xsOffset={3}
|
||||||
{t("Pin Number")}
|
className={"widget-body-tooltips"}>
|
||||||
</label>
|
<label>
|
||||||
<Help text={ToolTips.PIN_GUARD_PIN_NUMBER} requireClick={true}
|
{t("Pin Number")}
|
||||||
position={Position.RIGHT} />
|
</label>
|
||||||
</Col>
|
<Help text={ToolTips.PIN_GUARD_PIN_NUMBER} requireClick={true}
|
||||||
<Col xs={4}>
|
position={Position.TOP_RIGHT} />
|
||||||
<label>
|
</Col>
|
||||||
{t("Timeout (sec)")}
|
<Col xs={4}>
|
||||||
</label>
|
<label>
|
||||||
</Col>
|
{t("Timeout (sec)")}
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
</label>
|
||||||
<label>
|
</Col>
|
||||||
{t("To State")}
|
<Col xs={2} className={"centered-button-div"}>
|
||||||
</label>
|
<label>
|
||||||
</Col>
|
{t("To State")}
|
||||||
</Row>
|
</label>
|
||||||
|
</Col>
|
||||||
|
</Row>}
|
||||||
<PinGuardMCUInputGroup
|
<PinGuardMCUInputGroup
|
||||||
label={t("Pin Guard {{ num }}", { num: 1 })}
|
label={t("Pin Guard {{ num }}", { num: 1 })}
|
||||||
pinNumKey={"pin_guard_1_pin_nr"}
|
pinNumKey={"pin_guard_1_pin_nr"}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { Position } from "@blueprintjs/core";
|
||||||
import { DeviceSetting } from "../../../constants";
|
import { DeviceSetting } from "../../../constants";
|
||||||
import { Highlight } from "../maybe_highlight";
|
import { Highlight } from "../maybe_highlight";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
export interface SingleSettingRowProps {
|
export interface SingleSettingRowProps {
|
||||||
label: DeviceSetting;
|
label: DeviceSetting;
|
||||||
|
@ -13,15 +14,19 @@ export interface SingleSettingRowProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SingleSettingRow =
|
export const SingleSettingRow =
|
||||||
({ label, tooltip, settingType, children }: SingleSettingRowProps) =>
|
({ label, tooltip, settingType, children }: SingleSettingRowProps) => {
|
||||||
<Row>
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
<Highlight settingName={label}>
|
return <Highlight settingName={label}>
|
||||||
<Col xs={6} className={"widget-body-tooltips"}>
|
<Row>
|
||||||
|
<Col xs={newFormat ? 12 : 6} className={"widget-body-tooltips"}>
|
||||||
<label>{t(label)}</label>
|
<label>{t(label)}</label>
|
||||||
<Help text={tooltip} requireClick={true} position={Position.RIGHT} />
|
<Help text={tooltip} requireClick={true} position={Position.TOP_RIGHT} />
|
||||||
</Col>
|
</Col>
|
||||||
{settingType === "button"
|
{settingType === "button"
|
||||||
? <Col xs={2} className={"centered-button-div"}>{children}</Col>
|
? <Col xs={newFormat ? 5 : 2} className={"centered-button-div"}>
|
||||||
: <Col xs={6}>{children}</Col>}
|
{children}
|
||||||
</Highlight>
|
</Col>
|
||||||
</Row>;
|
: <Col xs={newFormat ? 8 : 6}>{children}</Col>}
|
||||||
|
</Row>
|
||||||
|
</Highlight>;
|
||||||
|
};
|
||||||
|
|
|
@ -1,23 +1,29 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Row, Col } from "../../../ui/index";
|
import { Row, Col } from "../../../ui/index";
|
||||||
import { t } from "../../../i18next_wrapper";
|
import { t } from "../../../i18next_wrapper";
|
||||||
|
import { DevSettings } from "../../../account/dev/dev_support";
|
||||||
|
|
||||||
export function SpacePanelHeader(_: {}) {
|
export function SpacePanelHeader() {
|
||||||
return <Row>
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
<Col xs={2} xsOffset={6} className={"centered-button-div"}>
|
const width = newFormat ? 4 : 2;
|
||||||
<label>
|
const offset = newFormat ? 0 : 6;
|
||||||
{t("X AXIS")}
|
return <div className="label-headings">
|
||||||
</label>
|
<Row>
|
||||||
</Col>
|
<Col xs={width} xsOffset={offset} className={"centered-button-div"}>
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
<label>
|
||||||
<label>
|
{t("X AXIS")}
|
||||||
{t("Y AXIS")}
|
</label>
|
||||||
</label>
|
</Col>
|
||||||
</Col>
|
<Col xs={width} className={"centered-button-div"}>
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
<label>
|
||||||
<label>
|
{t("Y AXIS")}
|
||||||
{t("Z AXIS")}
|
</label>
|
||||||
</label>
|
</Col>
|
||||||
</Col>
|
<Col xs={width} className={"centered-button-div"}>
|
||||||
</Row>;
|
<label>
|
||||||
|
{t("Z AXIS")}
|
||||||
|
</label>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ export interface HomingAndCalibrationProps {
|
||||||
controlPanelState: ControlPanelState;
|
controlPanelState: ControlPanelState;
|
||||||
sourceFwConfig: SourceFwConfig;
|
sourceFwConfig: SourceFwConfig;
|
||||||
firmwareConfig: FirmwareConfig | undefined;
|
firmwareConfig: FirmwareConfig | undefined;
|
||||||
botDisconnected: boolean;
|
botOnline: boolean;
|
||||||
firmwareHardware: FirmwareHardware | undefined;
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ export interface BooleanMCUInputGroupProps {
|
||||||
export interface CalibrationRowProps {
|
export interface CalibrationRowProps {
|
||||||
type: "find_home" | "calibrate" | "zero";
|
type: "find_home" | "calibrate" | "zero";
|
||||||
hardware: McuParams;
|
hardware: McuParams;
|
||||||
botDisconnected: boolean;
|
botOnline: boolean;
|
||||||
action(axis: Axis): void;
|
action(axis: Axis): void;
|
||||||
toolTip: string;
|
toolTip: string;
|
||||||
title: DeviceSetting;
|
title: DeviceSetting;
|
||||||
|
@ -116,5 +116,5 @@ export interface DangerZoneProps {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
controlPanelState: ControlPanelState;
|
controlPanelState: ControlPanelState;
|
||||||
onReset(): void;
|
onReset(): void;
|
||||||
botDisconnected: boolean;
|
botOnline: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
interface Props {
|
export interface LockableButtonProps {
|
||||||
onClick: Function;
|
onClick: Function;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LockableButton({ onClick, disabled, children, title }: Props) {
|
export function LockableButton(props: LockableButtonProps) {
|
||||||
|
const { onClick, disabled, children, title } = props;
|
||||||
const className = disabled ? "gray" : "yellow";
|
const className = disabled ? "gray" : "yellow";
|
||||||
return <button
|
return <button
|
||||||
className={"fb-button " + className}
|
className={"fb-button " + className}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ControlPanelState } from "../interfaces";
|
import { ControlPanelState } from "../interfaces";
|
||||||
import { toggleControlPanel } from "../actions";
|
import { toggleControlPanel, bulkToggleControlPanel } from "../actions";
|
||||||
import { urlFriendly } from "../../util";
|
import { urlFriendly } from "../../util";
|
||||||
import { DeviceSetting } from "../../constants";
|
import { DeviceSetting } from "../../constants";
|
||||||
import { trim } from "lodash";
|
import { trim } from "lodash";
|
||||||
|
@ -150,12 +150,16 @@ const getHighlightName = () => location.search.split("?highlight=").pop();
|
||||||
export const highlight = { opened: false, highlighted: false };
|
export const highlight = { opened: false, highlighted: false };
|
||||||
|
|
||||||
/** Open a panel if a setting in that panel is highlighted. */
|
/** Open a panel if a setting in that panel is highlighted. */
|
||||||
export const maybeOpenPanel = (panelState: ControlPanelState) =>
|
export const maybeOpenPanel = (
|
||||||
|
panelState: ControlPanelState,
|
||||||
|
closeOthers = false,
|
||||||
|
) =>
|
||||||
(dispatch: Function) => {
|
(dispatch: Function) => {
|
||||||
if (highlight.opened) { return; }
|
if (highlight.opened) { return; }
|
||||||
const urlFriendlySettingName = urlFriendly(getHighlightName() || "");
|
const urlFriendlySettingName = urlFriendly(getHighlightName() || "");
|
||||||
if (!urlFriendlySettingName) { return; }
|
if (!urlFriendlySettingName) { return; }
|
||||||
const panel = URL_FRIENDLY_LOOKUP[urlFriendlySettingName];
|
const panel = URL_FRIENDLY_LOOKUP[urlFriendlySettingName];
|
||||||
|
closeOthers && dispatch(bulkToggleControlPanel(false, closeOthers));
|
||||||
const panelIsOpen = panelState[panel];
|
const panelIsOpen = panelState[panel];
|
||||||
if (panelIsOpen) { return; }
|
if (panelIsOpen) { return; }
|
||||||
dispatch(toggleControlPanel(panel));
|
dispatch(toggleControlPanel(panel));
|
||||||
|
@ -176,6 +180,7 @@ export interface HighlightProps {
|
||||||
settingName: DeviceSetting;
|
settingName: DeviceSetting;
|
||||||
children: React.ReactChild
|
children: React.ReactChild
|
||||||
| React.ReactChild[]
|
| React.ReactChild[]
|
||||||
|
| (React.ReactChild | false)[]
|
||||||
| (React.ReactChild | React.ReactChild[])[];
|
| (React.ReactChild | React.ReactChild[])[];
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
@ -196,7 +201,10 @@ export class Highlight extends React.Component<HighlightProps, HighlightState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className={`${this.props.className} ${this.state.className}`}>
|
return <div className={[
|
||||||
|
this.props.className,
|
||||||
|
this.state.className,
|
||||||
|
].join(" ")}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,50 +5,65 @@ import { Row, Col, Help } from "../../ui/index";
|
||||||
import { Position } from "@blueprintjs/core";
|
import { Position } from "@blueprintjs/core";
|
||||||
import { Highlight } from "./maybe_highlight";
|
import { Highlight } from "./maybe_highlight";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
|
|
||||||
export function NumericMCUInputGroup(props: NumericMCUInputGroupProps) {
|
export class NumericMCUInputGroup
|
||||||
|
extends React.Component<NumericMCUInputGroupProps> {
|
||||||
|
|
||||||
const {
|
get newFormat() { return DevSettings.futureFeaturesEnabled(); }
|
||||||
sourceFwConfig, dispatch, tooltip, label, x, y, z, intSize, gray, float,
|
|
||||||
} = props;
|
Inputs = () => {
|
||||||
return <Row>
|
const {
|
||||||
<Highlight settingName={label}>
|
sourceFwConfig, dispatch, intSize, gray, float,
|
||||||
<Col xs={6} className={"widget-body-tooltips"}>
|
x, y, z, xScale, yScale, zScale,
|
||||||
<label>
|
} = this.props;
|
||||||
{t(label)}
|
return <div className={"mcu-inputs"}>
|
||||||
</label>
|
<Col xs={this.newFormat ? 4 : 2}>
|
||||||
<Help text={tooltip} requireClick={true} position={Position.RIGHT} />
|
|
||||||
</Col>
|
|
||||||
<Col xs={2}>
|
|
||||||
<McuInputBox
|
<McuInputBox
|
||||||
setting={x}
|
setting={x}
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
intSize={intSize}
|
intSize={intSize}
|
||||||
float={float}
|
float={float}
|
||||||
scale={props.xScale}
|
scale={xScale}
|
||||||
gray={gray?.x} />
|
gray={gray?.x} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={2}>
|
<Col xs={this.newFormat ? 4 : 2}>
|
||||||
<McuInputBox
|
<McuInputBox
|
||||||
setting={y}
|
setting={y}
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
intSize={intSize}
|
intSize={intSize}
|
||||||
float={float}
|
float={float}
|
||||||
scale={props.yScale}
|
scale={yScale}
|
||||||
gray={gray?.y} />
|
gray={gray?.y} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={2}>
|
<Col xs={this.newFormat ? 4 : 2}>
|
||||||
<McuInputBox
|
<McuInputBox
|
||||||
setting={z}
|
setting={z}
|
||||||
sourceFwConfig={sourceFwConfig}
|
sourceFwConfig={sourceFwConfig}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
intSize={intSize}
|
intSize={intSize}
|
||||||
float={float}
|
float={float}
|
||||||
scale={props.zScale}
|
scale={zScale}
|
||||||
gray={gray?.z} />
|
gray={gray?.z} />
|
||||||
</Col>
|
</Col>
|
||||||
</Highlight>
|
</div>;
|
||||||
</Row>;
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { tooltip, label } = this.props;
|
||||||
|
return <Highlight settingName={label}>
|
||||||
|
<Row>
|
||||||
|
<Col xs={this.newFormat ? 12 : 6} className={"widget-body-tooltips"}>
|
||||||
|
<label>
|
||||||
|
{t(label)}
|
||||||
|
</label>
|
||||||
|
<Help text={tooltip} requireClick={true} position={Position.TOP_RIGHT} />
|
||||||
|
</Col>
|
||||||
|
{!this.newFormat && <this.Inputs />}
|
||||||
|
</Row>
|
||||||
|
{this.newFormat && <Row><this.Inputs /></Row>}
|
||||||
|
</Highlight>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,108 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { McuInputBox } from "./mcu_input_box";
|
import { McuInputBox } from "./mcu_input_box";
|
||||||
import { PinGuardMCUInputGroupProps } from "./interfaces";
|
import { PinGuardMCUInputGroupProps } from "./interfaces";
|
||||||
import { Row, Col } from "../../ui/index";
|
import { Row, Col, Help } from "../../ui/index";
|
||||||
import { settingToggle } from "../actions";
|
import { settingToggle } from "../actions";
|
||||||
import { ToggleButton } from "../../controls/toggle_button";
|
import { ToggleButton } from "../../controls/toggle_button";
|
||||||
import { isUndefined } from "lodash";
|
import { isUndefined } from "lodash";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { PinNumberDropdown } from "./pin_number_dropdown";
|
import { PinNumberDropdown } from "./pin_number_dropdown";
|
||||||
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
|
import { ToolTips } from "../../constants";
|
||||||
|
import { Position } from "@blueprintjs/core";
|
||||||
|
|
||||||
export function PinGuardMCUInputGroup(props: PinGuardMCUInputGroupProps) {
|
export class PinGuardMCUInputGroup
|
||||||
|
extends React.Component<PinGuardMCUInputGroupProps> {
|
||||||
|
|
||||||
const { sourceFwConfig, dispatch, label, pinNumKey, timeoutKey, activeStateKey
|
get newFormat() { return DevSettings.futureFeaturesEnabled(); }
|
||||||
} = props;
|
|
||||||
const activeStateValue = sourceFwConfig(activeStateKey).value;
|
Number = () =>
|
||||||
const inactiveState = isUndefined(activeStateValue)
|
<PinNumberDropdown
|
||||||
? undefined
|
pinNumKey={this.props.pinNumKey}
|
||||||
: !activeStateValue;
|
dispatch={this.props.dispatch}
|
||||||
return <Row>
|
resources={this.props.resources}
|
||||||
<Col xs={3}>
|
sourceFwConfig={this.props.sourceFwConfig} />
|
||||||
<label>
|
|
||||||
{label}
|
Timeout = () =>
|
||||||
</label>
|
<McuInputBox
|
||||||
</Col>
|
setting={this.props.timeoutKey}
|
||||||
<Col xs={3}>
|
sourceFwConfig={this.props.sourceFwConfig}
|
||||||
<PinNumberDropdown
|
dispatch={this.props.dispatch}
|
||||||
pinNumKey={pinNumKey}
|
filter={32000} />
|
||||||
dispatch={dispatch}
|
|
||||||
resources={props.resources}
|
State = () => {
|
||||||
sourceFwConfig={sourceFwConfig} />
|
const { sourceFwConfig, dispatch, activeStateKey } = this.props;
|
||||||
</Col>
|
const activeStateValue = sourceFwConfig(activeStateKey).value;
|
||||||
<Col xs={4}>
|
const inactiveState = isUndefined(activeStateValue)
|
||||||
<McuInputBox
|
? undefined
|
||||||
setting={timeoutKey}
|
: !activeStateValue;
|
||||||
sourceFwConfig={sourceFwConfig}
|
return <ToggleButton
|
||||||
dispatch={dispatch}
|
customText={{ textFalse: t("low"), textTrue: t("high") }}
|
||||||
filter={32000} />
|
toggleValue={inactiveState}
|
||||||
</Col>
|
dim={!sourceFwConfig(activeStateKey).consistent}
|
||||||
<Col xs={2} className={"centered-button-div"}>
|
toggleAction={() =>
|
||||||
<ToggleButton
|
dispatch(settingToggle(activeStateKey, sourceFwConfig))} />;
|
||||||
customText={{ textFalse: t("low"), textTrue: t("high") }}
|
}
|
||||||
toggleValue={inactiveState}
|
|
||||||
dim={!sourceFwConfig(activeStateKey).consistent}
|
render() {
|
||||||
toggleAction={() =>
|
const { label } = this.props;
|
||||||
dispatch(settingToggle(activeStateKey, sourceFwConfig))} />
|
return !this.newFormat
|
||||||
</Col>
|
? <Row>
|
||||||
</Row>;
|
<Col xs={3}>
|
||||||
|
<label>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
</Col>
|
||||||
|
<Col xs={3}>
|
||||||
|
<this.Number />
|
||||||
|
</Col>
|
||||||
|
<Col xs={4}>
|
||||||
|
<this.Timeout />
|
||||||
|
</Col>
|
||||||
|
<Col xs={2} className={"centered-button-div"}>
|
||||||
|
<this.State />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
: <div className={"pin-guard-input-row"}>
|
||||||
|
<Row>
|
||||||
|
<Col xs={12}>
|
||||||
|
<label>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col xs={5} xsOffset={1} className="no-pad">
|
||||||
|
<label>
|
||||||
|
{t("Pin Number")}
|
||||||
|
</label>
|
||||||
|
<Help text={ToolTips.PIN_GUARD_PIN_NUMBER} requireClick={true}
|
||||||
|
position={Position.TOP_RIGHT} />
|
||||||
|
</Col>
|
||||||
|
<Col xs={5} className="no-pad">
|
||||||
|
<this.Number />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col xs={5} xsOffset={1} className="no-pad">
|
||||||
|
<label>
|
||||||
|
{t("Timeout (sec)")}
|
||||||
|
</label>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5} className="no-pad">
|
||||||
|
<this.Timeout />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col xs={5} xsOffset={1} className="no-pad">
|
||||||
|
<label>
|
||||||
|
{t("To State")}
|
||||||
|
</label>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5} className="no-pad">
|
||||||
|
<this.State />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,17 +5,16 @@ import { FarmbotOsSettings } from "./components/farmbot_os_settings";
|
||||||
import { Page, Col, Row } from "../ui/index";
|
import { Page, Col, Row } from "../ui/index";
|
||||||
import { mapStateToProps } from "./state_to_props";
|
import { mapStateToProps } from "./state_to_props";
|
||||||
import { Props } from "./interfaces";
|
import { Props } from "./interfaces";
|
||||||
import { getStatus } from "../connectivity/reducer_support";
|
|
||||||
import { isFwHardwareValue } from "./components/firmware_hardware_support";
|
import { isFwHardwareValue } from "./components/firmware_hardware_support";
|
||||||
|
import { maybeOpenPanel } from "./components/maybe_highlight";
|
||||||
|
|
||||||
export class RawDevices extends React.Component<Props, {}> {
|
export class RawDevices extends React.Component<Props, {}> {
|
||||||
|
|
||||||
|
componentDidMount = () =>
|
||||||
|
this.props.dispatch(maybeOpenPanel(this.props.bot.controlPanelState));
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.auth) {
|
if (this.props.auth) {
|
||||||
const { botToMqtt } = this.props;
|
|
||||||
const botToMqttStatus = getStatus(botToMqtt);
|
|
||||||
const botToMqttLastSeen = (botToMqtt && botToMqttStatus === "up")
|
|
||||||
? botToMqtt.at
|
|
||||||
: "";
|
|
||||||
const { value } = this.props.sourceFbosConfig("firmware_hardware");
|
const { value } = this.props.sourceFbosConfig("firmware_hardware");
|
||||||
const firmwareHardware = isFwHardwareValue(value) ? value : undefined;
|
const firmwareHardware = isFwHardwareValue(value) ? value : undefined;
|
||||||
return <Page className="device-page">
|
return <Page className="device-page">
|
||||||
|
@ -27,14 +26,10 @@ export class RawDevices extends React.Component<Props, {}> {
|
||||||
alerts={this.props.alerts}
|
alerts={this.props.alerts}
|
||||||
bot={this.props.bot}
|
bot={this.props.bot}
|
||||||
timeSettings={this.props.timeSettings}
|
timeSettings={this.props.timeSettings}
|
||||||
botToMqttLastSeen={new Date(botToMqttLastSeen).getTime()}
|
|
||||||
botToMqttStatus={botToMqttStatus}
|
|
||||||
sourceFbosConfig={this.props.sourceFbosConfig}
|
sourceFbosConfig={this.props.sourceFbosConfig}
|
||||||
shouldDisplay={this.props.shouldDisplay}
|
shouldDisplay={this.props.shouldDisplay}
|
||||||
isValidFbosConfig={this.props.isValidFbosConfig}
|
|
||||||
env={this.props.env}
|
env={this.props.env}
|
||||||
saveFarmwareEnv={this.props.saveFarmwareEnv}
|
saveFarmwareEnv={this.props.saveFarmwareEnv} />
|
||||||
webAppConfig={this.props.webAppConfig} />
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={12} sm={6}>
|
<Col xs={12} sm={6}>
|
||||||
<HardwareSettings
|
<HardwareSettings
|
||||||
|
@ -42,7 +37,6 @@ export class RawDevices extends React.Component<Props, {}> {
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
resources={this.props.resources}
|
resources={this.props.resources}
|
||||||
bot={this.props.bot}
|
bot={this.props.bot}
|
||||||
botToMqttStatus={botToMqttStatus}
|
|
||||||
shouldDisplay={this.props.shouldDisplay}
|
shouldDisplay={this.props.shouldDisplay}
|
||||||
firmwareHardware={firmwareHardware}
|
firmwareHardware={firmwareHardware}
|
||||||
sourceFwConfig={this.props.sourceFwConfig}
|
sourceFwConfig={this.props.sourceFwConfig}
|
||||||
|
|
|
@ -10,13 +10,10 @@ import {
|
||||||
JobProgress,
|
JobProgress,
|
||||||
FirmwareHardware,
|
FirmwareHardware,
|
||||||
Alert,
|
Alert,
|
||||||
TaggedWebAppConfig,
|
|
||||||
} from "farmbot";
|
} from "farmbot";
|
||||||
import { ResourceIndex } from "../resources/interfaces";
|
import { ResourceIndex } from "../resources/interfaces";
|
||||||
import { WD_ENV } from "../farmware/weed_detector/remote_env/interfaces";
|
import { WD_ENV } from "../farmware/weed_detector/remote_env/interfaces";
|
||||||
import {
|
import { ConnectionState, NetworkState } from "../connectivity/interfaces";
|
||||||
ConnectionStatus, ConnectionState, NetworkState,
|
|
||||||
} from "../connectivity/interfaces";
|
|
||||||
import { IntegerSize } from "../util";
|
import { IntegerSize } from "../util";
|
||||||
import { Farmwares } from "../farmware/interfaces";
|
import { Farmwares } from "../farmware/interfaces";
|
||||||
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
||||||
|
@ -24,9 +21,6 @@ import { GetWebAppConfigValue } from "../config_storage/actions";
|
||||||
import { TimeSettings } from "../interfaces";
|
import { TimeSettings } from "../interfaces";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
userToApi: ConnectionStatus | undefined;
|
|
||||||
userToMqtt: ConnectionStatus | undefined;
|
|
||||||
botToMqtt: ConnectionStatus | undefined;
|
|
||||||
auth: AuthState | undefined;
|
auth: AuthState | undefined;
|
||||||
bot: BotState;
|
bot: BotState;
|
||||||
deviceAccount: TaggedDevice;
|
deviceAccount: TaggedDevice;
|
||||||
|
@ -37,12 +31,10 @@ export interface Props {
|
||||||
sourceFwConfig: SourceFwConfig;
|
sourceFwConfig: SourceFwConfig;
|
||||||
shouldDisplay: ShouldDisplay;
|
shouldDisplay: ShouldDisplay;
|
||||||
firmwareConfig: FirmwareConfig | undefined;
|
firmwareConfig: FirmwareConfig | undefined;
|
||||||
isValidFbosConfig: boolean;
|
|
||||||
env: UserEnv;
|
env: UserEnv;
|
||||||
saveFarmwareEnv: SaveFarmwareEnv;
|
saveFarmwareEnv: SaveFarmwareEnv;
|
||||||
timeSettings: TimeSettings;
|
timeSettings: TimeSettings;
|
||||||
alerts: Alert[];
|
alerts: Alert[];
|
||||||
webAppConfig: TaggedWebAppConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Function to save a Farmware env variable to the API. */
|
/** Function to save a Farmware env variable to the API. */
|
||||||
|
@ -112,6 +104,8 @@ export interface BotState {
|
||||||
currentBetaOSCommit?: string;
|
currentBetaOSCommit?: string;
|
||||||
/** JSON string of minimum required FBOS versions for various features. */
|
/** JSON string of minimum required FBOS versions for various features. */
|
||||||
minOsFeatureData?: MinOsFeatureLookup;
|
minOsFeatureData?: MinOsFeatureLookup;
|
||||||
|
/** Notes notifying users of changes that may require intervention. */
|
||||||
|
osReleaseNotes?: string;
|
||||||
/** Is the bot in sync with the api */
|
/** Is the bot in sync with the api */
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
/** The state of the bot, as reported by the bot over MQTT. */
|
/** The state of the bot, as reported by the bot over MQTT. */
|
||||||
|
@ -164,20 +158,25 @@ export interface FarmbotOsProps {
|
||||||
bot: BotState;
|
bot: BotState;
|
||||||
alerts: Alert[];
|
alerts: Alert[];
|
||||||
deviceAccount: TaggedDevice;
|
deviceAccount: TaggedDevice;
|
||||||
botToMqttStatus: NetworkState;
|
|
||||||
botToMqttLastSeen: number;
|
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
sourceFbosConfig: SourceFbosConfig;
|
sourceFbosConfig: SourceFbosConfig;
|
||||||
shouldDisplay: ShouldDisplay;
|
shouldDisplay: ShouldDisplay;
|
||||||
isValidFbosConfig: boolean;
|
|
||||||
env: UserEnv;
|
env: UserEnv;
|
||||||
saveFarmwareEnv: SaveFarmwareEnv;
|
saveFarmwareEnv: SaveFarmwareEnv;
|
||||||
timeSettings: TimeSettings;
|
timeSettings: TimeSettings;
|
||||||
webAppConfig: TaggedWebAppConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FarmbotOsState {
|
export interface FarmbotSettingsProps {
|
||||||
allOsReleaseNotes: string;
|
bot: BotState;
|
||||||
|
alerts: Alert[];
|
||||||
|
device: TaggedDevice;
|
||||||
|
dispatch: Function;
|
||||||
|
sourceFbosConfig: SourceFbosConfig;
|
||||||
|
shouldDisplay: ShouldDisplay;
|
||||||
|
env: UserEnv;
|
||||||
|
saveFarmwareEnv: SaveFarmwareEnv;
|
||||||
|
timeSettings: TimeSettings;
|
||||||
|
botOnline: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface McuInputBoxProps {
|
export interface McuInputBoxProps {
|
||||||
|
@ -235,7 +234,6 @@ export interface FarmwareProps {
|
||||||
export interface HardwareSettingsProps {
|
export interface HardwareSettingsProps {
|
||||||
controlPanelState: ControlPanelState;
|
controlPanelState: ControlPanelState;
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
botToMqttStatus: NetworkState;
|
|
||||||
bot: BotState;
|
bot: BotState;
|
||||||
shouldDisplay: ShouldDisplay;
|
shouldDisplay: ShouldDisplay;
|
||||||
sourceFwConfig: SourceFwConfig;
|
sourceFwConfig: SourceFwConfig;
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { NetworkState } from "../connectivity/interfaces";
|
||||||
import { SyncStatus } from "farmbot";
|
import { SyncStatus } from "farmbot";
|
||||||
import { Content } from "../constants";
|
import { Content } from "../constants";
|
||||||
import { t } from "../i18next_wrapper";
|
import { t } from "../i18next_wrapper";
|
||||||
|
import { BotState } from "./interfaces";
|
||||||
|
import { getStatus } from "../connectivity/reducer_support";
|
||||||
|
|
||||||
/** Properties for the <MustBeOnline/> element. */
|
/** Properties for the <MustBeOnline/> element. */
|
||||||
export interface MBOProps {
|
export interface MBOProps {
|
||||||
|
@ -23,6 +25,12 @@ export function isBotOnline(
|
||||||
return !!(isBotUp(syncStatus) && botToMqttStatus === "up");
|
return !!(isBotUp(syncStatus) && botToMqttStatus === "up");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isBotOnlineFromState(bot: BotState) {
|
||||||
|
const { sync_status } = bot.hardware.informational_settings;
|
||||||
|
const { uptime } = bot.connectivity;
|
||||||
|
return isBotOnline(sync_status, getStatus(uptime["bot.mqtt"]));
|
||||||
|
}
|
||||||
|
|
||||||
export function MustBeOnline(props: MBOProps) {
|
export function MustBeOnline(props: MBOProps) {
|
||||||
const { children, hideBanner, lockOpen, networkState, syncStatus } = props;
|
const { children, hideBanner, lockOpen, networkState, syncStatus } = props;
|
||||||
const banner = hideBanner ? "" : "banner";
|
const banner = hideBanner ? "" : "banner";
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
PinBindingType, PinBindingSpecialAction,
|
PinBindingType, PinBindingSpecialAction,
|
||||||
} from "farmbot/dist/resources/api_resources";
|
} from "farmbot/dist/resources/api_resources";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
|
|
||||||
export class PinBindingInputGroup
|
export class PinBindingInputGroup
|
||||||
extends React.Component<PinBindingInputGroupProps, PinBindingInputGroupState> {
|
extends React.Component<PinBindingInputGroupProps, PinBindingInputGroupState> {
|
||||||
|
@ -104,41 +105,62 @@ export class PinBindingInputGroup
|
||||||
(ddi: { label: string, value: PinBindingSpecialAction }) =>
|
(ddi: { label: string, value: PinBindingSpecialAction }) =>
|
||||||
this.setState({ specialActionInput: ddi.value });
|
this.setState({ specialActionInput: ddi.value });
|
||||||
|
|
||||||
render() {
|
Number = () =>
|
||||||
const {
|
<PinNumberInputGroup
|
||||||
pinNumberInput, bindingType, specialActionInput, sequenceIdInput
|
pinNumberInput={this.state.pinNumberInput}
|
||||||
} = this.state;
|
boundPins={this.boundPins}
|
||||||
|
setSelectedPin={this.setSelectedPin} />
|
||||||
|
|
||||||
return <Row>
|
Type = () =>
|
||||||
<Col xs={PinBindingColWidth.pin}>
|
<BindingTypeDropDown
|
||||||
<PinNumberInputGroup
|
bindingType={this.state.bindingType}
|
||||||
pinNumberInput={pinNumberInput}
|
setBindingType={this.setBindingType} />
|
||||||
boundPins={this.boundPins}
|
|
||||||
setSelectedPin={this.setSelectedPin} />
|
Action = () =>
|
||||||
</Col>
|
this.state.bindingType == PinBindingType.special
|
||||||
<Col xs={PinBindingColWidth.type}>
|
? <ActionTargetDropDown
|
||||||
<BindingTypeDropDown
|
specialActionInput={this.state.specialActionInput}
|
||||||
bindingType={bindingType}
|
setSpecialAction={this.setSpecialAction} />
|
||||||
setBindingType={this.setBindingType} />
|
: <SequenceTargetDropDown
|
||||||
{bindingType == PinBindingType.special
|
sequenceIdInput={this.state.sequenceIdInput}
|
||||||
? <ActionTargetDropDown
|
resources={this.props.resources}
|
||||||
specialActionInput={specialActionInput}
|
setSequenceIdInput={this.setSequenceIdInput} />
|
||||||
setSpecialAction={this.setSpecialAction} />
|
|
||||||
: <SequenceTargetDropDown
|
render() {
|
||||||
sequenceIdInput={sequenceIdInput}
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
resources={this.props.resources}
|
return <div className="pin-binding-input-rows">
|
||||||
setSequenceIdInput={this.setSequenceIdInput} />}
|
{newFormat && <Row><label>{t("add new pin binding")}</label></Row>}
|
||||||
</Col>
|
{newFormat && <this.Number />}
|
||||||
<Col xs={PinBindingColWidth.button}>
|
{newFormat && <Row>
|
||||||
<button
|
<Col xs={5}>
|
||||||
className="fb-button green"
|
<this.Type />
|
||||||
type="button"
|
</Col>
|
||||||
title={t("BIND")}
|
<Col xs={7}>
|
||||||
onClick={this.bindPin}>
|
<this.Action />
|
||||||
<i className={"fa fa-plus"} />
|
</Col>
|
||||||
</button>
|
</Row>}
|
||||||
</Col>
|
<Row>
|
||||||
</Row>;
|
{!newFormat &&
|
||||||
|
<Col xs={PinBindingColWidth.pin}>
|
||||||
|
<this.Number />
|
||||||
|
</Col>}
|
||||||
|
{!newFormat && <Col xs={PinBindingColWidth.type}>
|
||||||
|
<this.Type />
|
||||||
|
<this.Action />
|
||||||
|
</Col>}
|
||||||
|
<Col xs={newFormat ? 12 : PinBindingColWidth.button}>
|
||||||
|
<button
|
||||||
|
className="fb-button green"
|
||||||
|
type="button"
|
||||||
|
title={t("BIND")}
|
||||||
|
onClick={this.bindPin}>
|
||||||
|
{newFormat
|
||||||
|
? t("Save")
|
||||||
|
: <i className={"fa fa-plus"} />}
|
||||||
|
</button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,9 +175,9 @@ export const PinNumberInputGroup = (props: {
|
||||||
label: generatePinLabel(pinNumberInput),
|
label: generatePinLabel(pinNumberInput),
|
||||||
value: "" + pinNumberInput
|
value: "" + pinNumberInput
|
||||||
} : undefined;
|
} : undefined;
|
||||||
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
return <Row>
|
return <Row>
|
||||||
<Col xs={3}>
|
<Col xs={newFormat ? 2 : 3}>
|
||||||
<Popover position={Position.TOP}>
|
<Popover position={Position.TOP}>
|
||||||
<i className="fa fa-th-large" />
|
<i className="fa fa-th-large" />
|
||||||
<RpiGpioDiagram
|
<RpiGpioDiagram
|
||||||
|
@ -164,7 +186,7 @@ export const PinNumberInputGroup = (props: {
|
||||||
selectedPin={pinNumberInput} />
|
selectedPin={pinNumberInput} />
|
||||||
</Popover>
|
</Popover>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={9}>
|
<Col xs={newFormat ? 10 : 9}>
|
||||||
<FBSelect
|
<FBSelect
|
||||||
key={"pin_number_input_" + pinNumberInput}
|
key={"pin_number_input_" + pinNumberInput}
|
||||||
onChange={ddi =>
|
onChange={ddi =>
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
PinBinding,
|
PinBinding,
|
||||||
} from "farmbot/dist/resources/api_resources";
|
} from "farmbot/dist/resources/api_resources";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
|
|
||||||
/** Width of UI columns in Pin Bindings widget. */
|
/** Width of UI columns in Pin Bindings widget. */
|
||||||
export enum PinBindingColWidth {
|
export enum PinBindingColWidth {
|
||||||
|
@ -70,13 +71,15 @@ const PinBindingsListHeader = () =>
|
||||||
export const PinBindingsContent = (props: PinBindingsContentProps) => {
|
export const PinBindingsContent = (props: PinBindingsContentProps) => {
|
||||||
const { dispatch, resources, firmwareHardware } = props;
|
const { dispatch, resources, firmwareHardware } = props;
|
||||||
const pinBindings = apiPinBindings(resources);
|
const pinBindings = apiPinBindings(resources);
|
||||||
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
return <div className="pin-bindings">
|
return <div className="pin-bindings">
|
||||||
<Row>
|
<Row>
|
||||||
|
{newFormat && <Help text={ToolTips.PIN_BINDINGS}
|
||||||
|
position={Position.TOP_RIGHT} requireClick={true} />}
|
||||||
<StockPinBindingsButton
|
<StockPinBindingsButton
|
||||||
dispatch={dispatch} firmwareHardware={firmwareHardware} />
|
dispatch={dispatch} firmwareHardware={firmwareHardware} />
|
||||||
<Popover
|
<Popover
|
||||||
position={Position.RIGHT_TOP}
|
position={Position.TOP_RIGHT}
|
||||||
interactionKind={PopoverInteractionKind.HOVER}
|
interactionKind={PopoverInteractionKind.HOVER}
|
||||||
portalClassName={"bindings-warning-icon"}
|
portalClassName={"bindings-warning-icon"}
|
||||||
popoverClassName={"help"}>
|
popoverClassName={"help"}>
|
||||||
|
@ -87,7 +90,7 @@ export const PinBindingsContent = (props: PinBindingsContentProps) => {
|
||||||
</Popover>
|
</Popover>
|
||||||
</Row>
|
</Row>
|
||||||
<div className={"pin-bindings-list-and-input"}>
|
<div className={"pin-bindings-list-and-input"}>
|
||||||
<PinBindingsListHeader />
|
{!newFormat && <PinBindingsListHeader />}
|
||||||
<PinBindingsList
|
<PinBindingsList
|
||||||
pinBindings={pinBindings}
|
pinBindings={pinBindings}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
|
|
|
@ -11,6 +11,10 @@ import { PinBindingColWidth } from "./pin_bindings";
|
||||||
import { PinBindingsListProps } from "./interfaces";
|
import { PinBindingsListProps } from "./interfaces";
|
||||||
import { sysBtnBindings } from "./tagged_pin_binding_init";
|
import { sysBtnBindings } from "./tagged_pin_binding_init";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
|
import {
|
||||||
|
PinBindingType, PinBindingSpecialAction,
|
||||||
|
} from "farmbot/dist/resources/api_resources";
|
||||||
|
|
||||||
export const PinBindingsList = (props: PinBindingsListProps) => {
|
export const PinBindingsList = (props: PinBindingsListProps) => {
|
||||||
const { pinBindings, resources, dispatch } = props;
|
const { pinBindings, resources, dispatch } = props;
|
||||||
|
@ -26,22 +30,33 @@ export const PinBindingsList = (props: PinBindingsListProps) => {
|
||||||
const delBtnColor = (pin: number) =>
|
const delBtnColor = (pin: number) =>
|
||||||
sysBtnBindings.includes(pin) ? "pseudo-disabled" : "red";
|
sysBtnBindings.includes(pin) ? "pseudo-disabled" : "red";
|
||||||
|
|
||||||
|
const bindingText = (
|
||||||
|
sequence_id: number | undefined,
|
||||||
|
binding_type: PinBindingType | undefined,
|
||||||
|
special_action: PinBindingSpecialAction | undefined,
|
||||||
|
) =>
|
||||||
|
`${t(bindingTypeLabelLookup[binding_type || ""])}: ${(sequence_id
|
||||||
|
? findSequenceById(resources, sequence_id).body.name
|
||||||
|
: t(getSpecialActionLabel(special_action)))}`;
|
||||||
|
|
||||||
|
const newFormat = DevSettings.futureFeaturesEnabled();
|
||||||
return <div className={"bindings-list"}>
|
return <div className={"bindings-list"}>
|
||||||
|
{newFormat && <Row><label>{t("saved pin bindings")}</label></Row>}
|
||||||
{pinBindings
|
{pinBindings
|
||||||
.sort((a, b) => sortByNameAndPin(a.pin_number, b.pin_number))
|
.sort((a, b) => sortByNameAndPin(a.pin_number, b.pin_number))
|
||||||
.map(x => {
|
.map(x => {
|
||||||
const { pin_number, sequence_id, binding_type, special_action } = x;
|
const { pin_number, sequence_id, binding_type, special_action } = x;
|
||||||
|
const binding = bindingText(sequence_id, binding_type, special_action);
|
||||||
return <Row key={`pin_${pin_number}_binding`}>
|
return <Row key={`pin_${pin_number}_binding`}>
|
||||||
<Col xs={PinBindingColWidth.pin}>
|
<Col xs={newFormat ? 11 : PinBindingColWidth.pin}>
|
||||||
{generatePinLabel(pin_number)}
|
<p>{generatePinLabel(pin_number)}</p>
|
||||||
|
<p className="binding-action">{newFormat && binding}</p>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={PinBindingColWidth.type}>
|
{!newFormat &&
|
||||||
{t(bindingTypeLabelLookup[binding_type || ""])}:
|
<Col xs={PinBindingColWidth.type}>
|
||||||
{sequence_id
|
{binding}
|
||||||
? findSequenceById(resources, sequence_id).body.name
|
</Col>}
|
||||||
: t(getSpecialActionLabel(special_action))}
|
<Col xs={newFormat ? 1 : PinBindingColWidth.button}>
|
||||||
</Col>
|
|
||||||
<Col xs={PinBindingColWidth.button}>
|
|
||||||
<button
|
<button
|
||||||
className={`fb-button ${delBtnColor(pin_number)} del-button`}
|
className={`fb-button ${delBtnColor(pin_number)} del-button`}
|
||||||
title={t("Delete")}
|
title={t("Delete")}
|
||||||
|
|
|
@ -35,7 +35,7 @@ export const initialState = (): BotState => ({
|
||||||
pin_guard: false,
|
pin_guard: false,
|
||||||
farm_designer: false,
|
farm_designer: false,
|
||||||
firmware: false,
|
firmware: false,
|
||||||
farmbot_os: false,
|
farmbot_os: true,
|
||||||
},
|
},
|
||||||
hardware: {
|
hardware: {
|
||||||
gpio_registry: {},
|
gpio_registry: {},
|
||||||
|
@ -79,6 +79,7 @@ export const initialState = (): BotState => ({
|
||||||
currentOSVersion: undefined,
|
currentOSVersion: undefined,
|
||||||
currentBetaOSVersion: undefined,
|
currentBetaOSVersion: undefined,
|
||||||
minOsFeatureData: undefined,
|
minOsFeatureData: undefined,
|
||||||
|
osReleaseNotes: undefined,
|
||||||
connectivity: {
|
connectivity: {
|
||||||
uptime: {
|
uptime: {
|
||||||
"bot.mqtt": undefined,
|
"bot.mqtt": undefined,
|
||||||
|
@ -118,21 +119,24 @@ export const botReducer = generateReducer<BotState>(initialState())
|
||||||
s.controlPanelState[a.payload] = !s.controlPanelState[a.payload];
|
s.controlPanelState[a.payload] = !s.controlPanelState[a.payload];
|
||||||
return s;
|
return s;
|
||||||
})
|
})
|
||||||
.add<boolean>(Actions.BULK_TOGGLE_CONTROL_PANEL, (s, a) => {
|
.add<{ open: boolean, all: boolean }>(
|
||||||
s.controlPanelState.homing_and_calibration = a.payload;
|
Actions.BULK_TOGGLE_CONTROL_PANEL, (s, a) => {
|
||||||
s.controlPanelState.motors = a.payload;
|
s.controlPanelState.homing_and_calibration = a.payload.open;
|
||||||
s.controlPanelState.encoders = a.payload;
|
s.controlPanelState.motors = a.payload.open;
|
||||||
s.controlPanelState.endstops = a.payload;
|
s.controlPanelState.encoders = a.payload.open;
|
||||||
s.controlPanelState.error_handling = a.payload;
|
s.controlPanelState.endstops = a.payload.open;
|
||||||
s.controlPanelState.pin_bindings = a.payload;
|
s.controlPanelState.error_handling = a.payload.open;
|
||||||
s.controlPanelState.pin_guard = a.payload;
|
s.controlPanelState.pin_bindings = a.payload.open;
|
||||||
s.controlPanelState.danger_zone = a.payload;
|
s.controlPanelState.pin_guard = a.payload.open;
|
||||||
s.controlPanelState.power_and_reset = a.payload;
|
s.controlPanelState.danger_zone = a.payload.open;
|
||||||
s.controlPanelState.farm_designer = a.payload;
|
if (a.payload.all) {
|
||||||
s.controlPanelState.firmware = a.payload;
|
s.controlPanelState.power_and_reset = a.payload.open;
|
||||||
s.controlPanelState.farmbot_os = a.payload;
|
s.controlPanelState.farm_designer = a.payload.open;
|
||||||
return s;
|
s.controlPanelState.firmware = a.payload.open;
|
||||||
})
|
s.controlPanelState.farmbot_os = a.payload.open;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
})
|
||||||
.add<OsUpdateInfo>(Actions.FETCH_OS_UPDATE_INFO_OK, (s, { payload }) => {
|
.add<OsUpdateInfo>(Actions.FETCH_OS_UPDATE_INFO_OK, (s, { payload }) => {
|
||||||
s.currentOSVersion = payload.version;
|
s.currentOSVersion = payload.version;
|
||||||
return s;
|
return s;
|
||||||
|
@ -147,6 +151,11 @@ export const botReducer = generateReducer<BotState>(initialState())
|
||||||
s.minOsFeatureData = payload;
|
s.minOsFeatureData = payload;
|
||||||
return s;
|
return s;
|
||||||
})
|
})
|
||||||
|
.add<string>(Actions.FETCH_OS_RELEASE_NOTES_OK,
|
||||||
|
(s, { payload }) => {
|
||||||
|
s.osReleaseNotes = payload;
|
||||||
|
return s;
|
||||||
|
})
|
||||||
.add<DeepPartial<HardwareState>>(Actions.STATUS_UPDATE, (s, { payload }) => {
|
.add<DeepPartial<HardwareState>>(Actions.STATUS_UPDATE, (s, { payload }) => {
|
||||||
s.hardware = merge(s.hardware, payload);
|
s.hardware = merge(s.hardware, payload);
|
||||||
legacyStatusHandler(s, incomingLegacyStatus(s.hardware));
|
legacyStatusHandler(s, incomingLegacyStatus(s.hardware));
|
||||||
|
|
|
@ -12,9 +12,7 @@ import { validFwConfig, validFbosConfig } from "../util";
|
||||||
import {
|
import {
|
||||||
saveOrEditFarmwareEnv, getEnv, getShouldDisplayFn,
|
saveOrEditFarmwareEnv, getEnv, getShouldDisplayFn,
|
||||||
} from "../farmware/state_to_props";
|
} from "../farmware/state_to_props";
|
||||||
import {
|
import { getFbosConfig, getFirmwareConfig } from "../resources/getters";
|
||||||
getFbosConfig, getFirmwareConfig, getWebAppConfig,
|
|
||||||
} from "../resources/getters";
|
|
||||||
import { getAllAlerts } from "../messages/state_to_props";
|
import { getAllAlerts } from "../messages/state_to_props";
|
||||||
|
|
||||||
export function mapStateToProps(props: Everything): Props {
|
export function mapStateToProps(props: Everything): Props {
|
||||||
|
@ -23,14 +21,7 @@ export function mapStateToProps(props: Everything): Props {
|
||||||
const firmwareConfig = validFwConfig(getFirmwareConfig(props.resources.index));
|
const firmwareConfig = validFwConfig(getFirmwareConfig(props.resources.index));
|
||||||
const shouldDisplay = getShouldDisplayFn(props.resources.index, props.bot);
|
const shouldDisplay = getShouldDisplayFn(props.resources.index, props.bot);
|
||||||
const env = getEnv(props.resources.index, shouldDisplay, props.bot);
|
const env = getEnv(props.resources.index, shouldDisplay, props.bot);
|
||||||
const webAppConfig = getWebAppConfig(props.resources.index);
|
|
||||||
if (!webAppConfig) {
|
|
||||||
throw new Error("Missing web app config");
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
userToApi: props.bot.connectivity.uptime["user.api"],
|
|
||||||
userToMqtt: props.bot.connectivity.uptime["user.mqtt"],
|
|
||||||
botToMqtt: props.bot.connectivity.uptime["bot.mqtt"],
|
|
||||||
deviceAccount: getDeviceAccountSettings(props.resources.index),
|
deviceAccount: getDeviceAccountSettings(props.resources.index),
|
||||||
auth: props.auth,
|
auth: props.auth,
|
||||||
bot: props.bot,
|
bot: props.bot,
|
||||||
|
@ -41,11 +32,9 @@ export function mapStateToProps(props: Everything): Props {
|
||||||
sourceFwConfig: sourceFwConfigValue(firmwareConfig, hardware.mcu_params),
|
sourceFwConfig: sourceFwConfigValue(firmwareConfig, hardware.mcu_params),
|
||||||
shouldDisplay,
|
shouldDisplay,
|
||||||
firmwareConfig,
|
firmwareConfig,
|
||||||
isValidFbosConfig: !!fbosConfig,
|
|
||||||
env,
|
env,
|
||||||
saveFarmwareEnv: saveOrEditFarmwareEnv(props.resources.index),
|
saveFarmwareEnv: saveOrEditFarmwareEnv(props.resources.index),
|
||||||
timeSettings: maybeGetTimeSettings(props.resources.index),
|
timeSettings: maybeGetTimeSettings(props.resources.index),
|
||||||
alerts: getAllAlerts(props.resources),
|
alerts: getAllAlerts(props.resources),
|
||||||
webAppConfig
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe("findBySlug()", () => {
|
||||||
it("returns crop default result: no slug provided", () => {
|
it("returns crop default result: no slug provided", () => {
|
||||||
const result = findBySlug([fakeCropLiveSearchResult()]);
|
const result = findBySlug([fakeCropLiveSearchResult()]);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
crop: expect.objectContaining({ name: "Name" }),
|
crop: expect.objectContaining({ name: "" }),
|
||||||
image: DEFAULT_ICON
|
image: DEFAULT_ICON
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,19 +15,16 @@ import {
|
||||||
DesignerPanel, DesignerPanelContent, DesignerPanelHeader,
|
DesignerPanel, DesignerPanelContent, DesignerPanelHeader,
|
||||||
} from "./designer_panel";
|
} from "./designer_panel";
|
||||||
import { t } from "../i18next_wrapper";
|
import { t } from "../i18next_wrapper";
|
||||||
import { isBotOnline } from "../devices/must_be_online";
|
import { isBotOnlineFromState } from "../devices/must_be_online";
|
||||||
import { getStatus } from "../connectivity/reducer_support";
|
|
||||||
import { PanelColor } from "./panel_header";
|
import { PanelColor } from "./panel_header";
|
||||||
|
|
||||||
export function mapStateToProps(props: Everything): MoveToProps {
|
export function mapStateToProps(props: Everything): MoveToProps {
|
||||||
const botToMqttStatus = getStatus(props.bot.connectivity.uptime["bot.mqtt"]);
|
|
||||||
const { sync_status } = props.bot.hardware.informational_settings;
|
|
||||||
return {
|
return {
|
||||||
chosenLocation: props.resources.consumers.farm_designer.chosenLocation,
|
chosenLocation: props.resources.consumers.farm_designer.chosenLocation,
|
||||||
currentBotLocation:
|
currentBotLocation:
|
||||||
validBotLocationData(props.bot.hardware.location_data).position,
|
validBotLocationData(props.bot.hardware.location_data).position,
|
||||||
dispatch: props.dispatch,
|
dispatch: props.dispatch,
|
||||||
botOnline: isBotOnline(sync_status, botToMqttStatus),
|
botOnline: isBotOnlineFromState(props.bot),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,9 +99,9 @@ describe("<CropInfo />", () => {
|
||||||
p.cropSearchResults[0].crop.row_spacing = undefined;
|
p.cropSearchResults[0].crop.row_spacing = undefined;
|
||||||
p.cropSearchResults[0].crop.common_names = [];
|
p.cropSearchResults[0].crop.common_names = [];
|
||||||
const wrapper = mount(<CropInfo {...p} />);
|
const wrapper = mount(<CropInfo {...p} />);
|
||||||
expect(wrapper.text().toLowerCase()).toContain("iconnot set");
|
expect(wrapper.text().toLowerCase()).toContain("iconnot available");
|
||||||
expect(wrapper.text().toLowerCase()).toContain("spacingnot set");
|
expect(wrapper.text().toLowerCase()).toContain("spacingnot available");
|
||||||
expect(wrapper.text().toLowerCase()).toContain("common namesnot set");
|
expect(wrapper.text().toLowerCase()).toContain("common namesnot available");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ const OMITTED_PROPERTIES = [
|
||||||
"guides_count",
|
"guides_count",
|
||||||
];
|
];
|
||||||
|
|
||||||
const NO_VALUE = t("Not Set");
|
const NO_VALUE = t("Not available");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there's a value, give it an img element to render the
|
* If there's a value, give it an img element to render the
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
import { CropLiveSearchResult } from "./interfaces";
|
import { CropLiveSearchResult } from "./interfaces";
|
||||||
import { DEFAULT_ICON } from "../open_farm/icons";
|
import { DEFAULT_ICON } from "../open_farm/icons";
|
||||||
import { startCase, find } from "lodash";
|
import { startCase, find } from "lodash";
|
||||||
import { t } from "../i18next_wrapper";
|
|
||||||
|
|
||||||
export function findBySlug(
|
export function findBySlug(
|
||||||
crops: CropLiveSearchResult[], slug?: string): CropLiveSearchResult {
|
crops: CropLiveSearchResult[], slug?: string): CropLiveSearchResult {
|
||||||
const crop = find(crops, result => result.crop.slug === slug);
|
const crop = find(crops, result => result.crop.slug === slug);
|
||||||
return crop || {
|
return crop || {
|
||||||
crop: {
|
crop: {
|
||||||
name: startCase((slug || t("Name")).split("-").join(" ")),
|
name: startCase((slug || "").split("-").join(" ")),
|
||||||
slug: slug || "slug",
|
slug: slug || "",
|
||||||
binomial_name: t("Binomial Name"),
|
binomial_name: "",
|
||||||
common_names: [t("Common Names")],
|
common_names: [],
|
||||||
description: t("Description"),
|
description: "",
|
||||||
sun_requirements: t("Sun Requirements"),
|
sun_requirements: "",
|
||||||
sowing_method: t("Sowing Method"),
|
sowing_method: "",
|
||||||
processing_pictures: 0
|
processing_pictures: 0
|
||||||
},
|
},
|
||||||
image: DEFAULT_ICON
|
image: DEFAULT_ICON
|
||||||
|
|
|
@ -40,7 +40,6 @@ describe("<Tools />", () => {
|
||||||
device: fakeDevice(),
|
device: fakeDevice(),
|
||||||
sensors: [fakeSensor()],
|
sensors: [fakeSensor()],
|
||||||
bot,
|
bot,
|
||||||
botToMqttStatus: "down",
|
|
||||||
hoveredToolSlot: undefined,
|
hoveredToolSlot: undefined,
|
||||||
firmwareHardware: undefined,
|
firmwareHardware: undefined,
|
||||||
isActive: jest.fn(),
|
isActive: jest.fn(),
|
||||||
|
@ -161,7 +160,7 @@ describe("<Tools />", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.tools = [fakeTool()];
|
p.tools = [fakeTool()];
|
||||||
p.bot.hardware.informational_settings.sync_status = "synced";
|
p.bot.hardware.informational_settings.sync_status = "synced";
|
||||||
p.botToMqttStatus = "up";
|
p.bot.connectivity.uptime["bot.mqtt"] = { state: "up", at: 0 };
|
||||||
const wrapper = mount(<Tools {...p} />);
|
const wrapper = mount(<Tools {...p} />);
|
||||||
expect(wrapper.text().toLowerCase()).toContain("mounted tool");
|
expect(wrapper.text().toLowerCase()).toContain("mounted tool");
|
||||||
wrapper.find(".yellow").first().simulate("click");
|
wrapper.find(".yellow").first().simulate("click");
|
||||||
|
@ -173,7 +172,7 @@ describe("<Tools />", () => {
|
||||||
it("can't verify tool attachment when offline", () => {
|
it("can't verify tool attachment when offline", () => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
p.tools = [fakeTool()];
|
p.tools = [fakeTool()];
|
||||||
p.botToMqttStatus = "down";
|
p.bot.connectivity.uptime["bot.mqtt"] = undefined;
|
||||||
const wrapper = mount(<Tools {...p} />);
|
const wrapper = mount(<Tools {...p} />);
|
||||||
wrapper.find(".yellow").first().simulate("click");
|
wrapper.find(".yellow").first().simulate("click");
|
||||||
expect(mockDevice.readPin).not.toHaveBeenCalled();
|
expect(mockDevice.readPin).not.toHaveBeenCalled();
|
||||||
|
|
|
@ -25,10 +25,8 @@ import { botPositionLabel } from "../map/layers/farmbot/bot_position_label";
|
||||||
import { Link } from "../../link";
|
import { Link } from "../../link";
|
||||||
import { edit, save } from "../../api/crud";
|
import { edit, save } from "../../api/crud";
|
||||||
import { readPin } from "../../devices/actions";
|
import { readPin } from "../../devices/actions";
|
||||||
import { isBotOnline } from "../../devices/must_be_online";
|
import { isBotOnlineFromState } from "../../devices/must_be_online";
|
||||||
import { BotState } from "../../devices/interfaces";
|
import { BotState } from "../../devices/interfaces";
|
||||||
import { NetworkState } from "../../connectivity/interfaces";
|
|
||||||
import { getStatus } from "../../connectivity/reducer_support";
|
|
||||||
import {
|
import {
|
||||||
setToolHover, ToolSlotSVG, ToolSVG,
|
setToolHover, ToolSlotSVG, ToolSVG,
|
||||||
} from "../map/layers/tool_slots/tool_graphics";
|
} from "../map/layers/tool_slots/tool_graphics";
|
||||||
|
@ -48,7 +46,6 @@ export interface ToolsProps {
|
||||||
device: TaggedDevice;
|
device: TaggedDevice;
|
||||||
sensors: TaggedSensor[];
|
sensors: TaggedSensor[];
|
||||||
bot: BotState;
|
bot: BotState;
|
||||||
botToMqttStatus: NetworkState;
|
|
||||||
hoveredToolSlot: string | undefined;
|
hoveredToolSlot: string | undefined;
|
||||||
firmwareHardware: FirmwareHardware | undefined;
|
firmwareHardware: FirmwareHardware | undefined;
|
||||||
isActive(id: number | undefined): boolean;
|
isActive(id: number | undefined): boolean;
|
||||||
|
@ -66,7 +63,6 @@ export const mapStateToProps = (props: Everything): ToolsProps => ({
|
||||||
device: getDeviceAccountSettings(props.resources.index),
|
device: getDeviceAccountSettings(props.resources.index),
|
||||||
sensors: selectAllSensors(props.resources.index),
|
sensors: selectAllSensors(props.resources.index),
|
||||||
bot: props.bot,
|
bot: props.bot,
|
||||||
botToMqttStatus: getStatus(props.bot.connectivity.uptime["bot.mqtt"]),
|
|
||||||
hoveredToolSlot: props.resources.consumers.farm_designer.hoveredToolSlot,
|
hoveredToolSlot: props.resources.consumers.farm_designer.hoveredToolSlot,
|
||||||
firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)),
|
firmwareHardware: getFwHardwareValue(getFbosConfig(props.resources.index)),
|
||||||
isActive: isActive(selectAllToolSlotPointers(props.resources.index)),
|
isActive: isActive(selectAllToolSlotPointers(props.resources.index)),
|
||||||
|
@ -114,11 +110,7 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
||||||
return !!this.props.bot.hardware.informational_settings.busy;
|
return !!this.props.bot.hardware.informational_settings.busy;
|
||||||
}
|
}
|
||||||
|
|
||||||
get botOnline() {
|
get botOnline() { return isBotOnlineFromState(this.props.bot); }
|
||||||
return isBotOnline(
|
|
||||||
this.props.bot.hardware.informational_settings.sync_status,
|
|
||||||
this.props.botToMqttStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isExpress() { return isExpressBoard(this.props.firmwareHardware); }
|
get isExpress() { return isExpressBoard(this.props.firmwareHardware); }
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,9 @@ export const sortAlerts = (alerts: Alert[]): Alert[] =>
|
||||||
const filterIncompleteAlerts = (x: Alert) =>
|
const filterIncompleteAlerts = (x: Alert) =>
|
||||||
x.problem_tag && isNumber(x.priority) && x.created_at;
|
x.problem_tag && isNumber(x.priority) && x.created_at;
|
||||||
|
|
||||||
|
export const filterAlerts = (x: Alert) =>
|
||||||
|
x.problem_tag != "farmbot_os.firmware.missing";
|
||||||
|
|
||||||
export const FirmwareAlerts = (props: FirmwareAlertsProps) => {
|
export const FirmwareAlerts = (props: FirmwareAlertsProps) => {
|
||||||
const firmwareAlerts = sortAlerts(props.alerts)
|
const firmwareAlerts = sortAlerts(props.alerts)
|
||||||
.filter(filterIncompleteAlerts)
|
.filter(filterIncompleteAlerts)
|
||||||
|
@ -34,7 +37,7 @@ export const Alerts = (props: AlertsProps) =>
|
||||||
<div className="problem-alerts-content">
|
<div className="problem-alerts-content">
|
||||||
{sortAlerts(props.alerts)
|
{sortAlerts(props.alerts)
|
||||||
.filter(filterIncompleteAlerts)
|
.filter(filterIncompleteAlerts)
|
||||||
.filter(x => x.problem_tag != "farmbot_os.firmware.missing")
|
.filter(filterAlerts)
|
||||||
.map(x =>
|
.map(x =>
|
||||||
<AlertCard key={x.slug + x.created_at}
|
<AlertCard key={x.slug + x.created_at}
|
||||||
alert={x}
|
alert={x}
|
||||||
|
|
Loading…
Reference in New Issue