no log: Support overriding backend server when developing UI

pull/1523/head
LASER-Yi 3 years ago
parent 3b4e81db5e
commit da21e5dad0

@ -1,7 +0,0 @@
# Please override by creating a .env.local file at the same directory
# Required
REACT_APP_APIKEY="YOUR_SERVER_API_KEY"
# Optional
REACT_APP_CAN_UPDATE=true
REACT_APP_HAS_UPDATE=false

@ -0,0 +1,24 @@
# Override by duplicating and rename to .env.local
# The following environment variables will only be used during development
# Required
# API key of your backend
REACT_APP_APIKEY="REPLACE_ME_TO_YOUR_SERVER_API_KEY"
# Address of your backend
REACT_APP_PROXY_URL=http://localhost:6767
# Optional
# Allow Unsecured connection to your backend
REACT_APP_PROXY_SECURE=true
# Allow websocket connection in Socket.IO
REACT_APP_ALLOW_WEBSOCKET=true
# Display update section in settings
REACT_APP_CAN_UPDATE=true
# Display update notification in notification center
REACT_APP_HAS_UPDATE=false

@ -1,10 +1,12 @@
# Bazarr Frontend
## Dependencies
* [Node.js](https://nodejs.org/)
* npm (included in Node.js)
- [Node.js](https://nodejs.org/)
- npm (included in Node.js)
## Getting Started
1. Clone or download this repository
```
@ -17,18 +19,36 @@
```
$ npm install
```
4. Duplicate `.env` file and rename to `.env.local`
3. Duplicate `.env.development` file and rename to `.env.local`
```
$ cp .env .env.local
```
6. Fill any variable that defined in `.env.local`
7. Run Bazarr backend (Backend must listening on `http://localhost:6767`)
4. Update your backend server's API key in `.env.local`
```
# API key of your backend
REACT_APP_APIKEY="REPLACE_ME_TO_YOUR_SERVER_API_KEY"
```
5. Change the address of your backend server (Optional)
> http://localhost:6767 will be used by default
```
# Address of your backend
REACT_APP_PROXY_URL=http://localhost:6767
```
6. Run Bazarr backend
```
$ python3 ../bazarr.py
```
9. Run the web client for local development
7. Run the web development tool
```
$ npm start

@ -28,7 +28,7 @@
"@types/react-table": "^7",
"axios": "^0.21",
"bootstrap": "^4",
"http-proxy-middleware": "^0.19",
"http-proxy-middleware": "^2",
"lodash": "^4",
"rc-slider": "^9.7",
"react": "^17",
@ -3505,6 +3505,14 @@
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz",
"integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w=="
},
"node_modules/@types/http-proxy": {
"version": "1.17.7",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz",
"integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/invariant": {
"version": "2.2.34",
"resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.34.tgz",
@ -10490,17 +10498,82 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/http-proxy-middleware": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.2.tgz",
"integrity": "sha512-aYk1rTKqLTus23X3L96LGNCGNgWpG4cG0XoZIT1GUPhhulEHX/QalnO6Vbo+WmKWi4AL2IidjuC0wZtbpg0yhQ==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz",
"integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==",
"dependencies": {
"@types/http-proxy": "^1.17.5",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.0",
"lodash": "^4.17.11",
"micromatch": "^3.1.10"
"is-glob": "^4.0.1",
"is-plain-obj": "^3.0.0",
"micromatch": "^4.0.2"
},
"engines": {
"node": ">=4.0.0"
"node": ">=12.0.0"
}
},
"node_modules/http-proxy-middleware/node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/http-proxy-middleware/node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/http-proxy-middleware/node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/http-proxy-middleware/node_modules/is-plain-obj": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
"integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/http-proxy-middleware/node_modules/micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dependencies": {
"braces": "^3.0.1",
"picomatch": "^2.2.3"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/http-proxy-middleware/node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/https-browserify": {
@ -24838,6 +24911,14 @@
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz",
"integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w=="
},
"@types/http-proxy": {
"version": "1.17.7",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz",
"integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==",
"requires": {
"@types/node": "*"
}
},
"@types/invariant": {
"version": "2.2.34",
"resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.34.tgz",
@ -30290,14 +30371,60 @@
}
},
"http-proxy-middleware": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.2.tgz",
"integrity": "sha512-aYk1rTKqLTus23X3L96LGNCGNgWpG4cG0XoZIT1GUPhhulEHX/QalnO6Vbo+WmKWi4AL2IidjuC0wZtbpg0yhQ==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz",
"integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==",
"requires": {
"@types/http-proxy": "^1.17.5",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.0",
"lodash": "^4.17.11",
"micromatch": "^3.1.10"
"is-glob": "^4.0.1",
"is-plain-obj": "^3.0.0",
"micromatch": "^4.0.2"
},
"dependencies": {
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"requires": {
"fill-range": "^7.0.1"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"requires": {
"to-regex-range": "^5.0.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-plain-obj": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
"integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA=="
},
"micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"requires": {
"braces": "^3.0.1",
"picomatch": "^2.2.3"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"requires": {
"is-number": "^7.0.0"
}
}
}
},
"https-browserify": {

@ -32,7 +32,7 @@
"@types/react-table": "^7",
"axios": "^0.21",
"bootstrap": "^4",
"http-proxy-middleware": "^0.19",
"http-proxy-middleware": "^2",
"lodash": "^4",
"rc-slider": "^9.7",
"react": "^17",

@ -1,6 +1,6 @@
import { debounce, forIn, remove, uniq } from "lodash";
import { io, Socket } from "socket.io-client";
import { getBaseUrl } from "../../utilities";
import { Environment } from "../../utilities";
import { conditionalLog, log } from "../../utilities/logger";
import { createDefaultReducer } from "./reducer";
@ -12,9 +12,8 @@ class SocketIOClient {
private reducers: SocketIO.Reducer[];
constructor() {
const baseUrl = getBaseUrl();
this.socket = io({
path: `${baseUrl}/api/socket.io`,
path: `${Environment.baseUrl}/api/socket.io`,
transports: ["polling", "websocket"],
upgrade: true,
rememberUpgrade: true,

@ -1,6 +1,7 @@
import { createReducer } from "@reduxjs/toolkit";
import { intersectionWith, pullAllWith, remove, sortBy, uniqBy } from "lodash";
import apis from "../../apis";
import { isProdEnv } from "../../utilities";
import {
siteAddNotifications,
siteAddProgress,
@ -59,7 +60,7 @@ const reducer = createReducer(defaultSite, (builder) => {
state.initialized = "An Error Occurred When Initializing Bazarr UI";
})
.addCase(siteRedirectToAuth, (state) => {
if (process.env.NODE_ENV !== "production") {
if (!isProdEnv) {
apis._resetApi("NEED_AUTH");
}
state.auth = false;

@ -20,7 +20,7 @@ import Sidebar from "../Sidebar";
import Auth from "../special-pages/AuthPage";
import ErrorBoundary from "../special-pages/ErrorBoundary";
import LaunchError from "../special-pages/LaunchError";
import { useBaseUrl, useHasUpdateInject } from "../utilities";
import { Environment } from "../utilities";
import Header from "./Header";
import Router from "./Router";
@ -35,9 +35,8 @@ const App: FunctionComponent<Props> = () => {
const notify = useNotification("has-update", 10 * 1000);
// Has any update?
const hasUpdate = useHasUpdateInject();
useEffectOnceWhen(() => {
if (hasUpdate) {
if (Environment.hasUpdate) {
notify({
type: "info",
message: "A new version of Bazarr is ready, restart is required",
@ -80,14 +79,12 @@ const App: FunctionComponent<Props> = () => {
};
const MainRouter: FunctionComponent = () => {
const baseUrl = useBaseUrl();
useEffect(() => {
Socketio.initialize();
}, []);
return (
<BrowserRouter basename={baseUrl}>
<BrowserRouter basename={Environment.baseUrl}>
<Switch>
<Route exact path="/login">
<Auth></Auth>

@ -6,11 +6,7 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { FunctionComponent, useState } from "react";
import { InputGroup } from "react-bootstrap";
import {
copyToClipboard,
toggleState,
useCanUpdateInject,
} from "../../utilities";
import { copyToClipboard, Environment, toggleState } from "../../utilities";
import {
Button,
Check,
@ -41,8 +37,6 @@ const baseUrlOverride = (settings: Settings) =>
const SettingsGeneralView: FunctionComponent = () => {
const [copied, setCopy] = useState(false);
const canUpdate = useCanUpdateInject();
return (
<SettingsProvider title="General - Bazarr (Settings)">
<Group header="Host">
@ -155,7 +149,7 @@ const SettingsGeneralView: FunctionComponent = () => {
</CollapseBox.Content>
</CollapseBox>
</Group>
<Group header="Updates" hidden={!canUpdate}>
<Group header="Updates" hidden={!Environment.canUpdate}>
<Input>
<Check
label="Automatic"

@ -7,7 +7,7 @@ import { useSystemLogs } from "../../@redux/hooks";
import { useReduxAction } from "../../@redux/hooks/base";
import { SystemApi } from "../../apis";
import { AsyncOverlay, ContentHeader } from "../../components";
import { useBaseUrl } from "../../utilities";
import { Environment } from "../../utilities";
import Table from "./table";
interface Props {}
@ -18,11 +18,9 @@ const SystemLogsView: FunctionComponent<Props> = () => {
const [resetting, setReset] = useState(false);
const baseUrl = useBaseUrl(true);
const download = useCallback(() => {
window.open(`${baseUrl}bazarr.log`);
}, [baseUrl]);
window.open(`${Environment.baseUrl}/bazarr.log`);
}, []);
return (
<AsyncOverlay ctx={logs}>

@ -1,19 +1,15 @@
import Axios, { AxiosError, AxiosInstance, CancelTokenSource } from "axios";
import { siteRedirectToAuth } from "../@redux/actions";
import { AppDispatch } from "../@redux/store";
import { getBaseUrl } from "../utilities";
import { Environment, isProdEnv } from "../utilities";
class Api {
axios!: AxiosInstance;
source!: CancelTokenSource;
dispatch!: AppDispatch;
constructor() {
const baseUrl = `${getBaseUrl()}/api/`;
if (process.env.NODE_ENV !== "production") {
this.initialize(baseUrl, process.env["REACT_APP_APIKEY"]!);
} else {
this.initialize(baseUrl, window.Bazarr.apiKey);
}
const baseUrl = `${Environment.baseUrl}/api/`;
this.initialize(baseUrl, Environment.apiKey);
}
initialize(url: string, apikey?: string) {
@ -53,7 +49,7 @@ class Api {
}
_resetApi(apikey: string) {
if (process.env.NODE_ENV !== "production") {
if (!isProdEnv) {
this.axios.defaults.headers.common["X-API-KEY"] = apikey;
}
}

@ -1,17 +1,23 @@
const proxy = require("http-proxy-middleware");
const { createProxyMiddleware } = require("http-proxy-middleware");
const target = "http://localhost:6767";
const target = process.env["REACT_APP_PROXY_URL"];
const allowWs = process.env["REACT_APP_ALLOW_WEBSOCKET"] === "true";
const secure = process.env["REACT_APP_PROXY_SECURE"] === "true";
module.exports = function (app) {
app.use(
proxy(["/api", "/images", "/test", "/bazarr.log"], {
createProxyMiddleware(["/api", "/images", "/test", "/bazarr.log"], {
target,
changeOrigin: true,
secure,
})
);
app.use(
proxy("/api/socket.io", {
createProxyMiddleware("/api/socket.io", {
target,
ws: true,
ws: allowWs,
changeOrigin: true,
secure,
logLevel: "error",
})
);

@ -0,0 +1,45 @@
export const isDevEnv = process.env.NODE_ENV === "development";
export const isProdEnv = process.env.NODE_ENV === "production";
export const isTestEnv = process.env.NODE_ENV === "test";
export const Environment = {
get apiKey(): string | undefined {
if (isDevEnv) {
return process.env["REACT_APP_APIKEY"]!;
} else if (isTestEnv) {
return undefined;
} else {
return window.Bazarr.apiKey;
}
},
get canUpdate(): boolean {
if (isDevEnv) {
return process.env["REACT_APP_CAN_UPDATE"] === "true";
} else if (isTestEnv) {
return false;
} else {
return window.Bazarr.canUpdate;
}
},
get hasUpdate(): boolean {
if (isDevEnv) {
return process.env["REACT_APP_HAS_UPDATE"] === "true";
} else if (isTestEnv) {
return false;
} else {
return window.Bazarr.hasUpdate;
}
},
get baseUrl(): string {
if (isDevEnv || isTestEnv) {
// TODO: Support overriding base URL in development env
return "";
} else {
let url = window.Bazarr.baseUrl;
if (url.endsWith("/")) {
url = url.slice(0, -1);
}
return url;
}
},
};

@ -1,33 +1,12 @@
import { useCallback, useMemo, useState } from "react";
import { useCallback, useState } from "react";
import { useHistory } from "react-router";
import { useDidUpdate, useMediaMatch } from "rooks";
import { getBaseUrl } from ".";
export function useBaseUrl(slash: boolean = false) {
return useMemo(() => getBaseUrl(slash), [slash]);
}
export function useGotoHomepage() {
const history = useHistory();
return useCallback(() => history.push("/"), [history]);
}
export function useCanUpdateInject() {
if (process.env.NODE_ENV !== "production") {
return process.env["REACT_APP_CAN_UPDATE"] === "true";
} else {
return window.Bazarr.canUpdate;
}
}
export function useHasUpdateInject() {
if (process.env.NODE_ENV !== "production") {
return process.env["REACT_APP_HAS_UPDATE"] === "true";
} else {
return window.Bazarr.hasUpdate;
}
}
export function useIsMobile() {
return useMediaMatch("(max-width: 576px)");
}

@ -2,21 +2,6 @@ import { difference, differenceWith } from "lodash";
import { Dispatch } from "react";
import { isEpisode, isMovie, isSeries } from "./validate";
export function getBaseUrl(slash: boolean = false) {
let url: string = "/";
if (process.env.NODE_ENV === "production") {
url = window.Bazarr.baseUrl;
}
const endsWithSlash = url.endsWith("/");
if (slash && !endsWithSlash) {
return `${url}/`;
} else if (!slash && endsWithSlash) {
return url.slice(0, -1);
}
return url;
}
export function copyToClipboard(s: string) {
let field = document.createElement("textarea");
field.innerText = s;
@ -84,5 +69,6 @@ export function filterSubtitleBy(
export * from "./async";
export * from "./entity";
export * from "./env";
export * from "./hooks";
export * from "./validate";

@ -1,7 +1,9 @@
import { isProdEnv } from ".";
type LoggerType = "info" | "warning" | "error";
export function log(type: LoggerType, msg: string, ...payload: any[]) {
if (process.env.NODE_ENV !== "production") {
if (!isProdEnv) {
let logger = console.log;
if (type === "warning") {
logger = console.warn;

Loading…
Cancel
Save