Merge changes
12
Gemfile.lock
|
@ -70,7 +70,7 @@ GEM
|
|||
url
|
||||
coderay (1.1.2)
|
||||
concurrent-ruby (1.1.5)
|
||||
crass (1.0.4)
|
||||
crass (1.0.5)
|
||||
database_cleaner (1.7.0)
|
||||
declarative (0.0.10)
|
||||
declarative-option (0.1.0)
|
||||
|
@ -106,15 +106,15 @@ GEM
|
|||
railties (>= 3.2, < 6.1)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
google-api-client (0.32.1)
|
||||
google-api-client (0.33.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.5, < 0.10.0)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.10)
|
||||
google-cloud-core (1.3.1)
|
||||
signet (~> 0.12)
|
||||
google-cloud-core (1.3.2)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-env (1.2.1)
|
||||
faraday (~> 0.11)
|
||||
|
@ -140,7 +140,7 @@ GEM
|
|||
json (2.2.0)
|
||||
jsonapi-renderer (0.2.2)
|
||||
jwt (2.2.1)
|
||||
loofah (2.3.0)
|
||||
loofah (2.3.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
|
|
|
@ -57,6 +57,7 @@ module Api
|
|||
status
|
||||
status_v8
|
||||
sync
|
||||
telemetry
|
||||
\\#
|
||||
\\*
|
||||
).map { |x| x + "(\\.|\\z)" }.join("|")
|
||||
|
|
|
@ -7,7 +7,7 @@ class AmqpLogParser < Mutations::Command
|
|||
TOO_OLD = "fbos version is out of date"
|
||||
DISCARD = "message type field is not the kind that gets saved in the DB"
|
||||
NOT_HASH = "logs must be a hash"
|
||||
|
||||
NOT_JSON = "Invalid JSON. Use a JSON validator."
|
||||
# I keep a Ruby copy of the JSON here for reference.
|
||||
# This is what a log will look like after JSON.parse()
|
||||
EXAMPLE_JSON = {
|
||||
|
@ -75,6 +75,8 @@ class AmqpLogParser < Mutations::Command
|
|||
def set_payload!
|
||||
# Parse from string to a Ruby hash (JSON)
|
||||
@output.payload = JSON.parse(payload)
|
||||
rescue JSON::ParserError
|
||||
add_error :json, :not_json, NOT_JSON
|
||||
end
|
||||
|
||||
def log
|
||||
|
|
|
@ -24,10 +24,12 @@ class LogService < AbstractServiceRunner
|
|||
|
||||
def maybe_deliver(data)
|
||||
violation = THROTTLE_POLICY.is_throttled(data.device_id)
|
||||
ok = data.valid? && !violation
|
||||
if violation
|
||||
return warn_user(data, violation)
|
||||
end
|
||||
|
||||
data.device.auto_sync_transaction do
|
||||
ok ? deliver(data) : warn_user(data, violation)
|
||||
deliver(data)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -41,6 +43,6 @@ class LogService < AbstractServiceRunner
|
|||
end
|
||||
|
||||
def warn_user(data, violation)
|
||||
data.device.maybe_throttle(violation)
|
||||
violation && data.device.maybe_throttle(violation)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# A singleton that runs on a separate process than the web server.
|
||||
# Listens to *ALL* incoming logs and stores them to the DB.
|
||||
# Also handles throttling.
|
||||
class TelemetryService < AbstractServiceRunner
|
||||
MESSAGE = "TELEMETRY MESSAGE FROM %s"
|
||||
FAILURE = "FAILED TELEMETRY MESSAGE FROM %s"
|
||||
|
||||
def process(delivery_info, payload)
|
||||
device_key = delivery_info
|
||||
.routing_key
|
||||
.split(".")[1]
|
||||
json = JSON.parse(payload)
|
||||
other_stuff = { device: device_key,
|
||||
is_telemetry: true,
|
||||
message: MESSAGE % device_key }
|
||||
puts json.merge(other_stuff).to_json
|
||||
rescue JSON::ParserError
|
||||
puts ({ device: device_key,
|
||||
is_telemetry: true,
|
||||
bad_json: payload,
|
||||
message: FAILURE % device_key }).to_json
|
||||
end
|
||||
end
|
|
@ -19,9 +19,9 @@ class ThrottlePolicy
|
|||
def is_throttled(unique_id)
|
||||
rules
|
||||
.map do |rule|
|
||||
is_violation = rule.time_period.usage_count_for(unique_id) > rule.limit
|
||||
is_violation ? Violation.new(rule) : nil
|
||||
end
|
||||
is_violation = rule.time_period.usage_count_for(unique_id) > rule.limit
|
||||
is_violation ? Violation.new(rule) : nil
|
||||
end
|
||||
.compact
|
||||
.max
|
||||
end
|
||||
|
|
|
@ -114,9 +114,10 @@ class Device < ApplicationRecord
|
|||
# Sets the `throttled_until` and `throttled_at` fields if unpopulated or
|
||||
# the throttle time period increases. Notifies user of cooldown period.
|
||||
def maybe_throttle(violation)
|
||||
return unless violation
|
||||
end_t = violation.ends_at
|
||||
# Some log validation errors will result in until_time being `nil`.
|
||||
if (violation && (throttled_until.nil? || end_t > throttled_until))
|
||||
if (throttled_until.nil? || end_t > throttled_until)
|
||||
reload.update_attributes!(throttled_until: end_t,
|
||||
throttled_at: Time.now)
|
||||
refresh_cache
|
||||
|
|
|
@ -42,6 +42,14 @@ class Transport
|
|||
.bind("amq.topic", routing_key: "bot.*.logs")
|
||||
end
|
||||
|
||||
def telemetry_channel
|
||||
@telemetry_channel ||= self
|
||||
.connection
|
||||
.create_channel
|
||||
.queue("api_telemetry_workers")
|
||||
.bind("amq.topic", routing_key: "bot.*.telemetry")
|
||||
end
|
||||
|
||||
def resource_channel
|
||||
@resource_channel ||= self
|
||||
.connection
|
||||
|
|
|
@ -759,6 +759,12 @@ export namespace Content {
|
|||
export const NO_GROUPS =
|
||||
trim(`Press "+" to add a group.`);
|
||||
|
||||
export const NO_WEEDS =
|
||||
trim(`Press "+" to add a weed.`);
|
||||
|
||||
export const NO_ZONES =
|
||||
trim(`Press "+" to add a zone.`);
|
||||
|
||||
export const ENTER_CROP_SEARCH_TERM =
|
||||
trim(`Search for a crop to add to your garden.`);
|
||||
|
||||
|
|
|
@ -80,16 +80,6 @@
|
|||
background-color: darken($dark-blue, 5%) !important;
|
||||
}
|
||||
}
|
||||
&.blue2 {
|
||||
background-color: $blue2;
|
||||
box-shadow: 0 2px 0px 0px $blue2dark;
|
||||
&:focus,
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: $blue2medium !important;
|
||||
|
||||
}
|
||||
}
|
||||
&.red {
|
||||
background-color: $red;
|
||||
box-shadow: 0 2px 0px 0px darken($red, 12%);
|
||||
|
@ -177,6 +167,69 @@
|
|||
background-color: darken($pink, 5%) !important;
|
||||
}
|
||||
}
|
||||
&.panel-green {
|
||||
background-color: $panel_green;
|
||||
box-shadow: 0 2px 0px 0px darken($panel_green, 12%);
|
||||
&:focus,
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: darken($panel_green, 5%) !important;
|
||||
}
|
||||
}
|
||||
&.panel-yellow {
|
||||
background-color: $panel_yellow;
|
||||
box-shadow: 0 2px 0px 0px darken($panel_yellow, 12%);
|
||||
&:focus,
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: darken($panel_yellow, 5%) !important;
|
||||
}
|
||||
}
|
||||
&.panel-gray {
|
||||
background-color: $panel_gray;
|
||||
box-shadow: 0 2px 0px 0px darken($panel_gray, 12%);
|
||||
&:focus,
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: darken($panel_gray, 5%) !important;
|
||||
}
|
||||
}
|
||||
&.panel-blue {
|
||||
background-color: $panel_blue;
|
||||
box-shadow: 0 2px 0px 0px darken($panel_blue, 12%);
|
||||
&:focus,
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: darken($panel_blue, 5%) !important;
|
||||
}
|
||||
}
|
||||
&.panel-brown {
|
||||
background-color: $panel_brown;
|
||||
box-shadow: 0 2px 0px 0px darken($panel_brown, 12%);
|
||||
&:focus,
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: darken($panel_brown, 5%) !important;
|
||||
}
|
||||
}
|
||||
&.panel-teal {
|
||||
background-color: $panel_teal;
|
||||
box-shadow: 0 2px 0px 0px darken($panel_teal, 12%);
|
||||
&:focus,
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: darken($panel_teal, 5%) !important;
|
||||
}
|
||||
}
|
||||
&.panel-red {
|
||||
background-color: $panel_red;
|
||||
box-shadow: 0 2px 0px 0px darken($panel_red, 12%);
|
||||
&:focus,
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: darken($panel_red, 5%) !important;
|
||||
}
|
||||
}
|
||||
&.zoom {
|
||||
padding: 8px 12px;
|
||||
&.zoom-out {
|
||||
|
|
|
@ -13,9 +13,6 @@ $darker_gray: #182026;
|
|||
$black: #000;
|
||||
$light_blue: #cdf;
|
||||
$blue: #a4c2f4;
|
||||
$blue2: #026365;
|
||||
$blue2medium: #025758;
|
||||
$blue2dark: #013738;
|
||||
$dark_blue: #37d;
|
||||
$light_cyan: #d0e0e3;
|
||||
$cyan: #45818e;
|
||||
|
@ -124,10 +121,6 @@ $panel_light_red: #fff7f6;
|
|||
background: $yellow !important;
|
||||
}
|
||||
|
||||
.blue2 {
|
||||
background: $blue2 !important;
|
||||
}
|
||||
|
||||
.blue,
|
||||
.info,
|
||||
.saucer-info {
|
||||
|
|
|
@ -65,9 +65,6 @@
|
|||
&.blue-panel {
|
||||
background-color: $panel_light_blue;
|
||||
}
|
||||
&.blue2-panel {
|
||||
background-color: $panel_light_blue;
|
||||
}
|
||||
&.teal-panel {
|
||||
background-color: $panel_light_teal;
|
||||
}
|
||||
|
@ -92,7 +89,7 @@
|
|||
background-color: $magenta;
|
||||
}
|
||||
&.gray-panel {
|
||||
background-color: $panel_medium_light_gray;
|
||||
background-color: $panel_gray;
|
||||
}
|
||||
&.yellow-panel {
|
||||
background-color: $panel_yellow;
|
||||
|
@ -100,9 +97,6 @@
|
|||
&.blue-panel {
|
||||
background-color: $panel_blue;
|
||||
}
|
||||
&.blue2-panel {
|
||||
background-color: $panel_blue;
|
||||
}
|
||||
&.teal-panel {
|
||||
background-color: $panel_teal;
|
||||
}
|
||||
|
@ -493,3 +487,23 @@
|
|||
min-width: 7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.weeds-inventory-panel,
|
||||
.zones-inventory-panel,
|
||||
.group-detail-panel,
|
||||
.groups-panel {
|
||||
.panel-content {
|
||||
max-height: calc(100vh - 19rem);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.weeds-inventory-panel,
|
||||
.zones-inventory-panel,
|
||||
.groups-panel {
|
||||
.panel-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -870,10 +870,7 @@ ul {
|
|||
p,
|
||||
h5,
|
||||
a {
|
||||
color: $panel_gray;
|
||||
}
|
||||
.empty-state-graphic {
|
||||
filter: saturate(0);
|
||||
color: $panel_teal;
|
||||
}
|
||||
}
|
||||
&.tools {
|
||||
|
@ -890,6 +887,20 @@ ul {
|
|||
color: $panel_blue;
|
||||
}
|
||||
}
|
||||
&.weeds {
|
||||
p,
|
||||
h5,
|
||||
a {
|
||||
color: $panel_red;
|
||||
}
|
||||
}
|
||||
&.zones {
|
||||
p,
|
||||
h5,
|
||||
a {
|
||||
color: $panel_brown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.farmware-selection-panel {
|
||||
|
|
|
@ -36,6 +36,7 @@ input:not([role="combobox"]) {
|
|||
background-color: $white !important;
|
||||
color: $red;
|
||||
}
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.input-error-wrapper {
|
||||
|
|
|
@ -48,7 +48,7 @@ describe("<DesignerNavTabs />", () => {
|
|||
mockPath = "/app/designer/points";
|
||||
mockDev = true;
|
||||
const wrapper = shallow(<DesignerNavTabs />);
|
||||
expect(wrapper.hasClass("gray-panel")).toBeTruthy();
|
||||
expect(wrapper.hasClass("teal-panel")).toBeTruthy();
|
||||
expect(wrapper.html()).toContain("active");
|
||||
});
|
||||
|
||||
|
@ -56,7 +56,23 @@ describe("<DesignerNavTabs />", () => {
|
|||
mockPath = "/app/designer/groups";
|
||||
mockDev = true;
|
||||
const wrapper = shallow(<DesignerNavTabs />);
|
||||
expect(wrapper.hasClass("blue2-panel")).toBeTruthy();
|
||||
expect(wrapper.hasClass("blue-panel")).toBeTruthy();
|
||||
expect(wrapper.html()).toContain("active");
|
||||
});
|
||||
|
||||
it("renders for weeds", () => {
|
||||
mockPath = "/app/designer/weeds";
|
||||
mockDev = true;
|
||||
const wrapper = shallow(<DesignerNavTabs />);
|
||||
expect(wrapper.hasClass("red-panel")).toBeTruthy();
|
||||
expect(wrapper.html()).toContain("active");
|
||||
});
|
||||
|
||||
it("renders for zones", () => {
|
||||
mockPath = "/app/designer/zones";
|
||||
mockDev = true;
|
||||
const wrapper = shallow(<DesignerNavTabs />);
|
||||
expect(wrapper.hasClass("brown-panel")).toBeTruthy();
|
||||
expect(wrapper.html()).toContain("active");
|
||||
});
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ export enum Panel {
|
|||
Settings = "Settings",
|
||||
Points = "Points",
|
||||
Groups = "Groups",
|
||||
Weeds = "Weeds",
|
||||
Zones = "Zones",
|
||||
}
|
||||
|
||||
type Tabs = keyof typeof Panel;
|
||||
|
@ -24,8 +26,10 @@ export const TAB_COLOR: { [key in Panel]: string } = {
|
|||
[Panel.SavedGardens]: "green",
|
||||
[Panel.Tools]: "gray",
|
||||
[Panel.Settings]: "gray",
|
||||
[Panel.Points]: "gray",
|
||||
[Panel.Groups]: "blue2",
|
||||
[Panel.Points]: "teal",
|
||||
[Panel.Groups]: "blue",
|
||||
[Panel.Weeds]: "red",
|
||||
[Panel.Zones]: "brown",
|
||||
};
|
||||
|
||||
const iconFile = (icon: string) => `/app-resources/img/icons/${icon}.svg`;
|
||||
|
@ -39,6 +43,8 @@ export const TAB_ICON: { [key in Panel]: string } = {
|
|||
[Panel.Settings]: iconFile("settings"),
|
||||
[Panel.Points]: iconFile("point"),
|
||||
[Panel.Groups]: iconFile("groups"),
|
||||
[Panel.Weeds]: iconFile("weeds"),
|
||||
[Panel.Zones]: iconFile("zones"),
|
||||
};
|
||||
|
||||
const getCurrentTab = (): Tabs => {
|
||||
|
@ -57,6 +63,10 @@ const getCurrentTab = (): Tabs => {
|
|||
return Panel.Points;
|
||||
} else if (pathArray.includes("groups")) {
|
||||
return Panel.Groups;
|
||||
} else if (pathArray.includes("weeds")) {
|
||||
return Panel.Weeds;
|
||||
} else if (pathArray.includes("zones")) {
|
||||
return Panel.Zones;
|
||||
} else {
|
||||
return Panel.Plants;
|
||||
}
|
||||
|
@ -103,11 +113,21 @@ export function DesignerNavTabs(props: { hidden?: boolean }) {
|
|||
panel={Panel.FarmEvents}
|
||||
linkTo={"/app/designer/events"}
|
||||
title={t("Events")} />
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<NavTab
|
||||
panel={Panel.Zones}
|
||||
linkTo={"/app/designer/zones"}
|
||||
title={t("Zones")} />}
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<NavTab
|
||||
panel={Panel.Points}
|
||||
linkTo={"/app/designer/points"}
|
||||
title={t("Points")} />}
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<NavTab
|
||||
panel={Panel.Weeds}
|
||||
linkTo={"/app/designer/weeds"}
|
||||
title={t("Weeds")} />}
|
||||
{DevSettings.futureFeaturesEnabled() &&
|
||||
<NavTab
|
||||
panel={Panel.Tools}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import {
|
||||
RawAddWeed as AddWeed, AddWeedProps, mapStateToProps
|
||||
} from "../weeds_add";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
|
||||
describe("<AddWeed />", () => {
|
||||
const fakeProps = (): AddWeedProps => ({
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<AddWeed {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("Add");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const state = fakeState();
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.dispatch).toEqual(expect.any(Function));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
let mockPath = "/app/designer/weeds/1";
|
||||
jest.mock("../../../history", () => ({
|
||||
getPathArray: jest.fn(() => mockPath.split("/")),
|
||||
history: { push: jest.fn() }
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import {
|
||||
RawEditWeed as EditWeed, EditWeedProps, mapStateToProps
|
||||
} from "../weeds_edit";
|
||||
import { fakePoint } from "../../../__test_support__/fake_state/resources";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
import {
|
||||
buildResourceIndex
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
|
||||
describe("<EditWeed />", () => {
|
||||
const fakeProps = (): EditWeedProps => ({
|
||||
dispatch: jest.fn(),
|
||||
findPoint: () => undefined,
|
||||
});
|
||||
|
||||
it("redirects", () => {
|
||||
mockPath = "/app/designer/weeds";
|
||||
const wrapper = mount(<EditWeed {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("Redirecting...");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
mockPath = "/app/designer/weeds/1";
|
||||
const p = fakeProps();
|
||||
const point = fakePoint();
|
||||
point.body.id = 1;
|
||||
p.findPoint = () => point;
|
||||
const wrapper = mount(<EditWeed {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("edit");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const state = fakeState();
|
||||
const point = fakePoint();
|
||||
point.body.id = 1;
|
||||
state.resources = buildResourceIndex([point]);
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.findPoint(1)).toEqual(point);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import {
|
||||
RawWeeds as Weeds, WeedsProps, mapStateToProps
|
||||
} from "../weeds_inventory";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
|
||||
describe("<Weeds> />", () => {
|
||||
const fakeProps = (): WeedsProps => ({
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders no points", () => {
|
||||
const wrapper = mount(<Weeds {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("No weeds yet.");
|
||||
});
|
||||
|
||||
it("changes search term", () => {
|
||||
const wrapper = shallow<Weeds>(<Weeds {...fakeProps()} />);
|
||||
wrapper.find("input").first().simulate("change",
|
||||
{ currentTarget: { value: "0" } });
|
||||
expect(wrapper.state().searchTerm).toEqual("0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const state = fakeState();
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.dispatch).toEqual(expect.any(Function));
|
||||
});
|
||||
});
|
|
@ -233,10 +233,10 @@ export class RawCreatePoints
|
|||
</Row>
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"point-creation"} panelColor={"brown"}>
|
||||
return <DesignerPanel panelName={"point-creation"} panelColor={"teal"}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"point-creation"}
|
||||
panelColor={"brown"}
|
||||
panelColor={"teal"}
|
||||
title={t("Create point")}
|
||||
backTo={"/app/designer/points"}
|
||||
description={Content.CREATE_POINTS_DESCRIPTION} />
|
||||
|
|
|
@ -87,7 +87,7 @@ export const DesignerPanelTop = (props: DesignerPanelTopProps) => {
|
|||
</div>
|
||||
{props.linkTo &&
|
||||
<Link to={props.linkTo}>
|
||||
<div className={`fb-button ${TAB_COLOR[props.panel || Panel.Plants]}`}>
|
||||
<div className={`fb-button panel-${TAB_COLOR[props.panel || Panel.Plants]}`}>
|
||||
<i className="fa fa-plus" title={props.title} />
|
||||
</div>
|
||||
</Link>}
|
||||
|
|
|
@ -66,10 +66,10 @@ export class RawEditPoint extends React.Component<EditPointProps, {}> {
|
|||
};
|
||||
|
||||
default = (point: TaggedPoint) => {
|
||||
return <DesignerPanel panelName={"plant-info"} panelColor={"green"}>
|
||||
return <DesignerPanel panelName={"plant-info"} panelColor={"teal"}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"plant-info"}
|
||||
panelColor={"gray"}
|
||||
panelColor={"teal"}
|
||||
title={`${t("Edit")} ${point.body.name}`}
|
||||
backTo={"/app/designer/points"}>
|
||||
</DesignerPanelHeader>
|
||||
|
|
|
@ -39,7 +39,7 @@ export class RawPoints extends React.Component<PointsProps, PointsState> {
|
|||
}
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"point-inventory"} panelColor={"brown"}>
|
||||
return <DesignerPanel panelName={"point-inventory"} panelColor={"teal"}>
|
||||
<DesignerNavTabs />
|
||||
<DesignerPanelTop
|
||||
panel={Panel.Points}
|
||||
|
@ -51,7 +51,7 @@ export class RawPoints extends React.Component<PointsProps, PointsState> {
|
|||
<DesignerPanelContent panelName={"point"}>
|
||||
<EmptyStateWrapper
|
||||
notEmpty={this.props.points.length > 0}
|
||||
graphic={EmptyStateGraphic.no_crop_results}
|
||||
graphic={EmptyStateGraphic.points}
|
||||
title={t("No points yet.")}
|
||||
text={Content.NO_POINTS}
|
||||
colorScheme={"points"}>
|
||||
|
|
|
@ -39,6 +39,8 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
}
|
||||
}
|
||||
|
||||
get selected() { return this.props.selected || []; }
|
||||
|
||||
destroySelected = (plantUUIDs: string[] | undefined) => {
|
||||
if (plantUUIDs && plantUUIDs.length > 0 &&
|
||||
confirm(t("Are you sure you want to delete {{length}} plants?",
|
||||
|
@ -53,14 +55,6 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
}
|
||||
}
|
||||
|
||||
SelectedNotUndef = (input?: string[] | undefined) => {
|
||||
if (input !== undefined) {
|
||||
return input;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
ActionButtons = () =>
|
||||
<div className="panel-action-buttons">
|
||||
<div className="buttonrow">
|
||||
|
@ -82,7 +76,7 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
</button>
|
||||
<button className="fb-button dark-blue"
|
||||
onClick={() => this.props.dispatch(createGroup({
|
||||
points: this.SelectedNotUndef(this.props.selected)
|
||||
points: this.selected
|
||||
}))}>
|
||||
{t("Create group")}
|
||||
</button>
|
||||
|
@ -101,7 +95,7 @@ export class RawSelectPlants extends React.Component<SelectPlantsProps, {}> {
|
|||
panelColor={"gray"}
|
||||
blackText={true}
|
||||
title={t("{{length}} plants selected",
|
||||
{ length: this.SelectedNotUndef(selected).length })}
|
||||
{ length: this.selected.length })}
|
||||
backTo={"/app/designer/plants"}
|
||||
description={Content.BOX_SELECT_DESCRIPTION} />
|
||||
<this.ActionButtons />
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelContent, DesignerPanelHeader
|
||||
} from "../plants/designer_panel";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
export interface AddWeedProps {
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
export interface AddWeedState {
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): AddWeedProps => ({
|
||||
dispatch: props.dispatch,
|
||||
});
|
||||
|
||||
export class RawAddWeed extends React.Component<AddWeedProps, AddWeedState> {
|
||||
state: AddWeedState = {};
|
||||
render() {
|
||||
return <DesignerPanel panelName={"add-weed"} panelColor={"red"}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"weeds"}
|
||||
title={t("Add new weed")}
|
||||
backTo={"/app/designer/weeds"}
|
||||
panelColor={"red"} />
|
||||
<DesignerPanelContent panelName={"add-weed"}>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
||||
|
||||
export const AddWeed = connect(mapStateToProps)(RawAddWeed);
|
|
@ -0,0 +1,53 @@
|
|||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
|
||||
} from "./designer_panel";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { history, getPathArray } from "../../history";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { TaggedPoint } from "farmbot";
|
||||
import { maybeFindPointById } from "../../resources/selectors";
|
||||
|
||||
export interface EditWeedProps {
|
||||
dispatch: Function;
|
||||
findPoint(id: number): TaggedPoint | undefined;
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): EditWeedProps => ({
|
||||
dispatch: props.dispatch,
|
||||
findPoint: id => maybeFindPointById(props.resources.index, id),
|
||||
});
|
||||
|
||||
export class RawEditWeed extends React.Component<EditWeedProps, {}> {
|
||||
get stringyID() { return getPathArray()[4] || ""; }
|
||||
get point() {
|
||||
if (this.stringyID) {
|
||||
return this.props.findPoint(parseInt(this.stringyID));
|
||||
}
|
||||
}
|
||||
|
||||
fallback = () => {
|
||||
history.push("/app/designer/weeds");
|
||||
return <span>{t("Redirecting...")}</span>;
|
||||
}
|
||||
|
||||
default = (point: TaggedPoint) => {
|
||||
return <DesignerPanel panelName={"weed-info"} panelColor={"red"}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"weed-info"}
|
||||
panelColor={"red"}
|
||||
title={`${t("Edit")} ${point.body.name}`}
|
||||
backTo={"/app/designer/points"}>
|
||||
</DesignerPanelHeader>
|
||||
<DesignerPanelContent panelName={"weed-info"}>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.point ? this.default(this.point) : this.fallback();
|
||||
}
|
||||
}
|
||||
|
||||
export const EditWeed = connect(mapStateToProps)(RawEditWeed);
|
|
@ -0,0 +1,56 @@
|
|||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { DesignerNavTabs, Panel } from "../panel_header";
|
||||
import {
|
||||
EmptyStateWrapper, EmptyStateGraphic
|
||||
} from "../../ui/empty_state_wrapper";
|
||||
import { Content } from "../../constants";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelContent, DesignerPanelTop
|
||||
} from "./designer_panel";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
export interface WeedsProps {
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
interface WeedsState {
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): WeedsProps => ({
|
||||
dispatch: props.dispatch,
|
||||
});
|
||||
|
||||
export class RawWeeds extends React.Component<WeedsProps, WeedsState> {
|
||||
state: WeedsState = { searchTerm: "" };
|
||||
|
||||
update = ({ currentTarget }: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
this.setState({ searchTerm: currentTarget.value });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"weeds-inventory"} panelColor={"red"}>
|
||||
<DesignerNavTabs />
|
||||
<DesignerPanelTop
|
||||
panel={Panel.Weeds}
|
||||
linkTo={"/app/designer/weeds/add"}
|
||||
title={t("Add weed")}>
|
||||
<input type="text" onChange={this.update}
|
||||
placeholder={t("Search your weeds...")} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"weeds-inventory"}>
|
||||
<EmptyStateWrapper
|
||||
notEmpty={[].length > 0}
|
||||
graphic={EmptyStateGraphic.weeds}
|
||||
title={t("No weeds yet.")}
|
||||
text={Content.NO_WEEDS}
|
||||
colorScheme={"weeds"}>
|
||||
</EmptyStateWrapper>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
||||
|
||||
export const Weeds = connect(mapStateToProps)(RawWeeds);
|
|
@ -70,7 +70,7 @@ export class GroupDetailActive
|
|||
}
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"groups"} panelColor={"blue"}>
|
||||
return <DesignerPanel panelName={"group-detail"} panelColor={"blue"}>
|
||||
<DesignerPanelHeader
|
||||
onBack={this.saveGroup}
|
||||
panelName={Panel.Groups}
|
||||
|
|
|
@ -39,7 +39,7 @@ export class RawGroupListPanel extends React.Component<GroupListPanelProps, Stat
|
|||
navigate = (id: number) => history.push(`/app/designer/groups/${id}`);
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"groups"} panelColor={"blue2"}>
|
||||
return <DesignerPanel panelName={"groups"} panelColor={"blue"}>
|
||||
<DesignerNavTabs />
|
||||
<DesignerPanelTop
|
||||
panel={Panel.Groups}
|
||||
|
|
|
@ -62,7 +62,7 @@ export class RawTools extends React.Component<ToolsProps, ToolsState> {
|
|||
<DesignerPanelContent panelName={"tools"}>
|
||||
<EmptyStateWrapper
|
||||
notEmpty={this.props.tools.length > 0}
|
||||
graphic={EmptyStateGraphic.sequences}
|
||||
graphic={EmptyStateGraphic.tools}
|
||||
title={t("Add a tool")}
|
||||
text={Content.NO_TOOLS}
|
||||
colorScheme={"tools"}>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import {
|
||||
RawAddZone as AddZone, AddZoneProps, mapStateToProps
|
||||
} from "../add_zone";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
|
||||
describe("<AddZone />", () => {
|
||||
const fakeProps = (): AddZoneProps => ({
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<AddZone {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("Add");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const state = fakeState();
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.dispatch).toEqual(expect.any(Function));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
let mockPath = "/app/designer/zones/1";
|
||||
jest.mock("../../../history", () => ({
|
||||
getPathArray: jest.fn(() => mockPath.split("/")),
|
||||
history: { push: jest.fn() }
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import {
|
||||
RawEditZone as EditZone, EditZoneProps, mapStateToProps
|
||||
} from "../edit_zone";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
|
||||
describe("<EditZone />", () => {
|
||||
const fakeProps = (): EditZoneProps => ({
|
||||
dispatch: jest.fn(),
|
||||
findZone: () => undefined,
|
||||
});
|
||||
|
||||
it("redirects", () => {
|
||||
mockPath = "/app/designer/zones";
|
||||
const wrapper = mount(<EditZone {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("Redirecting...");
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
mockPath = "/app/designer/zones/1";
|
||||
const p = fakeProps();
|
||||
p.findZone = () => "stub zone";
|
||||
const wrapper = mount(<EditZone {...p} />);
|
||||
expect(wrapper.text().toLowerCase()).toContain("edit");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const state = fakeState();
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.findZone(1)).toEqual(undefined);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
import * as React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import {
|
||||
RawZones as Zones, ZonesProps, mapStateToProps
|
||||
} from "../zones_inventory";
|
||||
import { fakeState } from "../../../__test_support__/fake_state";
|
||||
|
||||
describe("<Zones> />", () => {
|
||||
const fakeProps = (): ZonesProps => ({
|
||||
dispatch: jest.fn(),
|
||||
});
|
||||
|
||||
it("renders no zones", () => {
|
||||
const wrapper = mount(<Zones {...fakeProps()} />);
|
||||
expect(wrapper.text()).toContain("No zones yet.");
|
||||
});
|
||||
|
||||
it("changes search term", () => {
|
||||
const wrapper = shallow<Zones>(<Zones {...fakeProps()} />);
|
||||
wrapper.find("input").first().simulate("change",
|
||||
{ currentTarget: { value: "0" } });
|
||||
expect(wrapper.state().searchTerm).toEqual("0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapStateToProps()", () => {
|
||||
it("returns props", () => {
|
||||
const state = fakeState();
|
||||
const props = mapStateToProps(state);
|
||||
expect(props.dispatch).toEqual(expect.any(Function));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelContent, DesignerPanelHeader
|
||||
} from "../plants/designer_panel";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
export interface AddZoneProps {
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
export interface AddZoneState {
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): AddZoneProps => ({
|
||||
dispatch: props.dispatch,
|
||||
});
|
||||
|
||||
export class RawAddZone extends React.Component<AddZoneProps, AddZoneState> {
|
||||
state: AddZoneState = {};
|
||||
render() {
|
||||
return <DesignerPanel panelName={"add-zone"} panelColor={"brown"}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"add-zone"}
|
||||
title={t("Add new zone")}
|
||||
backTo={"/app/designer/zones"}
|
||||
panelColor={"brown"} />
|
||||
<DesignerPanelContent panelName={"add-zone"}>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
||||
|
||||
export const AddZone = connect(mapStateToProps)(RawAddZone);
|
|
@ -0,0 +1,51 @@
|
|||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelHeader, DesignerPanelContent
|
||||
} from "../plants/designer_panel";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { history, getPathArray } from "../../history";
|
||||
import { Everything } from "../../interfaces";
|
||||
|
||||
export interface EditZoneProps {
|
||||
dispatch: Function;
|
||||
findZone(id: number): string | undefined;
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): EditZoneProps => ({
|
||||
dispatch: props.dispatch,
|
||||
findZone: _id => undefined,
|
||||
});
|
||||
|
||||
export class RawEditZone extends React.Component<EditZoneProps, {}> {
|
||||
get stringyID() { return getPathArray()[4] || ""; }
|
||||
get zone() {
|
||||
if (this.stringyID) {
|
||||
return this.props.findZone(parseInt(this.stringyID));
|
||||
}
|
||||
}
|
||||
|
||||
fallback = () => {
|
||||
history.push("/app/designer/zones");
|
||||
return <span>{t("Redirecting...")}</span>;
|
||||
}
|
||||
|
||||
default = () => {
|
||||
return <DesignerPanel panelName={"zone-info"} panelColor={"brown"}>
|
||||
<DesignerPanelHeader
|
||||
panelName={"zone-info"}
|
||||
panelColor={"brown"}
|
||||
title={`${t("Edit")} zone`}
|
||||
backTo={"/app/designer/zones"}>
|
||||
</DesignerPanelHeader>
|
||||
<DesignerPanelContent panelName={"zone-info"}>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.zone ? this.default() : this.fallback();
|
||||
}
|
||||
}
|
||||
|
||||
export const EditZone = connect(mapStateToProps)(RawEditZone);
|
|
@ -0,0 +1,56 @@
|
|||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Everything } from "../../interfaces";
|
||||
import { DesignerNavTabs, Panel } from "../panel_header";
|
||||
import {
|
||||
EmptyStateWrapper, EmptyStateGraphic
|
||||
} from "../../ui/empty_state_wrapper";
|
||||
import { Content } from "../../constants";
|
||||
import {
|
||||
DesignerPanel, DesignerPanelContent, DesignerPanelTop
|
||||
} from "../plants/designer_panel";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
export interface ZonesProps {
|
||||
dispatch: Function;
|
||||
}
|
||||
|
||||
interface ZonesState {
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export const mapStateToProps = (props: Everything): ZonesProps => ({
|
||||
dispatch: props.dispatch,
|
||||
});
|
||||
|
||||
export class RawZones extends React.Component<ZonesProps, ZonesState> {
|
||||
state: ZonesState = { searchTerm: "" };
|
||||
|
||||
update = ({ currentTarget }: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
this.setState({ searchTerm: currentTarget.value });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <DesignerPanel panelName={"zones-inventory"} panelColor={"red"}>
|
||||
<DesignerNavTabs />
|
||||
<DesignerPanelTop
|
||||
panel={Panel.Zones}
|
||||
linkTo={"/app/designer/zones/add"}
|
||||
title={t("Add zone")}>
|
||||
<input type="text" onChange={this.update}
|
||||
placeholder={t("Search your zones...")} />
|
||||
</DesignerPanelTop>
|
||||
<DesignerPanelContent panelName={"zones-inventory"}>
|
||||
<EmptyStateWrapper
|
||||
notEmpty={[].length > 0}
|
||||
graphic={EmptyStateGraphic.zones}
|
||||
title={t("No zones yet.")}
|
||||
text={Content.NO_ZONES}
|
||||
colorScheme={"zones"}>
|
||||
</EmptyStateWrapper>
|
||||
</DesignerPanelContent>
|
||||
</DesignerPanel>;
|
||||
}
|
||||
}
|
||||
|
||||
export const Zones = connect(mapStateToProps)(RawZones);
|
|
@ -353,4 +353,52 @@ export const UNBOUND_ROUTES = [
|
|||
getChild: () => import("./farm_designer/point_groups/group_detail"),
|
||||
childKey: "GroupDetail"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/weeds",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/plants/weeds_inventory"),
|
||||
childKey: "Weeds"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/weeds/add",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/plants/weeds_add"),
|
||||
childKey: "AddWeed"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/weeds/:point_id",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/plants/weeds_edit"),
|
||||
childKey: "EditWeed"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/zones",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/zones/zones_inventory"),
|
||||
childKey: "Zones"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/zones/add",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/zones/add_zone"),
|
||||
childKey: "AddZone"
|
||||
}),
|
||||
route({
|
||||
children: true,
|
||||
$: "/designer/zones/:zone_id",
|
||||
getModule,
|
||||
key,
|
||||
getChild: () => import("./farm_designer/zones/edit_zone"),
|
||||
childKey: "EditZone"
|
||||
}),
|
||||
].concat([NOT_FOUND_ROUTE]);
|
||||
|
|
|
@ -8,7 +8,11 @@ export enum EmptyStateGraphic {
|
|||
sequences = "sequences",
|
||||
regimens = "regimens",
|
||||
farm_events = "farm_events",
|
||||
groups = "groups"
|
||||
groups = "groups",
|
||||
points = "points",
|
||||
tools = "tools",
|
||||
weeds = "weeds",
|
||||
zones = "zones",
|
||||
}
|
||||
|
||||
interface EmptyStateWrapperProps {
|
||||
|
@ -17,7 +21,8 @@ interface EmptyStateWrapperProps {
|
|||
text?: string;
|
||||
textElement?: JSX.Element;
|
||||
graphic: string;
|
||||
colorScheme?: "plants" | "events" | "gardens" | "points" | "tools" | "groups";
|
||||
colorScheme?: "plants" | "events" | "gardens" | "points" | "tools"
|
||||
| "groups" | "weeds" | "zones";
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ class RabbitWorker
|
|||
|
||||
loop do
|
||||
ThreadsWait.all_waits([
|
||||
thread { TelemetryService.new.go!(t.telemetry_channel) },
|
||||
thread { LogService.new.go!(t.log_channel) },
|
||||
thread { Resources::Service.new.go!(t.resource_channel) },
|
||||
])
|
||||
|
|
20
package.json
|
@ -29,16 +29,16 @@
|
|||
"@blueprintjs/datetime": "3.14.0",
|
||||
"@blueprintjs/select": "3.11.1",
|
||||
"@types/enzyme": "3.10.3",
|
||||
"@types/jest": "24.0.18",
|
||||
"@types/jest": "24.0.19",
|
||||
"@types/lodash": "4.14.144",
|
||||
"@types/markdown-it": "0.0.9",
|
||||
"@types/moxios": "0.4.9",
|
||||
"@types/node": "12.7.12",
|
||||
"@types/node": "12.11.5",
|
||||
"@types/promise-timeout": "1.3.0",
|
||||
"@types/react": "16.9.5",
|
||||
"@types/react": "16.9.9",
|
||||
"@types/react-color": "3.0.1",
|
||||
"@types/react-dom": "16.9.1",
|
||||
"@types/react-redux": "7.1.4",
|
||||
"@types/react-dom": "16.9.2",
|
||||
"@types/react-redux": "7.1.5",
|
||||
"axios": "0.19.0",
|
||||
"boxed_value": "1.0.0",
|
||||
"browser-speech": "1.1.1",
|
||||
|
@ -46,7 +46,7 @@
|
|||
"enzyme": "3.10.0",
|
||||
"enzyme-adapter-react-16": "1.15.1",
|
||||
"farmbot": "8.3.0-rc6",
|
||||
"i18next": "17.2.0",
|
||||
"i18next": "17.3.0",
|
||||
"install": "0.13.0",
|
||||
"lodash": "4.17.15",
|
||||
"markdown-it": "10.0.0",
|
||||
|
@ -58,18 +58,18 @@
|
|||
"parcel-bundler": "1.12.4",
|
||||
"promise-timeout": "1.3.0",
|
||||
"raf": "3.4.1",
|
||||
"react": "16.10.2",
|
||||
"react": "16.11.0",
|
||||
"react-addons-test-utils": "15.6.2",
|
||||
"react-color": "2.17.3",
|
||||
"react-dom": "16.10.2",
|
||||
"react-dom": "16.11.0",
|
||||
"react-joyride": "2.1.1",
|
||||
"react-redux": "7.1.1",
|
||||
"react-test-renderer": "16.10.2",
|
||||
"react-test-renderer": "16.11.0",
|
||||
"react-transition-group": "4.3.0",
|
||||
"redux": "4.0.4",
|
||||
"redux-immutable-state-invariant": "2.1.0",
|
||||
"redux-thunk": "2.3.0",
|
||||
"sass": "1.23.0",
|
||||
"sass": "1.23.1",
|
||||
"sass-lint": "1.13.1",
|
||||
"takeme": "0.11.3",
|
||||
"ts-jest": "24.1.0",
|
||||
|
|
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 115 KiB |
|
@ -1,119 +1,145 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!-- Generator: Adobe Illustrator 23.0.6, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st1{fill:#C46F24;}
|
||||
.st2{opacity:0.5;fill:#C46F24;}
|
||||
.st3{fill:#FFFFFF;stroke:#000000;stroke-width:7;stroke-miterlimit:10;}
|
||||
.st4{fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st5{fill:#FFFFFF;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st6{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st7{fill:#F15A24;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st8{fill:none;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st9{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st10{fill:#FFFFFF;}
|
||||
.st11{fill:#FFFFFF;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st12{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:4.088,10.22;}
|
||||
.st13{fill:#FFFFFF;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st14{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st15{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st16{fill:none;stroke:#969696;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st17{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st18{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st19{fill:none;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st20{fill:#FFFFFF;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st21{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st22{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st23{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3.2727,9.8182;}
|
||||
.st24{fill:none;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st25{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st26{fill:none;stroke:#333333;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st27{fill:none;stroke:#E6E6E6;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st28{fill:#1EB287;}
|
||||
.st29{fill:#DEEDCB;}
|
||||
.st30{fill:#505305;}
|
||||
.st31{fill:#186435;}
|
||||
.st32{fill:#A44F79;}
|
||||
.st33{fill:#2AB188;}
|
||||
.st34{fill:#A35915;}
|
||||
.st35{fill:#4D4D4D;}
|
||||
.st36{fill:#F6B330;}
|
||||
.st37{fill:#324872;}
|
||||
.st38{fill:#2BA270;}
|
||||
.st39{fill:#53A4EA;}
|
||||
.st40{fill:#3BA2A0;}
|
||||
.st41{fill:#1792CD;}
|
||||
.st42{fill:#0C2E3D;}
|
||||
.st43{fill:#35761B;}
|
||||
.st44{fill:#0C6364;}
|
||||
.st45{fill:#F4A519;}
|
||||
.st46{opacity:0.06;fill:#3B9910;}
|
||||
.st47{opacity:0.06;fill:#E56200;}
|
||||
.st48{opacity:0.06;fill:#2E5799;}
|
||||
.st49{opacity:0.06;fill:#007F7C;}
|
||||
.st50{opacity:0.06;fill:#00B7FF;}
|
||||
.st51{opacity:0.06;fill:#FF9D00;}
|
||||
.st52{opacity:0.06;fill:#00CC8D;}
|
||||
.st53{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st54{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st55{fill:#F15A24;}
|
||||
.st56{fill:#F15A24;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st57{fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st58{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st59{opacity:0.8;enable-background:new ;}
|
||||
.st60{clip-path:url(#SVGID_2_);}
|
||||
.st61{clip-path:url(#SVGID_4_);}
|
||||
.st62{clip-path:url(#SVGID_6_);fill:#333333;}
|
||||
.st63{clip-path:url(#SVGID_8_);}
|
||||
.st64{clip-path:url(#SVGID_10_);fill:#333333;}
|
||||
.st65{clip-path:url(#SVGID_12_);}
|
||||
.st66{clip-path:url(#SVGID_14_);}
|
||||
.st67{clip-path:url(#SVGID_16_);fill:#333333;}
|
||||
.st68{clip-path:url(#SVGID_18_);}
|
||||
.st69{clip-path:url(#SVGID_20_);fill:#333333;}
|
||||
.st70{clip-path:url(#SVGID_22_);}
|
||||
.st71{clip-path:url(#SVGID_24_);fill:#333333;}
|
||||
.st72{clip-path:url(#SVGID_26_);}
|
||||
.st73{clip-path:url(#SVGID_28_);fill:#333333;}
|
||||
.st74{clip-path:url(#SVGID_30_);}
|
||||
.st75{clip-path:url(#SVGID_32_);fill:#333333;}
|
||||
.st76{clip-path:url(#SVGID_34_);}
|
||||
.st77{clip-path:url(#SVGID_36_);fill:#333333;}
|
||||
.st78{clip-path:url(#SVGID_38_);}
|
||||
.st79{clip-path:url(#SVGID_40_);fill:#333333;}
|
||||
.st80{clip-path:url(#SVGID_42_);}
|
||||
.st81{clip-path:url(#SVGID_44_);fill:#333333;}
|
||||
.st82{clip-path:url(#SVGID_46_);}
|
||||
.st83{clip-path:url(#SVGID_48_);fill:#333333;}
|
||||
.st84{clip-path:url(#SVGID_50_);}
|
||||
.st85{clip-path:url(#SVGID_52_);fill:#333333;}
|
||||
.st86{clip-path:url(#SVGID_54_);}
|
||||
.st87{clip-path:url(#SVGID_56_);fill:#333333;}
|
||||
.st88{clip-path:url(#SVGID_58_);}
|
||||
.st89{clip-path:url(#SVGID_60_);fill:#333333;}
|
||||
.st90{clip-path:url(#SVGID_62_);}
|
||||
.st91{clip-path:url(#SVGID_64_);}
|
||||
.st92{clip-path:url(#SVGID_66_);enable-background:new ;}
|
||||
.st93{clip-path:url(#SVGID_68_);}
|
||||
.st94{clip-path:url(#SVGID_70_);}
|
||||
.st95{clip-path:url(#SVGID_72_);fill:#333333;}
|
||||
.st96{clip-path:url(#SVGID_74_);}
|
||||
.st97{clip-path:url(#SVGID_76_);}
|
||||
.st98{clip-path:url(#SVGID_78_);fill:#333333;}
|
||||
.st99{clip-path:url(#SVGID_80_);}
|
||||
.st100{clip-path:url(#SVGID_82_);fill:#333333;}
|
||||
.st101{clip-path:url(#SVGID_84_);}
|
||||
.st102{clip-path:url(#SVGID_86_);}
|
||||
.st103{clip-path:url(#SVGID_88_);fill:#333333;}
|
||||
.st104{clip-path:url(#SVGID_90_);}
|
||||
.st105{clip-path:url(#SVGID_92_);fill:#333333;}
|
||||
.st106{clip-path:url(#SVGID_94_);}
|
||||
.st107{clip-path:url(#SVGID_96_);fill:#333333;}
|
||||
.st108{clip-path:url(#SVGID_98_);}
|
||||
.st109{clip-path:url(#SVGID_100_);fill:#333333;}
|
||||
.st110{opacity:0.8;clip-path:url(#SVGID_102_);fill:#333333;}
|
||||
.st3{fill:#C1631E;}
|
||||
.st4{fill:#FFFFFF;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st5{fill:#FFFFFF;stroke:#000000;stroke-width:7;stroke-miterlimit:10;}
|
||||
.st6{fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st7{fill:#FFFFFF;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st8{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st9{fill:#F15A24;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st10{fill:none;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st11{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st12{fill:#FFFFFF;}
|
||||
.st13{fill:#FFFFFF;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st14{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:4.088,10.22;}
|
||||
.st15{fill:#FFFFFF;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st16{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st17{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st18{fill:none;stroke:#969696;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st19{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st20{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st21{fill:none;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st22{fill:#FFFFFF;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st23{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st24{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st25{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3.2727,9.8182;}
|
||||
.st26{fill:none;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st27{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st28{fill:none;stroke:#333333;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st29{fill:none;stroke:#E6E6E6;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st30{fill:#1EB287;}
|
||||
.st31{fill:#DEEDCB;}
|
||||
.st32{fill:#505305;}
|
||||
.st33{fill:#186435;}
|
||||
.st34{fill:#A44F79;}
|
||||
.st35{fill:#2AB188;}
|
||||
.st36{fill:#A35915;}
|
||||
.st37{fill:#4D4D4D;}
|
||||
.st38{fill:#F6B330;}
|
||||
.st39{fill:#324872;}
|
||||
.st40{fill:#2BA270;}
|
||||
.st41{fill:#53A4EA;}
|
||||
.st42{fill:#3BA2A0;}
|
||||
.st43{fill:#1792CD;}
|
||||
.st44{fill:#0C2E3D;}
|
||||
.st45{fill:#35761B;}
|
||||
.st46{fill:#0C6364;}
|
||||
.st47{fill:#F4A519;}
|
||||
.st48{opacity:0.06;fill:#3B9910;}
|
||||
.st49{opacity:0.06;fill:#E56200;}
|
||||
.st50{opacity:0.06;fill:#2E5799;}
|
||||
.st51{opacity:0.06;fill:#007F7C;}
|
||||
.st52{opacity:0.06;fill:#00B7FF;}
|
||||
.st53{opacity:0.06;fill:#FF9D00;}
|
||||
.st54{opacity:0.06;fill:#00CC8D;}
|
||||
.st55{fill:#91A7B4;}
|
||||
.st56{fill:#9F6300;}
|
||||
.st57{fill:#07B386;}
|
||||
.st58{fill:#FF4D2D;}
|
||||
.st59{fill:#F9FBFC;}
|
||||
.st60{fill:#FBF7F0;}
|
||||
.st61{fill:#F0F8F8;}
|
||||
.st62{fill:#F1FCF9;}
|
||||
.st63{fill:#FFF7F6;}
|
||||
.st64{fill:#AF8761;}
|
||||
.st65{fill:#FFF8F3;}
|
||||
.st66{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st67{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st68{fill:#F15A24;}
|
||||
.st69{fill:#F15A24;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st70{fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st71{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st72{opacity:0.8;enable-background:new ;}
|
||||
.st73{clip-path:url(#SVGID_2_);}
|
||||
.st74{clip-path:url(#SVGID_4_);}
|
||||
.st75{clip-path:url(#SVGID_6_);fill:#333333;}
|
||||
.st76{clip-path:url(#SVGID_8_);}
|
||||
.st77{clip-path:url(#SVGID_10_);fill:#333333;}
|
||||
.st78{clip-path:url(#SVGID_12_);}
|
||||
.st79{clip-path:url(#SVGID_14_);}
|
||||
.st80{clip-path:url(#SVGID_16_);fill:#333333;}
|
||||
.st81{clip-path:url(#SVGID_18_);}
|
||||
.st82{clip-path:url(#SVGID_20_);fill:#333333;}
|
||||
.st83{clip-path:url(#SVGID_22_);}
|
||||
.st84{clip-path:url(#SVGID_24_);fill:#333333;}
|
||||
.st85{clip-path:url(#SVGID_26_);}
|
||||
.st86{clip-path:url(#SVGID_28_);fill:#333333;}
|
||||
.st87{clip-path:url(#SVGID_30_);}
|
||||
.st88{clip-path:url(#SVGID_32_);fill:#333333;}
|
||||
.st89{clip-path:url(#SVGID_34_);}
|
||||
.st90{clip-path:url(#SVGID_36_);fill:#333333;}
|
||||
.st91{clip-path:url(#SVGID_38_);}
|
||||
.st92{clip-path:url(#SVGID_40_);fill:#333333;}
|
||||
.st93{clip-path:url(#SVGID_42_);}
|
||||
.st94{clip-path:url(#SVGID_44_);fill:#333333;}
|
||||
.st95{clip-path:url(#SVGID_46_);}
|
||||
.st96{clip-path:url(#SVGID_48_);fill:#333333;}
|
||||
.st97{clip-path:url(#SVGID_50_);}
|
||||
.st98{clip-path:url(#SVGID_52_);fill:#333333;}
|
||||
.st99{clip-path:url(#SVGID_54_);}
|
||||
.st100{clip-path:url(#SVGID_56_);fill:#333333;}
|
||||
.st101{clip-path:url(#SVGID_58_);}
|
||||
.st102{clip-path:url(#SVGID_60_);fill:#333333;}
|
||||
.st103{clip-path:url(#SVGID_62_);}
|
||||
.st104{clip-path:url(#SVGID_64_);}
|
||||
.st105{clip-path:url(#SVGID_66_);enable-background:new ;}
|
||||
.st106{clip-path:url(#SVGID_68_);}
|
||||
.st107{clip-path:url(#SVGID_70_);}
|
||||
.st108{clip-path:url(#SVGID_72_);fill:#333333;}
|
||||
.st109{clip-path:url(#SVGID_74_);}
|
||||
.st110{clip-path:url(#SVGID_76_);}
|
||||
.st111{clip-path:url(#SVGID_78_);fill:#333333;}
|
||||
.st112{clip-path:url(#SVGID_80_);}
|
||||
.st113{clip-path:url(#SVGID_82_);fill:#333333;}
|
||||
.st114{clip-path:url(#SVGID_84_);}
|
||||
.st115{clip-path:url(#SVGID_86_);}
|
||||
.st116{clip-path:url(#SVGID_88_);fill:#333333;}
|
||||
.st117{clip-path:url(#SVGID_90_);}
|
||||
.st118{clip-path:url(#SVGID_92_);fill:#333333;}
|
||||
.st119{clip-path:url(#SVGID_94_);}
|
||||
.st120{clip-path:url(#SVGID_96_);fill:#333333;}
|
||||
.st121{clip-path:url(#SVGID_98_);}
|
||||
.st122{clip-path:url(#SVGID_100_);fill:#333333;}
|
||||
.st123{opacity:0.8;clip-path:url(#SVGID_102_);fill:#333333;}
|
||||
.st124{fill:#FFFFFF;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st125{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st126{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st127{fill:#FFFFFF;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st128{opacity:0.8;}
|
||||
.st129{opacity:0.8;fill:#FFFFFF;}
|
||||
.st130{fill:none;stroke:#FFFFFF;stroke-width:6;stroke-miterlimit:10;}
|
||||
.st131{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st132{opacity:0.2;fill:url(#SVGID_103_);}
|
||||
.st133{fill:#D8EAD2;}
|
||||
.st134{opacity:0.2;fill:url(#SVGID_104_);}
|
||||
.st135{opacity:0.06;fill:#E07127;}
|
||||
.st136{fill:#FFFFFF;stroke:#00B485;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
|
@ -122,19 +148,11 @@
|
|||
<g id="Layer_4">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path d="M46.86,22.5l-3.1,0c-0.82,0-1.53-0.57-1.71-1.37c-1.47-6.45-6.53-11.65-13.14-13.17C28.09,7.78,27.5,7.05,27.5,6.2l0-3.06
|
||||
c0-1.31-0.94-2.5-2.24-2.63C23.76,0.36,22.5,1.53,22.5,3v3.24c0,0.82-0.57,1.52-1.37,1.71C14.68,9.42,9.48,14.48,7.97,21.09
|
||||
C7.78,21.91,7.05,22.5,6.2,22.5l-3.06,0c-1.31,0-2.5,0.94-2.63,2.24C0.36,26.24,1.53,27.5,3,27.5h3.19c0.84,0,1.58,0.58,1.76,1.4
|
||||
c0.61,2.69,1.84,5.21,3.67,7.37c2.48,2.94,5.79,4.93,9.48,5.76c0.82,0.19,1.4,0.92,1.4,1.76l0,3.06c0,1.31,0.94,2.5,2.24,2.63
|
||||
c1.5,0.15,2.76-1.02,2.76-2.49v-3.24c0-0.82,0.57-1.53,1.37-1.71c6.45-1.47,11.66-6.53,13.17-13.14c0.19-0.83,0.92-1.41,1.77-1.41
|
||||
H47c1.47,0,2.64-1.26,2.49-2.76C49.36,23.44,48.17,22.5,46.86,22.5z M28.31,37.05c-0.91,0.25-1.81-0.41-1.81-1.36v-3.91
|
||||
c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5v3.9c0,0.91-0.86,1.61-1.73,1.38c-2.46-0.65-4.65-2.03-6.32-4.01
|
||||
c-1.2-1.43-2.05-3.07-2.52-4.82c-0.23-0.88,0.47-1.73,1.38-1.73h3.92c0.83,0,1.5-0.67,1.5-1.5s-0.67-1.5-1.5-1.5h-3.9
|
||||
c-0.94,0-1.6-0.91-1.35-1.82c1.17-4.27,4.54-7.57,8.72-8.73c0.91-0.25,1.81,0.41,1.81,1.36v3.91c0,0.83,0.67,1.5,1.5,1.5
|
||||
s1.5-0.67,1.5-1.5v-3.9c0-0.94,0.91-1.6,1.82-1.35c4.27,1.17,7.57,4.54,8.73,8.72c0.25,0.91-0.41,1.81-1.36,1.81h-3.91
|
||||
c-0.83,0-1.5,0.67-1.5,1.5s0.67,1.5,1.5,1.5h3.9c0.95,0,1.6,0.91,1.35,1.82C35.86,32.59,32.5,35.89,28.31,37.05z"/>
|
||||
<circle cx="25" cy="25" r="1.75"/>
|
||||
</g>
|
||||
<path d="M23.52,0.61c-3.43,0.46-6.45,2.65-8.02,5.72c-2.16,4.22-1.22,8.99,1.69,12.12c0.46,0.5,0.76,1.13,0.87,1.8l4.46,27.02
|
||||
c0.18,1.07,0.97,1.99,2.03,2.18c1.41,0.25,2.7-0.7,2.92-2.05l4.48-27.15c0.11-0.67,0.41-1.3,0.87-1.8
|
||||
c1.82-1.96,2.86-4.56,2.86-7.26C35.68,4.81,30.07-0.28,23.52,0.61z M19.3,11.19c0-3.14,2.56-5.7,5.7-5.7c3.14,0,5.7,2.56,5.7,5.7
|
||||
s-2.56,5.7-5.7,5.7C21.86,16.89,19.3,14.33,19.3,11.19z M24.31,27.46l-1.14-6.88c-0.07-0.44,0.3-0.82,0.74-0.77
|
||||
c0.36,0.05,0.72,0.07,1.09,0.07c0.37,0,0.73-0.03,1.09-0.07c0.44-0.06,0.81,0.33,0.74,0.77l-1.14,6.88
|
||||
C25.56,28.24,24.44,28.24,24.31,27.46z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 8.0 KiB |
|
@ -0,0 +1,141 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st1{fill:#C46F24;}
|
||||
.st2{opacity:0.5;fill:#C46F24;}
|
||||
.st3{fill:#FFFFFF;stroke:#000000;stroke-width:7;stroke-miterlimit:10;}
|
||||
.st4{fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st5{fill:#FFFFFF;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st6{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st7{fill:#F15A24;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st8{fill:none;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st9{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st10{fill:#FFFFFF;}
|
||||
.st11{fill:#FFFFFF;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st12{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:4.088,10.22;}
|
||||
.st13{fill:#FFFFFF;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st14{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st15{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st16{fill:none;stroke:#969696;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st17{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st18{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st19{fill:none;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st20{fill:#FFFFFF;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st21{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st22{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st23{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3.2727,9.8182;}
|
||||
.st24{fill:none;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st25{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st26{fill:none;stroke:#333333;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st27{fill:none;stroke:#E6E6E6;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st28{fill:#1EB287;}
|
||||
.st29{fill:#DEEDCB;}
|
||||
.st30{fill:#505305;}
|
||||
.st31{fill:#186435;}
|
||||
.st32{fill:#A44F79;}
|
||||
.st33{fill:#2AB188;}
|
||||
.st34{fill:#A35915;}
|
||||
.st35{fill:#4D4D4D;}
|
||||
.st36{fill:#F6B330;}
|
||||
.st37{fill:#324872;}
|
||||
.st38{fill:#2BA270;}
|
||||
.st39{fill:#53A4EA;}
|
||||
.st40{fill:#3BA2A0;}
|
||||
.st41{fill:#1792CD;}
|
||||
.st42{fill:#0C2E3D;}
|
||||
.st43{fill:#35761B;}
|
||||
.st44{fill:#0C6364;}
|
||||
.st45{fill:#F4A519;}
|
||||
.st46{opacity:0.06;fill:#3B9910;}
|
||||
.st47{opacity:0.06;fill:#E56200;}
|
||||
.st48{opacity:0.06;fill:#2E5799;}
|
||||
.st49{opacity:0.06;fill:#007F7C;}
|
||||
.st50{opacity:0.06;fill:#00B7FF;}
|
||||
.st51{opacity:0.06;fill:#FF9D00;}
|
||||
.st52{opacity:0.06;fill:#00CC8D;}
|
||||
.st53{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st54{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st55{fill:#F15A24;}
|
||||
.st56{fill:#F15A24;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st57{fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st58{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st59{opacity:0.8;enable-background:new ;}
|
||||
.st60{clip-path:url(#SVGID_2_);}
|
||||
.st61{clip-path:url(#SVGID_4_);}
|
||||
.st62{clip-path:url(#SVGID_6_);fill:#333333;}
|
||||
.st63{clip-path:url(#SVGID_8_);}
|
||||
.st64{clip-path:url(#SVGID_10_);fill:#333333;}
|
||||
.st65{clip-path:url(#SVGID_12_);}
|
||||
.st66{clip-path:url(#SVGID_14_);}
|
||||
.st67{clip-path:url(#SVGID_16_);fill:#333333;}
|
||||
.st68{clip-path:url(#SVGID_18_);}
|
||||
.st69{clip-path:url(#SVGID_20_);fill:#333333;}
|
||||
.st70{clip-path:url(#SVGID_22_);}
|
||||
.st71{clip-path:url(#SVGID_24_);fill:#333333;}
|
||||
.st72{clip-path:url(#SVGID_26_);}
|
||||
.st73{clip-path:url(#SVGID_28_);fill:#333333;}
|
||||
.st74{clip-path:url(#SVGID_30_);}
|
||||
.st75{clip-path:url(#SVGID_32_);fill:#333333;}
|
||||
.st76{clip-path:url(#SVGID_34_);}
|
||||
.st77{clip-path:url(#SVGID_36_);fill:#333333;}
|
||||
.st78{clip-path:url(#SVGID_38_);}
|
||||
.st79{clip-path:url(#SVGID_40_);fill:#333333;}
|
||||
.st80{clip-path:url(#SVGID_42_);}
|
||||
.st81{clip-path:url(#SVGID_44_);fill:#333333;}
|
||||
.st82{clip-path:url(#SVGID_46_);}
|
||||
.st83{clip-path:url(#SVGID_48_);fill:#333333;}
|
||||
.st84{clip-path:url(#SVGID_50_);}
|
||||
.st85{clip-path:url(#SVGID_52_);fill:#333333;}
|
||||
.st86{clip-path:url(#SVGID_54_);}
|
||||
.st87{clip-path:url(#SVGID_56_);fill:#333333;}
|
||||
.st88{clip-path:url(#SVGID_58_);}
|
||||
.st89{clip-path:url(#SVGID_60_);fill:#333333;}
|
||||
.st90{clip-path:url(#SVGID_62_);}
|
||||
.st91{clip-path:url(#SVGID_64_);}
|
||||
.st92{clip-path:url(#SVGID_66_);enable-background:new ;}
|
||||
.st93{clip-path:url(#SVGID_68_);}
|
||||
.st94{clip-path:url(#SVGID_70_);}
|
||||
.st95{clip-path:url(#SVGID_72_);fill:#333333;}
|
||||
.st96{clip-path:url(#SVGID_74_);}
|
||||
.st97{clip-path:url(#SVGID_76_);}
|
||||
.st98{clip-path:url(#SVGID_78_);fill:#333333;}
|
||||
.st99{clip-path:url(#SVGID_80_);}
|
||||
.st100{clip-path:url(#SVGID_82_);fill:#333333;}
|
||||
.st101{clip-path:url(#SVGID_84_);}
|
||||
.st102{clip-path:url(#SVGID_86_);}
|
||||
.st103{clip-path:url(#SVGID_88_);fill:#333333;}
|
||||
.st104{clip-path:url(#SVGID_90_);}
|
||||
.st105{clip-path:url(#SVGID_92_);fill:#333333;}
|
||||
.st106{clip-path:url(#SVGID_94_);}
|
||||
.st107{clip-path:url(#SVGID_96_);fill:#333333;}
|
||||
.st108{clip-path:url(#SVGID_98_);}
|
||||
.st109{clip-path:url(#SVGID_100_);fill:#333333;}
|
||||
.st110{opacity:0.8;clip-path:url(#SVGID_102_);fill:#333333;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_3">
|
||||
</g>
|
||||
<g id="Layer_4">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path d="M29.58,2.46C23.56,1.23,17.43,2.43,12.3,5.82s-8.62,8.58-9.84,14.6S2.43,32.58,5.82,37.7c3.39,5.12,8.58,8.62,14.6,9.84
|
||||
c1.54,0.31,3.07,0.46,4.59,0.46c10.71,0,20.32-7.53,22.53-18.42C50.07,17.15,42.01,4.99,29.58,2.46z M8.93,16.83
|
||||
c0.68-1.4,1.57-2.7,2.62-3.84c3.62-3.93,8.44-5.99,13.41-5.99c1.2,0,2.42,0.12,3.62,0.37c3.52,0.72,6.59,2.43,8.99,4.78
|
||||
c0.86,0.84,0.74,2.25-0.26,2.91l-3.54,2.34c-0.66,0.44-1.54,0.34-2.09-0.23c-3.52-3.67-8.7-5.55-14.38-5.1
|
||||
c-0.51,0.04-0.98,0.35-1.22,0.81c-2.63,5.06-2.91,10.57-0.91,15.24c0.31,0.72,0.05,1.56-0.61,2l-3.54,2.35
|
||||
c-1,0.67-2.35,0.21-2.79-0.91C6.41,26.91,6.56,21.66,8.93,16.83z M19.78,15.02c3.58,0.17,6.79,1.47,9.18,3.67
|
||||
c0.61,0.56,0.51,1.56-0.18,2.02l-2.32,1.53c-0.57,0.38-1.33,0.22-1.71-0.35l-0.66-1c-0.46-0.69-1.39-0.88-2.08-0.42
|
||||
c-0.69,0.46-0.88,1.39-0.42,2.08l0.66,1c0.38,0.57,0.22,1.33-0.35,1.71l-2.32,1.54c-0.69,0.46-1.65,0.16-1.93-0.62
|
||||
c-1.09-3.06-1.03-6.52,0.2-9.88C18.14,15.5,18.92,14.98,19.78,15.02z M25.26,28.11l3.64,5.5c0.36,0.54-0.1,1.25-0.74,1.14
|
||||
c-2.74-0.49-5.19-1.67-7.11-3.43c-0.61-0.56-0.51-1.56,0.18-2.02l2.31-1.53C24.12,27.38,24.88,27.54,25.26,28.11z M31.41,31.95
|
||||
l-3.64-5.5c-0.38-0.57-0.22-1.33,0.35-1.71l2.32-1.54c0.69-0.46,1.65-0.16,1.93,0.62c0.88,2.46,1.01,5.18,0.38,7.89
|
||||
C32.6,32.34,31.76,32.49,31.41,31.95z M41.84,31.37c-2.76,7.41-10.26,12.2-18.15,11.59c-4.36-0.34-8.29-2.16-11.27-5.08
|
||||
c-0.86-0.84-0.75-2.26,0.26-2.92l3.54-2.35c0.66-0.44,1.54-0.34,2.09,0.23C21.49,36.15,26.02,38,31.05,38
|
||||
c0.54,0,1.09-0.02,1.64-0.06c0.51-0.04,0.98-0.35,1.22-0.81c2.63-5.06,2.91-10.57,0.91-15.24c-0.31-0.72-0.05-1.56,0.61-2
|
||||
l3.53-2.34c1-0.66,2.34-0.22,2.78,0.89C43.3,22.38,43.5,26.92,41.84,31.37z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.8 KiB |
|
@ -0,0 +1,159 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st1{fill:#C46F24;}
|
||||
.st2{opacity:0.5;fill:#C46F24;}
|
||||
.st3{fill:#FFFFFF;stroke:#000000;stroke-width:7;stroke-miterlimit:10;}
|
||||
.st4{fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st5{fill:#FFFFFF;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st6{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st7{fill:#F15A24;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st8{fill:none;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st9{fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st10{fill:#FFFFFF;}
|
||||
.st11{fill:#FFFFFF;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st12{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:4.088,10.22;}
|
||||
.st13{fill:#FFFFFF;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st14{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st15{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st16{fill:none;stroke:#969696;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st17{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st18{fill:#B3B3B3;stroke:#4D4D4D;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st19{fill:none;stroke:#4D4D4D;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st20{fill:#FFFFFF;stroke:#000000;stroke-miterlimit:10;}
|
||||
.st21{fill:none;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st22{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st23{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3.2727,9.8182;}
|
||||
.st24{fill:none;stroke:#333333;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st25{fill:none;stroke:#333333;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st26{fill:none;stroke:#333333;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st27{fill:none;stroke:#E6E6E6;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st28{fill:#1EB287;}
|
||||
.st29{fill:#DEEDCB;}
|
||||
.st30{fill:#505305;}
|
||||
.st31{fill:#186435;}
|
||||
.st32{fill:#A44F79;}
|
||||
.st33{fill:#2AB188;}
|
||||
.st34{fill:#A35915;}
|
||||
.st35{fill:#4D4D4D;}
|
||||
.st36{fill:#F6B330;}
|
||||
.st37{fill:#324872;}
|
||||
.st38{fill:#2BA270;}
|
||||
.st39{fill:#53A4EA;}
|
||||
.st40{fill:#3BA2A0;}
|
||||
.st41{fill:#1792CD;}
|
||||
.st42{fill:#0C2E3D;}
|
||||
.st43{fill:#35761B;}
|
||||
.st44{fill:#0C6364;}
|
||||
.st45{fill:#F4A519;}
|
||||
.st46{opacity:0.06;fill:#3B9910;}
|
||||
.st47{opacity:0.06;fill:#E56200;}
|
||||
.st48{opacity:0.06;fill:#2E5799;}
|
||||
.st49{opacity:0.06;fill:#007F7C;}
|
||||
.st50{opacity:0.06;fill:#00B7FF;}
|
||||
.st51{opacity:0.06;fill:#FF9D00;}
|
||||
.st52{opacity:0.06;fill:#00CC8D;}
|
||||
.st53{fill:none;stroke:#FFFFFF;stroke-width:5;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st54{fill:none;stroke:#FFFFFF;stroke-width:3;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
.st55{fill:#F15A24;}
|
||||
.st56{fill:#F15A24;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st57{fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:10;}
|
||||
.st58{fill:#FFFFFF;stroke:#000000;stroke-width:3;stroke-miterlimit:10;}
|
||||
.st59{opacity:0.8;enable-background:new ;}
|
||||
.st60{clip-path:url(#SVGID_2_);}
|
||||
.st61{clip-path:url(#SVGID_4_);}
|
||||
.st62{clip-path:url(#SVGID_6_);fill:#333333;}
|
||||
.st63{clip-path:url(#SVGID_8_);}
|
||||
.st64{clip-path:url(#SVGID_10_);fill:#333333;}
|
||||
.st65{clip-path:url(#SVGID_12_);}
|
||||
.st66{clip-path:url(#SVGID_14_);}
|
||||
.st67{clip-path:url(#SVGID_16_);fill:#333333;}
|
||||
.st68{clip-path:url(#SVGID_18_);}
|
||||
.st69{clip-path:url(#SVGID_20_);fill:#333333;}
|
||||
.st70{clip-path:url(#SVGID_22_);}
|
||||
.st71{clip-path:url(#SVGID_24_);fill:#333333;}
|
||||
.st72{clip-path:url(#SVGID_26_);}
|
||||
.st73{clip-path:url(#SVGID_28_);fill:#333333;}
|
||||
.st74{clip-path:url(#SVGID_30_);}
|
||||
.st75{clip-path:url(#SVGID_32_);fill:#333333;}
|
||||
.st76{clip-path:url(#SVGID_34_);}
|
||||
.st77{clip-path:url(#SVGID_36_);fill:#333333;}
|
||||
.st78{clip-path:url(#SVGID_38_);}
|
||||
.st79{clip-path:url(#SVGID_40_);fill:#333333;}
|
||||
.st80{clip-path:url(#SVGID_42_);}
|
||||
.st81{clip-path:url(#SVGID_44_);fill:#333333;}
|
||||
.st82{clip-path:url(#SVGID_46_);}
|
||||
.st83{clip-path:url(#SVGID_48_);fill:#333333;}
|
||||
.st84{clip-path:url(#SVGID_50_);}
|
||||
.st85{clip-path:url(#SVGID_52_);fill:#333333;}
|
||||
.st86{clip-path:url(#SVGID_54_);}
|
||||
.st87{clip-path:url(#SVGID_56_);fill:#333333;}
|
||||
.st88{clip-path:url(#SVGID_58_);}
|
||||
.st89{clip-path:url(#SVGID_60_);fill:#333333;}
|
||||
.st90{clip-path:url(#SVGID_62_);}
|
||||
.st91{clip-path:url(#SVGID_64_);}
|
||||
.st92{clip-path:url(#SVGID_66_);enable-background:new ;}
|
||||
.st93{clip-path:url(#SVGID_68_);}
|
||||
.st94{clip-path:url(#SVGID_70_);}
|
||||
.st95{clip-path:url(#SVGID_72_);fill:#333333;}
|
||||
.st96{clip-path:url(#SVGID_74_);}
|
||||
.st97{clip-path:url(#SVGID_76_);}
|
||||
.st98{clip-path:url(#SVGID_78_);fill:#333333;}
|
||||
.st99{clip-path:url(#SVGID_80_);}
|
||||
.st100{clip-path:url(#SVGID_82_);fill:#333333;}
|
||||
.st101{clip-path:url(#SVGID_84_);}
|
||||
.st102{clip-path:url(#SVGID_86_);}
|
||||
.st103{clip-path:url(#SVGID_88_);fill:#333333;}
|
||||
.st104{clip-path:url(#SVGID_90_);}
|
||||
.st105{clip-path:url(#SVGID_92_);fill:#333333;}
|
||||
.st106{clip-path:url(#SVGID_94_);}
|
||||
.st107{clip-path:url(#SVGID_96_);fill:#333333;}
|
||||
.st108{clip-path:url(#SVGID_98_);}
|
||||
.st109{clip-path:url(#SVGID_100_);fill:#333333;}
|
||||
.st110{opacity:0.8;clip-path:url(#SVGID_102_);fill:#333333;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_3">
|
||||
</g>
|
||||
<g id="Layer_4">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path d="M46.15,40c-0.85-0.25-1.73-0.14-2.46,0.31c-0.38,0.23-0.69,0.54-0.93,0.9c-0.3,0.46-0.69,0.85-1.15,1.15
|
||||
c-0.36,0.24-0.67,0.56-0.9,0.93c-0.45,0.73-0.56,1.61-0.31,2.46c0.36,1.24,1.58,2.11,2.96,2.11h1.91c1.71,0,3-1.38,3-3.21v-1.7
|
||||
C48.25,41.58,47.39,40.36,46.15,40z"/>
|
||||
<path d="M32.56,41.87h-3.15c-0.92,0-1.78,0.42-2.36,1.14c-0.57,0.73-0.78,1.67-0.55,2.59c0.31,1.31,1.59,2.26,3.03,2.26h3.15
|
||||
c0.92,0,1.78-0.42,2.36-1.14c0.57-0.73,0.78-1.67,0.55-2.59C35.28,42.82,34,41.87,32.56,41.87z"/>
|
||||
<path d="M19.47,41.87h-3.15c-0.92,0-1.78,0.42-2.36,1.14c-0.57,0.73-0.78,1.67-0.55,2.59c0.31,1.31,1.59,2.26,3.03,2.26h3.15
|
||||
c0.92,0,1.78-0.42,2.36-1.14c0.57-0.73,0.78-1.67,0.55-2.59C22.19,42.82,20.91,41.87,19.47,41.87z"/>
|
||||
<path d="M8.91,42.37c-0.46-0.3-0.85-0.69-1.15-1.15c-0.24-0.36-0.56-0.67-0.93-0.9C6.09,39.86,5.21,39.75,4.36,40
|
||||
c-1.24,0.36-2.11,1.58-2.11,2.96v1.91c0,1.66,1.34,3,3,3h1.91c1.38,0,2.59-0.87,2.96-2.11c0.25-0.85,0.14-1.73-0.31-2.46
|
||||
C9.58,42.92,9.27,42.6,8.91,42.37z"/>
|
||||
<path d="M4.52,35.59c0.24,0.06,0.49,0.09,0.73,0.09c0.67,0,1.32-0.22,1.86-0.64c0.73-0.57,1.14-1.43,1.14-2.36v-3.27
|
||||
c0-0.92-0.42-1.78-1.14-2.36s-1.67-0.78-2.59-0.56c-1.31,0.32-2.26,1.59-2.26,3.03v3.03C2.25,34,3.2,35.28,4.52,35.59z"/>
|
||||
<path d="M4.52,22.5c0.24,0.06,0.49,0.09,0.73,0.09c0.67,0,1.32-0.22,1.86-0.64c0.73-0.57,1.14-1.43,1.14-2.36v-3.27
|
||||
c0-0.92-0.42-1.78-1.14-2.36c-0.73-0.57-1.67-0.78-2.59-0.56C3.2,13.72,2.25,15,2.25,16.44v3.03C2.25,20.91,3.2,22.19,4.52,22.5z"
|
||||
/>
|
||||
<path d="M7.16,1.87H5.25c-1.66,0-3,1.34-3,3v1.91c0,1.38,0.87,2.59,2.11,2.96c0.3,0.09,0.6,0.13,0.9,0.13
|
||||
c0.55,0,1.09-0.15,1.57-0.44c0.38-0.23,0.69-0.54,0.93-0.9c0.3-0.46,0.69-0.85,1.15-1.15c0.36-0.24,0.67-0.56,0.9-0.93
|
||||
c0.45-0.73,0.56-1.61,0.31-2.46C9.76,2.73,8.54,1.87,7.16,1.87z"/>
|
||||
<path d="M32.56,1.87h-3.15c-0.92,0-1.78,0.42-2.36,1.14c-0.57,0.73-0.78,1.67-0.55,2.59c0.31,1.31,1.59,2.26,3.03,2.26h3.15
|
||||
c0.92,0,1.78-0.42,2.36-1.14c0.57-0.73,0.78-1.67,0.55-2.59C35.28,2.82,34,1.87,32.56,1.87z"/>
|
||||
<path d="M19.47,1.87h-3.15c-0.92,0-1.78,0.42-2.36,1.14c-0.57,0.73-0.78,1.67-0.55,2.59c0.31,1.31,1.59,2.26,3.03,2.26h3.15
|
||||
c0.92,0,1.78-0.42,2.36-1.14C22.52,6,22.72,5.05,22.5,4.13C22.19,2.82,20.91,1.87,19.47,1.87z"/>
|
||||
<path d="M45.25,1.87h-1.91c-1.38,0-2.59,0.87-2.96,2.11c-0.25,0.85-0.14,1.73,0.31,2.46c0.23,0.38,0.54,0.69,0.9,0.93
|
||||
c0.46,0.3,0.85,0.69,1.15,1.15c0.24,0.36,0.56,0.67,0.93,0.9c0.48,0.29,1.02,0.44,1.57,0.44c0.3,0,0.6-0.04,0.9-0.13
|
||||
c1.24-0.36,2.11-1.58,2.11-2.96V4.87C48.25,3.21,46.91,1.87,45.25,1.87z"/>
|
||||
<path d="M47.11,27.05c-0.73-0.57-1.67-0.77-2.59-0.56c-1.31,0.32-2.26,1.59-2.26,3.03v3.03c0,1.44,0.95,2.72,2.26,3.03
|
||||
c0.24,0.06,0.49,0.09,0.73,0.09c0.67,0,1.32-0.22,1.86-0.64c0.73-0.57,1.14-1.43,1.14-2.36v-3.27
|
||||
C48.25,28.48,47.84,27.63,47.11,27.05z"/>
|
||||
<path d="M47.11,13.96c-0.73-0.57-1.67-0.78-2.59-0.56c-1.31,0.32-2.26,1.59-2.26,3.03v3.03c0,1.44,0.95,2.72,2.26,3.03
|
||||
c0.24,0.06,0.49,0.09,0.73,0.09c0.67,0,1.32-0.22,1.86-0.64c0.73-0.57,1.14-1.43,1.14-2.36v-3.27
|
||||
C48.25,15.39,47.84,14.53,47.11,13.96z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.1 KiB |
|
@ -205,6 +205,8 @@ describe Api::RmqUtilsController do
|
|||
".status.*",
|
||||
".status",
|
||||
".sync.*",
|
||||
".telemetry.*",
|
||||
".telemetry",
|
||||
".sync",
|
||||
".status_v8.*",
|
||||
".status_v8"].map { |x| expect(random_channel(x).match(r)).to be }
|
||||
|
|
|
@ -13,8 +13,6 @@ describe LogService do
|
|||
channels: [],
|
||||
}.to_json
|
||||
|
||||
FakeDeliveryInfo = Struct.new(:routing_key, :device)
|
||||
|
||||
let!(:device) { FactoryBot.create(:device) }
|
||||
let!(:device_id) { device.id }
|
||||
let!(:fake_delivery_info) do
|
||||
|
@ -31,6 +29,12 @@ describe LogService do
|
|||
expect(calls).to include(["amq.topic", { routing_key: "bot.*.logs" }])
|
||||
end
|
||||
|
||||
it "has a telemetry_channel" do
|
||||
calls = Transport.current.telemetry_channel.calls[:bind]
|
||||
call = ["amq.topic", { :routing_key => "bot.*.telemetry" }]
|
||||
expect(calls).to include(call)
|
||||
end
|
||||
|
||||
it "has a resource_channel" do
|
||||
calls = Transport.current.resource_channel.calls[:bind]
|
||||
expect(calls).to include([
|
||||
|
@ -54,9 +58,29 @@ describe LogService do
|
|||
LogService.new.warn_user(data, time)
|
||||
end
|
||||
|
||||
it "triggers a throttle" do
|
||||
tp = LogService::THROTTLE_POLICY
|
||||
ls = LogService.new
|
||||
data = AmqpLogParser::DeliveryInfo.new
|
||||
data.device_id = FactoryBot.create(:device).id
|
||||
violation = ThrottlePolicy::Violation.new(Object.new)
|
||||
allow(ls).to receive(:deliver)
|
||||
expect(ls).to receive(:warn_user)
|
||||
expect(tp).to receive(:is_throttled)
|
||||
.with(data.device_id)
|
||||
.and_return(violation)
|
||||
ls.maybe_deliver(data)
|
||||
end
|
||||
|
||||
it "handles bad params" do
|
||||
expect do
|
||||
LogService.new.process(fake_delivery_info, {})
|
||||
end.to raise_error(Mutations::ValidationException)
|
||||
end
|
||||
|
||||
it "handles malformed params" do
|
||||
expect do
|
||||
LogService.new.process(fake_delivery_info, "}}{{")
|
||||
end.to raise_error(Mutations::ValidationException)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe TelemetryService do
|
||||
it "handles malformed JSON" do
|
||||
ts = TelemetryService.new
|
||||
routing_key = "bot.device_123.telemetry"
|
||||
payload = "}"
|
||||
expected = "{\"device\":\"device_123\"," \
|
||||
"\"is_telemetry\":true,\"bad_json\":\"}\"," \
|
||||
"\"message\":\"FAILED TELEMETRY MESSAGE " \
|
||||
"FROM device_123\"}\n"
|
||||
delivery_info =
|
||||
FakeDeliveryInfo.new(routing_key, payload)
|
||||
expect do
|
||||
ts.process(delivery_info, payload)
|
||||
end.to output(expected).to_stdout
|
||||
end
|
||||
|
||||
it "parses telemetry from the device" do
|
||||
ts = TelemetryService.new
|
||||
routing_key = "bot.device_123.telemetry"
|
||||
payload = {
|
||||
foo: "bar",
|
||||
# I'm putting this key here to make sure
|
||||
# bots cannot change their `device_id` /
|
||||
# spoof teleemetry of other bots.
|
||||
device: "device_456",
|
||||
}.to_json
|
||||
expected = [
|
||||
"{\"foo\":\"bar\"," \
|
||||
"\"device\":\"device_123\"," \
|
||||
"\"is_telemetry\":true," \
|
||||
"\"message\":\"TELEMETRY MESSAGE " \
|
||||
"FROM device_123\"}\n",
|
||||
].join("")
|
||||
delivery_info =
|
||||
FakeDeliveryInfo.new(routing_key, payload)
|
||||
expect do
|
||||
ts.process(delivery_info, payload)
|
||||
end.to output(expected).to_stdout
|
||||
end
|
||||
end
|
|
@ -171,3 +171,5 @@ class NiceResponse
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
FakeDeliveryInfo = Struct.new(:routing_key, :device)
|
||||
|
|