Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ GEM
railties (>= 5.0.0)
faker (3.5.1)
i18n (>= 1.8.11, < 2)
faraday (2.12.2)
faraday (2.13.0)
faraday-net_http (>= 2.0, < 3.5)
json
logger
Expand Down Expand Up @@ -188,7 +188,7 @@ GEM
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
logger (1.6.6)
logger (1.7.0)
lograge (0.14.0)
actionpack (>= 4)
activesupport (>= 4)
Expand Down Expand Up @@ -222,18 +222,18 @@ GEM
net-smtp (0.5.1)
net-protocol
nio4r (2.7.4)
nokogiri (1.18.6-aarch64-linux-gnu)
nokogiri (1.18.7-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.6-x86_64-linux-gnu)
nokogiri (1.18.7-x86_64-linux-gnu)
racc (~> 1.4)
orm_adapter (0.5.0)
os (1.1.4)
parser (3.3.7.3)
parser (3.3.7.4)
ast (~> 2.4.1)
racc
passenger (6.0.23)
passenger (6.0.27)
rack (>= 1.6.13)
rackup
rackup (>= 1.0.1)
rake (>= 12.3.3)
pg (1.5.9)
pry (0.15.2)
Expand Down Expand Up @@ -306,7 +306,7 @@ GEM
railties (>= 5.2)
retriable (3.1.2)
rexml (3.4.1)
rollbar (3.6.1)
rollbar (3.6.2)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
Expand Down
30 changes: 19 additions & 11 deletions app/controllers/api/logs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,27 @@ class LogsController < Api::AbstractController
def search
conf = current_device.web_app_config
mt = CeleryScriptSettingsBag::ALLOWED_MESSAGE_TYPES
query = mt
.map { |x| "(type = '#{x}' AND verbosity <= ?)" }
.join(" OR ")
conditions = mt.map { |x| "#{x}_log" }.map { |x| conf.send(x) }
args_ = conditions.unshift(query)
limit = current_device.max_log_count || Device::DEFAULT_MAX_LOGS

render json: current_device
.logs
.order(created_at: :desc)
.where(*args_)
.limit(limit)
.where(search_params)
# Build conditions once
type_conditions = mt.map.with_index do |type, i|
verbosity = conf.send("#{type}_log")
["(type = ? AND verbosity <= ?)", type, verbosity]
end

# Combine conditions efficiently
where_clause = type_conditions.map(&:first).join(" OR ")
where_values = type_conditions.flat_map { |c| c[1..-1] }

# Single query with all conditions
logs = current_device
.logs
.order(created_at: :desc)
.limit(limit)
.where(where_clause, *where_values)
.where(search_params)

render json: logs
end

def create
Expand Down
11 changes: 2 additions & 9 deletions app/controllers/api/sensor_readings_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module Api
class SensorReadingsController < Api::AbstractController
LIMIT = 2500
before_action :clean_old

def create
Expand All @@ -23,20 +22,14 @@ def destroy
private

def clean_old
if current_device.sensor_readings.count > LIMIT
current_device
.sensor_readings
.where
.not(id: readings.pluck(:id))
.delete_all
end
current_device.delay.trim_excess_sensor_readings
end

def readings
@readings ||= SensorReading
.where(device: current_device)
.order(created_at: :desc)
.limit(LIMIT)
.limit(Device::DEFAULT_MAX_SENSOR_READINGS)
end

def reading
Expand Down
30 changes: 22 additions & 8 deletions app/controllers/api/sequences_controller.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
module Api
class SequencesController < Api::AbstractController
include ActionController::Live

before_action :clean_expired_farm_events, only: [:destroy]

def index
render json: sequences
.to_a
.map { |s| Sequences::Show.run!(sequence: s) }
# Stream to reduce memory usage
response.headers['Content-Type'] = 'application/json'
response.headers['Cache-Control'] = 'no-cache'
response.stream.write '['

Sequence.where(device: current_device)
.includes(:sequence_publication, :sequence_version)
.each_with_index do |s, index|
# Load the sequence with all needed associations and convert to JSON
seq_json = Sequences::Show.run!(sequence: Sequence.with_usage_reports.find(s.id)).to_json
# Append a comma for all but the first element to maintain valid JSON syntax
response.stream.write ',' unless index.zero?
response.stream.write seq_json
end

response.stream.write ']'
ensure
response.stream.close
end

def show
Expand Down Expand Up @@ -65,12 +82,9 @@ def sequence_params
@sequence_params ||= raw_json[:sequence] || raw_json || {}
end

def sequences
@sequences ||= Sequence.with_usage_reports.where(device: current_device)
end

# Retrieve a single sequence record directly associated with the current device
def sequence
@sequence ||= sequences.find(params[:id])
@sequence ||= Sequence.with_usage_reports.find_by!(id: params[:id], device: current_device)
end
end
end
19 changes: 19 additions & 0 deletions app/models/device.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Device < ApplicationRecord
DEFAULT_MAX_IMAGES = 100
DEFAULT_MAX_LOGS = 1000
DEFAULT_MAX_TELEMETRY = 300
DEFAULT_MAX_SENSOR_READINGS = 2500
DEFAULT_MAX_LOG_AGE_IN_DAYS = 60
DEFAULT_MAX_SEQUENCE_COUNT = 75
DEFAULT_MAX_SEQUENCE_LENGTH = 30
Expand Down Expand Up @@ -120,6 +121,24 @@ def trim_excess_telemetry
excess_telemetry.delete_all
end

# Give the user back the amount of sensor readings they are allowed to view.
def limited_sensor_readings_list
sensor_readings
.order(created_at: :desc)
.limit(DEFAULT_MAX_SENSOR_READINGS)
end

def excess_sensor_readings
sensor_readings
.where
.not(id: limited_sensor_readings_list.pluck(:id))
.where(device_id: self.id)
end

def trim_excess_sensor_readings
excess_sensor_readings.delete_all
end

def self.current
RequestStore.store[:device]
end
Expand Down
2 changes: 2 additions & 0 deletions app/models/sequence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class Sequence < ApplicationRecord
has_many :regimen_items
has_many :primary_nodes, dependent: :destroy
has_many :edge_nodes, dependent: :destroy
has_one :sequence_publication, foreign_key: :author_sequence_id
belongs_to :sequence_version, optional: true

# allowable label colors for the frontend.
[:name, :kind].each { |n| validates n, presence: true }
Expand Down
14 changes: 11 additions & 3 deletions app/mutations/sequences/show.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,23 @@ def copyright
end

def sequence_publication
@sequence_publication ||= SequencePublication.find_by(author_sequence_id: sequence.id)
# Cache the association if it's already loaded
return @sequence_publication if defined?(@sequence_publication)
@sequence_publication = sequence.association(:sequence_publication).loaded? ?
sequence.sequence_publication :
SequencePublication.find_by(author_sequence_id: sequence.id)
end

def description
sequence.description || sequence_version&.description
end

def sequence_version
@sequence_version ||= SequenceVersion.find_by(id: sequence_version_id)
# Cache the association if it's already loaded
return @sequence_version if defined?(@sequence_version)
@sequence_version = sequence.association(:sequence_version).loaded? ?
sequence.sequence_version :
SequenceVersion.find_by(id: sequence_version_id)
end

def use_upstream_version?
Expand All @@ -90,7 +98,7 @@ def available_version_ids
# If it is not published, don't show anything to the author.
# If it IS published, show the versions to the author.
if is_owner?
return sequence_publication.sequence_versions.pluck(:id)
return sequence_publication&.sequence_versions&.pluck(:id) || []
end

# Second attempt:
Expand Down
1 change: 1 addition & 0 deletions frontend/__test_support__/additional_mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jest.mock("react-router", () => ({
Route: jest.fn(({ children }) => <div>{children}</div>),
Routes: jest.fn(({ children }) => <div>{children}</div>),
useNavigate: () => mockNavigate,
useLocation: () => window.location,
Navigate: ({ to }: { to: string }) => <div>{mockNavigate(to)}</div>,
Outlet: jest.fn(() => <div />),
}));
1 change: 1 addition & 0 deletions frontend/controls/move/__tests__/jog_buttons_test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ describe("<PowerAndResetMenu />", () => {
const fakeProps = (): PowerAndResetMenuProps => ({
botOnline: true,
showAdvanced: true,
dispatch: jest.fn(),
});

it("restarts firmware", () => {
Expand Down
6 changes: 5 additions & 1 deletion frontend/controls/move/bot_position_rows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import { NumberConfigKey } from "farmbot/dist/resources/configs/firmware";
import { isUndefined } from "lodash";
import { calculateScale } from "../../settings/hardware_settings";
import { setPanelOpen } from "../../farm_designer/panel_header";

export const BotPositionRows = (props: BotPositionRowsProps) => {
const { locationData, getConfigValue, arduinoBusy, locked } = props;
Expand Down Expand Up @@ -141,7 +142,10 @@ export const AxisActions = (props: AxisActionsProps) => {
onClick={setAxisLength({ axis, dispatch, botPosition, sourceFwConfig })}>
{t("SET LENGTH")}
</LockableButton>
<a onClick={() => { navigate(Path.settings("axes")); }}>
<a onClick={() => {
dispatch(setPanelOpen(true));
navigate(Path.settings("axes"));
}}>
<i className="fa fa-external-link" />
{t("Settings")}
</a>
Expand Down
9 changes: 7 additions & 2 deletions frontend/controls/move/jog_buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ export class JogButtons
target={<button
className={"fa fa-power-off arrow-button fb-button"}
title={t("click to open power and reset menu")} />}
content={<PowerAndResetMenu botOnline={botOnline}
content={<PowerAndResetMenu
botOnline={botOnline}
dispatch={dispatch}
showAdvanced={showAdvanced} />} />
</td>
<td>
Expand Down Expand Up @@ -160,6 +162,7 @@ export class JogButtons
export interface PowerAndResetMenuProps {
botOnline: boolean;
showAdvanced: boolean;
dispatch: Function;
}

export const PowerAndResetMenu = (props: PowerAndResetMenuProps) => {
Expand Down Expand Up @@ -188,6 +191,8 @@ export const PowerAndResetMenu = (props: PowerAndResetMenuProps) => {
buttonText={t("SHUTDOWN")}
color={"red"}
action={powerOff} />
<FactoryResetRows botOnline={botOnline} />
<FactoryResetRows
dispatch={props.dispatch}
botOnline={botOnline} />
</div>;
};
1 change: 1 addition & 0 deletions frontend/devices/connectivity/__tests__/diagnosis_test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe("<Diagnosis/>", () => {
botAPI: true,
botFirmware: true,
},
dispatch: jest.fn(),
});

it("renders help text", () => {
Expand Down
8 changes: 6 additions & 2 deletions frontend/devices/connectivity/connectivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export class Connectivity
: undefined}
hover={this.hover}
hoveredConnection={this.state.hoveredConnection} />)}
<Diagnosis statusFlags={this.props.flags} />
<Diagnosis statusFlags={this.props.flags} dispatch={this.props.dispatch} />
{this.props.flags.userAPI && this.props.flags.userMQTT
&& this.props.flags.botAPI && this.props.flags.botMQTT
&& this.props.apiFirmwareValue
Expand Down Expand Up @@ -162,7 +162,11 @@ export class Connectivity
<PortRow port={"80 - HTTP"} status={flags["botAPI"]} />
<PortRow port={"443 - HTTPS"} status={flags["botAPI"]} />
<PortRow port={"8883 - MQTT"} status={flags["botMQTT"]} />
<a onClick={docLinkClick("for-it-security-professionals", this.navigate)}>
<a onClick={docLinkClick({
slug: "for-it-security-professionals",
navigate: this.navigate,
dispatch: this.props.dispatch,
})}>
<i className="fa fa-external-link" />
{t("Learn more about ports")}
</a>
Expand Down
19 changes: 16 additions & 3 deletions frontend/devices/connectivity/diagnosis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SyncStatus } from "farmbot";
import { syncText } from "../../nav/sync_text";
import { useNavigate } from "react-router";
import { linkToSetting } from "../../settings/maybe_highlight";
import { setPanelOpen } from "../../farm_designer/panel_header";

export type ConnectionName =
| "userAPI"
Expand All @@ -20,6 +21,7 @@ export type ConnectionStatusFlags = Record<ConnectionName, boolean>;
export interface DiagnosisProps {
statusFlags: ConnectionStatusFlags;
hideGraphic?: boolean;
dispatch: Function;
}
export interface DiagnosisSaucerProps extends ConnectionStatusFlags {
className?: string;
Expand Down Expand Up @@ -61,19 +63,30 @@ export function Diagnosis(props: DiagnosisProps) {
<p className="blinking">
{t("Always")}&nbsp;
<a className="blinking"
onClick={() => { navigate(linkToSetting(DeviceSetting.farmbotOS)); }}>
onClick={() => {
props.dispatch(setPanelOpen(true));
navigate(linkToSetting(DeviceSetting.farmbotOS));
}}>
<u>{t("upgrade FarmBot OS")}</u>
</a>
&nbsp;{t("before troubleshooting.")}
</p>
<p>
{diagnosisMessage(getDiagnosisCode(props.statusFlags))}
</p>
<a onClick={docLinkClick("connecting-farmbot-to-the-internet", navigate)}>
<a onClick={docLinkClick({
slug: "connecting-farmbot-to-the-internet",
navigate,
dispatch: props.dispatch,
})}>
<i className="fa fa-external-link" />
{t("Click here to learn more about connectivity codes.")}
</a>
<a onClick={docLinkClick("for-it-security-professionals", navigate)}>
<a onClick={docLinkClick({
slug: "for-it-security-professionals",
navigate,
dispatch: props.dispatch,
})}>
<i className="fa fa-external-link" />
{t("Click here for document to show to your IT department.")}
</a>
Expand Down
Loading