refactor external urls

pull/1698/head
gabrielburnworth 2020-02-15 10:30:23 -08:00
parent cf0af59e42
commit 66b5e3c962
29 changed files with 152 additions and 74 deletions

View File

@ -0,0 +1,33 @@
jest.unmock("../external_urls");
import { ExternalUrl } from "../external_urls";
/* tslint:disable:max-line-length */
describe("ExternalUrl", () => {
it("returns urls", () => {
expect(ExternalUrl.featureMinVersions)
.toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/FEATURE_MIN_VERSIONS.json");
expect(ExternalUrl.osReleaseNotes)
.toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/RELEASE_NOTES.md");
expect(ExternalUrl.latestRelease)
.toEqual("https://api.github.com/repos/FarmBot/farmbot_os/releases/latest");
expect(ExternalUrl.webAppRepo)
.toEqual("https://github.com/FarmBot/Farmbot-Web-App");
expect(ExternalUrl.gitHubFarmBot)
.toEqual("https://github.com/FarmBot");
expect(ExternalUrl.softwareDocs)
.toEqual("https://software.farm.bot/docs");
expect(ExternalUrl.softwareForum)
.toEqual("http://forum.farmbot.org/c/software");
expect(ExternalUrl.OpenFarm.cropApi)
.toEqual("https://openfarm.cc/api/v1/crops/");
expect(ExternalUrl.OpenFarm.cropBrowse)
.toEqual("https://openfarm.cc/crops/");
expect(ExternalUrl.OpenFarm.newCrop)
.toEqual("https://openfarm.cc/en/crops/new");
expect(ExternalUrl.Videos.desktop)
.toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Farm_Designer_Loop.mp4?9552037556691879018");
expect(ExternalUrl.Videos.mobile)
.toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Controls.png?9668345515035078097");
});
});

View File

@ -158,6 +158,10 @@ export class API {
get farmwareInstallationPath() { get farmwareInstallationPath() {
return `${this.baseUrl}/api/farmware_installations/`; return `${this.baseUrl}/api/farmware_installations/`;
} }
/** /api/first_party_farmwares */
get firstPartyFarmwarePath() {
return `${this.baseUrl}/api/first_party_farmwares`;
}
/** /api/alerts/:id */ /** /api/alerts/:id */
get alertPath() { return `${this.baseUrl}/api/alerts/`; } get alertPath() { return `${this.baseUrl}/api/alerts/`; }
/** /api/global_bulletins/:id */ /** /api/global_bulletins/:id */

View File

@ -1,5 +1,6 @@
import * as React from "react"; import * as React from "react";
import { Session } from "./session"; import { Session } from "./session";
import { ExternalUrl } from "./external_urls";
const OUTER_STYLE: React.CSSProperties = { const OUTER_STYLE: React.CSSProperties = {
borderRadius: "10px", borderRadius: "10px",
@ -47,7 +48,7 @@ export function Apology(_: {}) {
<li> <li>
<span> <span>
Send a report to our developer team via the&nbsp; Send a report to our developer team via the&nbsp;
<a href="http://forum.farmbot.org/c/software">FarmBot software <a href={ExternalUrl.softwareForum}>FarmBot software
forum</a>. Including additional information (such as steps leading up forum</a>. Including additional information (such as steps leading up
to the error) helps us identify solutions more quickly. to the error) helps us identify solutions more quickly.
</span> </span>

View File

@ -1,6 +1,6 @@
import axios from "axios"; import axios from "axios";
import { import {
fetchReleases, fetchMinOsFeatureData, FEATURE_MIN_VERSIONS_URL, fetchReleases, fetchMinOsFeatureData,
fetchLatestGHBetaRelease fetchLatestGHBetaRelease
} from "../devices/actions"; } from "../devices/actions";
import { AuthState } from "./interfaces"; import { AuthState } from "./interfaces";
@ -16,6 +16,7 @@ import { Actions } from "../constants";
import { connectDevice } from "../connectivity/connect_device"; import { connectDevice } from "../connectivity/connect_device";
import { getFirstPartyFarmwareList } from "../farmware/actions"; import { getFirstPartyFarmwareList } from "../farmware/actions";
import { readOnlyInterceptor } from "../read_only_mode"; import { readOnlyInterceptor } from "../read_only_mode";
import { ExternalUrl } from "../external_urls";
export function didLogin(authState: AuthState, dispatch: Function) { export function didLogin(authState: AuthState, dispatch: Function) {
API.setBaseUrl(authState.token.unencoded.iss); API.setBaseUrl(authState.token.unencoded.iss);
@ -24,7 +25,7 @@ export function didLogin(authState: AuthState, dispatch: Function) {
beta_os_update_server && beta_os_update_server != "NOT_SET" && beta_os_update_server && beta_os_update_server != "NOT_SET" &&
dispatch(fetchLatestGHBetaRelease(beta_os_update_server)); dispatch(fetchLatestGHBetaRelease(beta_os_update_server));
dispatch(getFirstPartyFarmwareList()); dispatch(getFirstPartyFarmwareList());
dispatch(fetchMinOsFeatureData(FEATURE_MIN_VERSIONS_URL)); dispatch(fetchMinOsFeatureData(ExternalUrl.featureMinVersions));
dispatch(setToken(authState)); dispatch(setToken(authState));
Sync.fetchSyncData(dispatch); Sync.fetchSyncData(dispatch);
dispatch(connectDevice(authState)); dispatch(connectDevice(authState));

View File

@ -952,8 +952,7 @@ export namespace DiagnosticMessages {
but we have no recent record of FarmBot connecting to the internet. but we have no recent record of FarmBot connecting to the internet.
This usually happens because of poor WiFi connectivity in the garden, This usually happens because of poor WiFi connectivity in the garden,
a bad password during configuration, a very long power outage, or a bad password during configuration, a very long power outage, or
blocked ports on FarmBot's local network. Please refer IT staff to blocked ports on FarmBot's local network. Please refer IT staff to:`);
https://software.farm.bot/docs/for-it-security-professionals`);
export const NO_WS_AVAILABLE = trim(`You are either offline, using a web export const NO_WS_AVAILABLE = trim(`You are either offline, using a web
browser that does not support WebSockets, or are behind a firewall that browser that does not support WebSockets, or are behind a firewall that

View File

@ -2,6 +2,7 @@ import * as React from "react";
import { get } from "lodash"; import { get } from "lodash";
import { Page } from "./ui/index"; import { Page } from "./ui/index";
import { Session } from "./session"; import { Session } from "./session";
import { ExternalUrl } from "./external_urls";
/** Use currying to pass down `error` object for now. */ /** Use currying to pass down `error` object for now. */
export function crashPage(error: object) { export function crashPage(error: object) {
@ -24,7 +25,7 @@ export function crashPage(error: object) {
<li>Perform a "hard refresh" (<strong>CTRL + SHIFT + R</strong> on most machines).</li> <li>Perform a "hard refresh" (<strong>CTRL + SHIFT + R</strong> on most machines).</li>
<li><span><a onClick={() => Session.clear()}>Log out by clicking here.</a></span></li> <li><span><a onClick={() => Session.clear()}>Log out by clicking here.</a></span></li>
<li>Send the error information (below) to our developer team via the <li>Send the error information (below) to our developer team via the
<a href="http://forum.farmbot.org/c/software">FarmBot software <a href={ExternalUrl.softwareForum}>FarmBot software
forum</a>. Including additional information (such as steps leading up forum</a>. Including additional information (such as steps leading up
to the error) help us identify solutions more quickly. </li> to the error) help us identify solutions more quickly. </li>
</ol> </ol>

View File

@ -2,16 +2,13 @@ import { connect, MqttClient } from "mqtt";
import React from "react"; import React from "react";
import { uuid } from "farmbot"; import { uuid } from "farmbot";
import axios from "axios"; import axios from "axios";
import { ExternalUrl } from "../external_urls";
interface State { interface State {
error: Error | undefined; error: Error | undefined;
stage: string; stage: string;
} }
const VIDEO_URL =
"https://cdn.shopify.com/s/files/1/2040/0289/files/Farm_Designer_Loop.mp4?9552037556691879018";
const PHONE_URL =
"https://cdn.shopify.com/s/files/1/2040/0289/files/Controls.png?9668345515035078097";
const WS_CONFIG = { const WS_CONFIG = {
username: "farmbot_demo", username: "farmbot_demo",
password: "required, but not used.", password: "required, but not used.",
@ -63,9 +60,9 @@ export class DemoIframe extends React.Component<{}, State> {
return <div className="demo-container"> return <div className="demo-container">
<video muted={true} autoPlay={true} loop={true} className="demo-video"> <video muted={true} autoPlay={true} loop={true} className="demo-video">
<source src={VIDEO_URL} type="video/mp4" /> <source src={ExternalUrl.Videos.desktop} type="video/mp4" />
</video> </video>
<img className="demo-phone" src={PHONE_URL} /> <img className="demo-phone" src={ExternalUrl.Videos.mobile} />
<button className="demo-button" onClick={this.requestAccount}> <button className="demo-button" onClick={this.requestAccount}>
{this.state.stage} {this.state.stage}
</button> </button>

View File

@ -26,9 +26,6 @@ import { t } from "../i18next_wrapper";
const ON = 1, OFF = 0; const ON = 1, OFF = 0;
export type ConfigKey = keyof McuParams; export type ConfigKey = keyof McuParams;
export const FEATURE_MIN_VERSIONS_URL =
"https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/" +
"FEATURE_MIN_VERSIONS.json";
// Already filtering messages in FarmBot OS and the API- this is just for // Already filtering messages in FarmBot OS and the API- this is just for
// an additional layer of safety. // an additional layer of safety.
const BAD_WORDS = ["WPA", "PSK", "PASSWORD", "NERVES"]; const BAD_WORDS = ["WPA", "PSK", "PASSWORD", "NERVES"];

View File

@ -15,6 +15,7 @@ import { AutoUpdateRow } from "./fbos_settings/auto_update_row";
import { AutoSyncRow } from "./fbos_settings/auto_sync_row"; import { AutoSyncRow } from "./fbos_settings/auto_sync_row";
import { PowerAndReset } from "./fbos_settings/power_and_reset"; import { PowerAndReset } from "./fbos_settings/power_and_reset";
import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector"; import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector";
import { ExternalUrl } from "../../external_urls";
export enum ColWidth { export enum ColWidth {
label = 3, label = 3,
@ -22,15 +23,12 @@ export enum ColWidth {
button = 2 button = 2
} }
const OS_RELEASE_NOTES_URL =
"https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/RELEASE_NOTES.md";
export class FarmbotOsSettings export class FarmbotOsSettings
extends React.Component<FarmbotOsProps, FarmbotOsState> { extends React.Component<FarmbotOsProps, FarmbotOsState> {
state: FarmbotOsState = { allOsReleaseNotes: "" }; state: FarmbotOsState = { allOsReleaseNotes: "" };
componentDidMount() { componentDidMount() {
this.fetchReleaseNotes(OS_RELEASE_NOTES_URL); this.fetchReleaseNotes(ExternalUrl.osReleaseNotes);
} }
get osMajorVersion() { get osMajorVersion() {

View File

@ -14,6 +14,7 @@ import { timeFormatString } from "../../../util";
import { TimeSettings } from "../../../interfaces"; import { TimeSettings } from "../../../interfaces";
import { StringConfigKey } from "farmbot/dist/resources/configs/fbos"; import { StringConfigKey } from "farmbot/dist/resources/configs/fbos";
import { boardType, FIRMWARE_CHOICES_DDI } from "../firmware_hardware_support"; import { boardType, FIRMWARE_CHOICES_DDI } from "../firmware_hardware_support";
import { ExternalUrl, FarmBotRepo } from "../../../external_urls";
/** Return an indicator color for the given temperature (C). */ /** Return an indicator color for the given temperature (C). */
export const colorFromTemp = (temp: number | undefined): string => { export const colorFromTemp = (temp: number | undefined): string => {
@ -170,7 +171,7 @@ const shortenCommit = (longCommit: string) => (longCommit || "").slice(0, 8);
interface CommitDisplayProps { interface CommitDisplayProps {
title: string; title: string;
repo: string; repo: FarmBotRepo;
commit: string; commit: string;
} }
@ -184,7 +185,7 @@ const CommitDisplay = (
{shortCommit === "---" {shortCommit === "---"
? shortCommit ? shortCommit
: <a : <a
href={`https://github.com/FarmBot/${repo}/tree/${shortCommit}`} href={`${ExternalUrl.gitHubFarmBot}/${repo}/tree/${shortCommit}`}
target="_blank"> target="_blank">
{shortCommit} {shortCommit}
</a>} </a>}
@ -270,14 +271,15 @@ export function FbosDetails(props: FbosDetailsProps) {
timeSettings={props.timeSettings} timeSettings={props.timeSettings}
device={props.deviceAccount} /> device={props.deviceAccount} />
<p><b>{t("Environment")}: </b>{env}</p> <p><b>{t("Environment")}: </b>{env}</p>
<CommitDisplay title={t("Commit")} repo={"farmbot_os"} commit={commit} /> <CommitDisplay title={t("Commit")}
repo={FarmBotRepo.FarmBotOS} commit={commit} />
<p><b>{t("Target")}: </b>{target}</p> <p><b>{t("Target")}: </b>{target}</p>
<p><b>{t("Node name")}: </b>{last((node_name || "").split("@"))}</p> <p><b>{t("Node name")}: </b>{last((node_name || "").split("@"))}</p>
<p><b>{t("Device ID")}: </b>{props.deviceAccount.body.id}</p> <p><b>{t("Device ID")}: </b>{props.deviceAccount.body.id}</p>
{isString(private_ip) && <p><b>{t("Local IP address")}: </b>{private_ip}</p>} {isString(private_ip) && <p><b>{t("Local IP address")}: </b>{private_ip}</p>}
<p><b>{t("Firmware")}: </b>{reformatFwVersion(firmware_version)}</p> <p><b>{t("Firmware")}: </b>{reformatFwVersion(firmware_version)}</p>
<CommitDisplay title={t("Firmware commit")} <CommitDisplay title={t("Firmware commit")}
repo={"farmbot-arduino-firmware"} commit={firmwareCommit} /> repo={FarmBotRepo.FarmBotArduinoFirmware} commit={firmwareCommit} />
<p><b>{t("Firmware code")}: </b>{firmware_version}</p> <p><b>{t("Firmware code")}: </b>{firmware_version}</p>
{isNumber(uptime) && <UptimeDisplay uptime_sec={uptime} />} {isNumber(uptime) && <UptimeDisplay uptime_sec={uptime} />}
{isNumber(memory_usage) && {isNumber(memory_usage) &&

View File

@ -1,5 +1,11 @@
import { Dictionary } from "farmbot"; import { Dictionary } from "farmbot";
import { DiagnosticMessages } from "../../constants"; import { DiagnosticMessages } from "../../constants";
import { docLink } from "../../ui/doc_link";
import { trim } from "../../util/util";
const DiagnosticMessagesWiFiOrConfig =
trim(`${DiagnosticMessages.WIFI_OR_CONFIG}
${docLink("for-it-security-professionals")}`);
// I don't like this at all. // I don't like this at all.
// If anyone has a cleaner solution, I'd love to hear it. // If anyone has a cleaner solution, I'd love to hear it.
@ -16,13 +22,13 @@ export const TRUTH_TABLE: Readonly<Dictionary<string | undefined>> = {
// 17: No MQTT connections. // 17: No MQTT connections.
[0b10001]: DiagnosticMessages.NO_WS_AVAILABLE, [0b10001]: DiagnosticMessages.NO_WS_AVAILABLE,
// 24: Browser is connected to API and MQTT. // 24: Browser is connected to API and MQTT.
[0b11000]: DiagnosticMessages.WIFI_OR_CONFIG, [0b11000]: DiagnosticMessagesWiFiOrConfig,
// 9: At least the browser is connected to MQTT. // 9: At least the browser is connected to MQTT.
[0b01001]: DiagnosticMessages.WIFI_OR_CONFIG, [0b01001]: DiagnosticMessagesWiFiOrConfig,
// 8: At least the browser is connected to MQTT. // 8: At least the browser is connected to MQTT.
[0b01000]: DiagnosticMessages.WIFI_OR_CONFIG, [0b01000]: DiagnosticMessagesWiFiOrConfig,
// 25: Farmbot offline. // 25: Farmbot offline.
[0b11001]: DiagnosticMessages.WIFI_OR_CONFIG, [0b11001]: DiagnosticMessagesWiFiOrConfig,
// 2: Browser offline. Farmbot last seen by the API recently. // 2: Browser offline. Farmbot last seen by the API recently.
[0b00010]: DiagnosticMessages.NO_WS_AVAILABLE, [0b00010]: DiagnosticMessages.NO_WS_AVAILABLE,
// 18: Farmbot last seen by the API recently. // 18: Farmbot last seen by the API recently.

View File

@ -93,7 +93,7 @@ export enum Feature {
variables = "variables", variables = "variables",
} }
/** Object fetched from FEATURE_MIN_VERSIONS_URL. */ /** Object fetched from ExternalUrl.featureMinVersions. */
export type MinOsFeatureLookup = Partial<Record<Feature, string>>; export type MinOsFeatureLookup = Partial<Record<Feature, string>>;
export interface BotState { export interface BotState {

View File

@ -0,0 +1,51 @@
enum Org {
FarmBot = "FarmBot",
FarmBotLabs = "FarmBot-Labs",
}
export enum FarmBotRepo {
FarmBotWebApp = "Farmbot-Web-App",
FarmBotOS = "farmbot_os",
FarmBotArduinoFirmware = "farmbot-arduino-firmware",
}
enum FbosFile {
featureMinVersions = "FEATURE_MIN_VERSIONS.json",
osReleaseNotes = "RELEASE_NOTES.md",
}
export namespace ExternalUrl {
const GITHUB = "https://github.com";
const GITHUB_RAW = "https://raw.githubusercontent.com";
const GITHUB_API = "https://api.github.com";
const OPENFARM = "https://openfarm.cc";
const SOFTWARE_DOCS = "https://software.farm.bot";
const FORUM = "http://forum.farmbot.org";
const SHOPIFY_CDN = "https://cdn.shopify.com/s/files/1/2040/0289/files";
const FBOS_RAW = `${GITHUB_RAW}/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}`;
export const featureMinVersions = `${FBOS_RAW}/${FbosFile.featureMinVersions}`;
export const osReleaseNotes = `${FBOS_RAW}/${FbosFile.osReleaseNotes}`;
export const latestRelease =
`${GITHUB_API}/repos/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}/releases/latest`;
export const gitHubFarmBot = `${GITHUB}/${Org.FarmBot}`;
export const webAppRepo =
`${GITHUB}/${Org.FarmBot}/${FarmBotRepo.FarmBotWebApp}`;
export const softwareDocs = `${SOFTWARE_DOCS}/docs`;
export const softwareForum = `${FORUM}/c/software`;
export namespace OpenFarm {
export const cropApi = `${OPENFARM}/api/v1/crops/`;
export const cropBrowse = `${OPENFARM}/crops/`;
export const newCrop = `${OPENFARM}/en/crops/new`;
}
export namespace Videos {
export const desktop =
`${SHOPIFY_CDN}/Farm_Designer_Loop.mp4?9552037556691879018`;
export const mobile = `${SHOPIFY_CDN}/Controls.png?9668345515035078097`;
}
}

View File

@ -67,9 +67,6 @@ export namespace OpenFarm {
type: string; type: string;
attributes: ImageAttrs; attributes: ImageAttrs;
} }
export const cropUrl = "https://openfarm.cc/api/v1/crops";
export const browsingCropUrl = "https://openfarm.cc/crops/";
} }
/** Returned by https://openfarm.cc/api/v1/crops?filter=q */ /** Returned by https://openfarm.cc/api/v1/crops?filter=q */
export interface CropSearchResult { export interface CropSearchResult {

View File

@ -24,6 +24,7 @@ import {
import { startCase, isArray, chain, isNumber } from "lodash"; import { startCase, isArray, chain, isNumber } from "lodash";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";
import { Panel } from "../panel_header"; import { Panel } from "../panel_header";
import { ExternalUrl } from "../../external_urls";
interface InfoFieldProps { interface InfoFieldProps {
title: string; title: string;
@ -170,7 +171,7 @@ const CropDragInfoTile =
const EditOnOpenFarm = ({ slug }: { slug: string }) => const EditOnOpenFarm = ({ slug }: { slug: string }) =>
<div className="edit-on-openfarm"> <div className="edit-on-openfarm">
<span>{t("Edit on")}&nbsp;</span> <span>{t("Edit on")}&nbsp;</span>
<a href={OpenFarm.browsingCropUrl + slug} target="_blank" <a href={ExternalUrl.OpenFarm.cropBrowse + slug} target="_blank"
title={t("Open OpenFarm.cc in a new tab")}> title={t("Open OpenFarm.cc in a new tab")}>
{"OpenFarm"} {"OpenFarm"}
</a> </a>

View File

@ -5,6 +5,7 @@ import {
} from "../../ui/empty_state_wrapper"; } from "../../ui/empty_state_wrapper";
import { Content } from "../../constants"; import { Content } from "../../constants";
import { t } from "../../i18next_wrapper"; import { t } from "../../i18next_wrapper";
import { ExternalUrl } from "../../external_urls";
/** A stripped down version of OFSearchResult */ /** A stripped down version of OFSearchResult */
interface Result { interface Result {
@ -24,7 +25,7 @@ export class OpenFarmResults extends React.Component<SearchResultProps, {}> {
get text(): JSX.Element { get text(): JSX.Element {
return <p>{`${t(Content.CROP_NOT_FOUND_INTRO)} `} return <p>{`${t(Content.CROP_NOT_FOUND_INTRO)} `}
<a href="https://openfarm.cc/en/crops/new" target="_blank"> <a href={ExternalUrl.OpenFarm.newCrop} target="_blank">
{t(Content.CROP_NOT_FOUND_LINK)} {t(Content.CROP_NOT_FOUND_LINK)}
</a> </a>
</p>; </p>;

View File

@ -4,8 +4,10 @@ import { DEFAULT_ICON } from "../open_farm/icons";
import { Actions } from "../constants"; import { Actions } from "../constants";
import { ExecutableType } from "farmbot/dist/resources/api_resources"; import { ExecutableType } from "farmbot/dist/resources/api_resources";
import { get } from "lodash"; import { get } from "lodash";
import { ExternalUrl } from "../external_urls";
const url = (q: string) => `${OpenFarm.cropUrl}?include=pictures&filter=${q}`; const url = (q: string) =>
`${ExternalUrl.OpenFarm.cropApi}?include=pictures&filter=${q}`;
const openFarmSearchQuery = (q: string): AxiosPromise<CropSearchResult> => const openFarmSearchQuery = (q: string): AxiosPromise<CropSearchResult> =>
axios.get<CropSearchResult>(url(q)); axios.get<CropSearchResult>(url(q));

View File

@ -2,8 +2,8 @@ jest.mock("axios", () => ({
get: jest.fn(() => { get: jest.fn(() => {
return Promise.resolve({ return Promise.resolve({
data: [ data: [
{ manifest: "url", name: "farmware0" }, { package: "farmware0" },
{ manifest: "url", name: "farmware1" } { package: "farmware1" }
] ]
}); });
}), }),

View File

@ -1,17 +1,14 @@
import axios from "axios"; import axios from "axios";
import { FarmwareManifestEntry } from "./interfaces";
import { Actions } from "../constants"; import { Actions } from "../constants";
import { urlFor } from "../api/crud"; import { urlFor } from "../api/crud";
import { API } from "../api";
const farmwareManifestUrl = import { FarmwareManifest } from "farmbot";
"https://raw.githubusercontent.com/FarmBot-Labs/farmware_manifests" +
"/master/manifest.json";
export const getFirstPartyFarmwareList = () => { export const getFirstPartyFarmwareList = () => {
return (dispatch: Function) => { return (dispatch: Function) => {
axios.get<FarmwareManifestEntry[]>(farmwareManifestUrl) axios.get<FarmwareManifest[]>(API.current.firstPartyFarmwarePath)
.then(r => { .then(r => {
const names = r.data.map((fw: FarmwareManifestEntry) => fw.name); const names = r.data.map(fw => fw.package);
dispatch({ dispatch({
type: Actions.FETCH_FIRST_PARTY_FARMWARE_NAMES_OK, type: Actions.FETCH_FIRST_PARTY_FARMWARE_NAMES_OK,
payload: names payload: names

View File

@ -24,8 +24,6 @@ export interface FarmwareState {
infoOpen: boolean; infoOpen: boolean;
} }
export type FarmwareManifestEntry = Record<"name" | "manifest", string>;
export interface FarmwareConfigMenuProps { export interface FarmwareConfigMenuProps {
show: boolean | undefined; show: boolean | undefined;
dispatch: Function; dispatch: Function;

View File

@ -1,6 +1,5 @@
import * as React from "react"; import * as React from "react";
const VIDEO_URL = "https://cdn.shopify.com/s/files/1/2040/0289/files/" + import { ExternalUrl } from "../external_urls";
"Farm_Designer_Loop.mp4?9552037556691879018";
export const LaptopSplash = ({ className }: { className: string }) => export const LaptopSplash = ({ className }: { className: string }) =>
<div className={className}> <div className={className}>
@ -8,7 +7,7 @@ export const LaptopSplash = ({ className }: { className: string }) =>
<div className="laptop"> <div className="laptop">
<div className="laptop-screen"> <div className="laptop-screen">
<video muted autoPlay loop> <video muted autoPlay loop>
<source src={VIDEO_URL} type="video/mp4" /> <source src={ExternalUrl.Videos.desktop} type="video/mp4" />
</video> </video>
<span className="laptop-shine" /> <span className="laptop-shine" />
</div> </div>

View File

@ -3,6 +3,7 @@ import { AccountMenuProps } from "./interfaces";
import { Link } from "../link"; import { Link } from "../link";
import { shortRevision } from "../util"; import { shortRevision } from "../util";
import { t } from "../i18next_wrapper"; import { t } from "../i18next_wrapper";
import { ExternalUrl } from "../external_urls";
export const AdditionalMenu = (props: AccountMenuProps) => { export const AdditionalMenu = (props: AccountMenuProps) => {
return <div className="nav-additional-menu"> return <div className="nav-additional-menu">
@ -30,7 +31,7 @@ export const AdditionalMenu = (props: AccountMenuProps) => {
</div> </div>
<div className="app-version"> <div className="app-version">
<label>{t("VERSION")}</label>:&nbsp; <label>{t("VERSION")}</label>:&nbsp;
<a href="https://github.com/FarmBot/Farmbot-Web-App" target="_blank"> <a href={ExternalUrl.webAppRepo} target="_blank">
{shortRevision().slice(0, 8)} {shortRevision().slice(0, 8)}
</a> </a>
</div> </div>

View File

@ -1,10 +1,4 @@
import { OpenFarmAPI, svgToUrl } from "../icons"; import { svgToUrl } from "../icons";
describe("OpenFarmAPI", () => {
it("has a base URL", () => {
expect(OpenFarmAPI.OFBaseURL).toContain("openfarm.cc");
});
});
describe("svgToUrl()", () => { describe("svgToUrl()", () => {
it("returns svg url", () => { it("returns svg url", () => {

View File

@ -1,7 +1,8 @@
import axios, { AxiosResponse } from "axios"; import axios, { AxiosResponse } from "axios";
import { Dictionary } from "farmbot"; import { Dictionary } from "farmbot";
import { isObject } from "lodash"; import { isObject } from "lodash";
import { OFCropAttrs, OFCropResponse, OpenFarmAPI, svgToUrl } from "./icons"; import { OFCropAttrs, OFCropResponse, svgToUrl } from "./icons";
import { ExternalUrl } from "../external_urls";
export type OFIcon = Readonly<OFCropAttrs>; export type OFIcon = Readonly<OFCropAttrs>;
type IconDictionary = Dictionary<OFIcon | undefined>; type IconDictionary = Dictionary<OFIcon | undefined>;
@ -57,7 +58,7 @@ const cacheTheIcon = (slug: string) =>
}; };
function HTTPIconFetch(slug: string) { function HTTPIconFetch(slug: string) {
const url = OpenFarmAPI.OFBaseURL + slug; const url = ExternalUrl.OpenFarm.cropApi + slug;
// Avoid duplicate requests. // Avoid duplicate requests.
if (promiseCache[url]) { return promiseCache[url]; } if (promiseCache[url]) { return promiseCache[url]; }
promiseCache[url] = axios promiseCache[url] = axios

View File

@ -1,4 +1,3 @@
const BASE = "https://openfarm.cc/api/v1/crops/";
export const DATA_URI = "data:image/svg+xml;utf8,"; export const DATA_URI = "data:image/svg+xml;utf8,";
export const DEFAULT_ICON = "/app-resources/img/generic-plant.svg"; export const DEFAULT_ICON = "/app-resources/img/generic-plant.svg";
@ -20,10 +19,6 @@ export interface OFCropResponse {
}; };
} }
export namespace OpenFarmAPI {
export const OFBaseURL = BASE;
}
export function svgToUrl(xml: string | undefined): string { export function svgToUrl(xml: string | undefined): string {
return xml ? return xml ?
(DATA_URI + encodeURIComponent(xml)) : DEFAULT_ICON; (DATA_URI + encodeURIComponent(xml)) : DEFAULT_ICON;

View File

@ -3,9 +3,7 @@ import axios from "axios";
import { t } from "../i18next_wrapper"; import { t } from "../i18next_wrapper";
import { GithubRelease } from "../devices/interfaces"; import { GithubRelease } from "../devices/interfaces";
import { Content } from "../constants"; import { Content } from "../constants";
import { ExternalUrl } from "../external_urls";
const LATEST_RELEASE_URL =
"https://api.github.com/repos/farmbot/farmbot_os/releases/latest";
interface OsDownloadState { interface OsDownloadState {
tagName: string; tagName: string;
@ -49,7 +47,7 @@ export class OsDownload extends React.Component<{}, OsDownloadState> {
} }
fetchLatestRelease = () => fetchLatestRelease = () =>
axios.get<GithubRelease>(LATEST_RELEASE_URL) axios.get<GithubRelease>(ExternalUrl.latestRelease)
.then(resp => .then(resp =>
this.setState({ this.setState({
tagName: resp.data.tag_name, tagName: resp.data.tag_name,

View File

@ -8,6 +8,7 @@ import { API } from "../api";
import { Row, Col, Widget, WidgetHeader, WidgetBody } from "../ui"; import { Row, Col, Widget, WidgetHeader, WidgetBody } from "../ui";
import { TermsCheckbox } from "../front_page/terms_checkbox"; import { TermsCheckbox } from "../front_page/terms_checkbox";
import { t } from "../i18next_wrapper"; import { t } from "../i18next_wrapper";
import { ExternalUrl } from "../external_urls";
interface Props { } interface Props { }
interface State { interface State {
@ -86,7 +87,7 @@ export class TosUpdate extends React.Component<Props, Partial<State>> {
</p> </p>
<p> <p>
{t("Please send us an email at contact@farm.bot or see the ")} {t("Please send us an email at contact@farm.bot or see the ")}
<a href="http://forum.farmbot.org/"> <a href={ExternalUrl.softwareForum}>
{t("FarmBot forum.")} {t("FarmBot forum.")}
</a> </a>
</p> </p>

View File

@ -1,8 +1,9 @@
import { docLink, BASE_URL } from "../doc_link"; import { docLink } from "../doc_link";
import { ExternalUrl } from "../../external_urls";
describe("docLink", () => { describe("docLink", () => {
it("creates doc links", () => { it("creates doc links", () => {
expect(docLink()).toEqual(BASE_URL); expect(docLink()).toEqual(ExternalUrl.softwareDocs + "/");
expect(docLink("farmware")).toEqual(BASE_URL + "farmware"); expect(docLink("farmware")).toEqual(ExternalUrl.softwareDocs + "/farmware");
}); });
}); });

View File

@ -1,4 +1,4 @@
export const BASE_URL = "https://software.farm.bot/docs/"; import { ExternalUrl } from "../external_urls";
/** A centralized list of all documentation slugs in the app makes it easier to /** A centralized list of all documentation slugs in the app makes it easier to
* rename / move links in the future. */ * rename / move links in the future. */
@ -7,11 +7,13 @@ export const DOC_SLUGS = {
"camera-calibration": "Camera Calibration", "camera-calibration": "Camera Calibration",
"the-farmbot-web-app": "Web App", "the-farmbot-web-app": "Web App",
"farmware": "Farmware", "farmware": "Farmware",
"connecting-farmbot-to-the-internet": "Connecting FarmBot to the Internet" "connecting-farmbot-to-the-internet": "Connecting FarmBot to the Internet",
"for-it-security-professionals": "For IT Security Professionals",
}; };
export type DocSlug = keyof typeof DOC_SLUGS; export type DocSlug = keyof typeof DOC_SLUGS;
/** WHY?: The function keeps things DRY. It also makes life easier when the /** WHY?: The function keeps things DRY. It also makes life easier when the
* documentation URL / slug name changes. */ * documentation URL / slug name changes. */
export const docLink = (slug?: DocSlug) => BASE_URL + (slug || ""); export const docLink = (slug?: DocSlug) =>
`${ExternalUrl.softwareDocs}/${slug || ""}`;