⚙️Settings-driven AirDC++ configuration first draft
This commit is contained in:
@@ -30,10 +30,16 @@ function sleep(ms: number): Promise<NodeJS.Timeout> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const search =
|
export const search =
|
||||||
(data: SearchData, ADCPPSocket: any) => async (dispatch) => {
|
(data: SearchData, ADCPPSocket: any, credentials: any) =>
|
||||||
|
async (dispatch) => {
|
||||||
try {
|
try {
|
||||||
|
console.log(credentials);
|
||||||
if (!ADCPPSocket.isConnected()) {
|
if (!ADCPPSocket.isConnected()) {
|
||||||
await ADCPPSocket.connect("admin", "password", true);
|
await ADCPPSocket.connect(
|
||||||
|
credentials.username,
|
||||||
|
credentials.password,
|
||||||
|
true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const instance: SearchInstance = await ADCPPSocket.post("search");
|
const instance: SearchInstance = await ADCPPSocket.post("search");
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { SETTINGS_OBJECT_FETCHED } from "../constants/action-types";
|
import {
|
||||||
|
SETTINGS_OBJECT_FETCHED,
|
||||||
|
SETTINGS_OBJECT_DELETED,
|
||||||
|
} from "../constants/action-types";
|
||||||
import { SETTINGS_SERVICE_BASE_URI } from "../constants/endpoints";
|
import { SETTINGS_SERVICE_BASE_URI } from "../constants/endpoints";
|
||||||
|
|
||||||
export const saveSettings =
|
export const saveSettings =
|
||||||
@@ -29,3 +32,17 @@ export const getSettings = (settingsKey?) => async (dispatch) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deleteSettings = () => async (dispatch) => {
|
||||||
|
const result = await axios({
|
||||||
|
url: `${SETTINGS_SERVICE_BASE_URI}/deleteSettings`,
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.data.ok === 1) {
|
||||||
|
dispatch({
|
||||||
|
type: SETTINGS_OBJECT_FETCHED,
|
||||||
|
data: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import React, { useCallback, useContext, ReactElement } from "react";
|
import React, { useCallback, useContext, ReactElement, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
search,
|
search,
|
||||||
downloadAirDCPPItem,
|
downloadAirDCPPItem,
|
||||||
getBundlesForComic,
|
getBundlesForComic,
|
||||||
} from "../actions/airdcpp.actions";
|
} from "../actions/airdcpp.actions";
|
||||||
import { SocketContext } from "../context/AirDCPPSocket";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { RootState, SearchInstance } from "threetwo-ui-typings";
|
import { RootState, SearchInstance } from "threetwo-ui-typings";
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
import { isEmpty, isNil, isUndefined, map } from "lodash";
|
import { isEmpty, isNil, isUndefined, map } from "lodash";
|
||||||
|
import { AirDCPPSocketContext } from "../context/AirDCPPSocket";
|
||||||
|
import { getSettings } from "../actions/settings.actions";
|
||||||
|
import AirDCPPSocket from "../services/DcppSearchService";
|
||||||
interface IAcquisitionPanelProps {
|
interface IAcquisitionPanelProps {
|
||||||
comicBookMetadata: any;
|
comicBookMetadata: any;
|
||||||
}
|
}
|
||||||
@@ -21,7 +23,6 @@ export const AcquisitionPanel = (
|
|||||||
const sanitizedVolumeName = volumeName.replace(/[^a-zA-Z0-9 ]/g, "");
|
const sanitizedVolumeName = volumeName.replace(/[^a-zA-Z0-9 ]/g, "");
|
||||||
const issueName = props.comicBookMetadata.sourcedMetadata.comicvine.name;
|
const issueName = props.comicBookMetadata.sourcedMetadata.comicvine.name;
|
||||||
|
|
||||||
// local state
|
|
||||||
// Selectors for picking state
|
// Selectors for picking state
|
||||||
const airDCPPSearchResults = useSelector((state: RootState) => {
|
const airDCPPSearchResults = useSelector((state: RootState) => {
|
||||||
return state.airdcpp.searchResults;
|
return state.airdcpp.searchResults;
|
||||||
@@ -35,18 +36,31 @@ export const AcquisitionPanel = (
|
|||||||
const searchInstance: SearchInstance = useSelector(
|
const searchInstance: SearchInstance = useSelector(
|
||||||
(state: RootState) => state.airdcpp.searchInstance,
|
(state: RootState) => state.airdcpp.searchInstance,
|
||||||
);
|
);
|
||||||
const airDCPPClientSettings = useSelector(
|
|
||||||
(state: RootState) => state.settings.data,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const userSettings = useSelector((state: RootState) => state.settings.data);
|
||||||
|
const { ADCPPSocket, setADCPPSocket } = useContext(AirDCPPSocketContext);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const ADCPPSocket = useContext(SocketContext);
|
useEffect(() => {
|
||||||
|
dispatch(getSettings());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (isEmpty(ADCPPSocket) && !isEmpty(userSettings)) {
|
||||||
|
setADCPPSocket(
|
||||||
|
new AirDCPPSocket({
|
||||||
|
hostname: `${userSettings.directConnect.client.hostname}`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const getDCPPSearchResults = useCallback(
|
const getDCPPSearchResults = useCallback(
|
||||||
(searchQuery) => {
|
(searchQuery) => {
|
||||||
console.log(ADCPPSocket);
|
dispatch(
|
||||||
dispatch(search(searchQuery, ADCPPSocket));
|
search(searchQuery, ADCPPSocket, {
|
||||||
|
username: `${userSettings.directConnect.client.username}`,
|
||||||
|
password: `${userSettings.directConnect.client.password}`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[dispatch, ADCPPSocket],
|
[dispatch, ADCPPSocket],
|
||||||
);
|
);
|
||||||
@@ -80,8 +94,9 @@ export const AcquisitionPanel = (
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{JSON.stringify(ADCPPSocket)}
|
||||||
<div className="comic-detail columns">
|
<div className="comic-detail columns">
|
||||||
{!isEmpty(ADCPPSocket) && !isUndefined(ADCPPSocket) ? (
|
{!isEmpty(ADCPPSocket) ? (
|
||||||
<div className="column is-one-fifth">
|
<div className="column is-one-fifth">
|
||||||
<button
|
<button
|
||||||
className={
|
className={
|
||||||
|
|||||||
@@ -1,25 +1,31 @@
|
|||||||
import React, { ReactElement, useEffect } from "react";
|
import React, { ReactElement, useCallback, useContext, useEffect } from "react";
|
||||||
import { Form, Field } from "react-final-form";
|
import { Form, Field } from "react-final-form";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { saveSettings, getSettings } from "../actions/settings.actions";
|
import {
|
||||||
|
getSettings,
|
||||||
|
saveSettings,
|
||||||
|
deleteSettings,
|
||||||
|
} from "../actions/settings.actions";
|
||||||
import { AirDCPPSettingsConfirmation } from "./AirDCPPSettings/AirDCPPSettingsConfirmation";
|
import { AirDCPPSettingsConfirmation } from "./AirDCPPSettings/AirDCPPSettingsConfirmation";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { AirDCPPSocketContext } from "../context/AirDCPPSocket";
|
||||||
|
import AirDCPPSocket from "../services/DcppSearchService";
|
||||||
import { isUndefined, isEmpty } from "lodash";
|
import { isUndefined, isEmpty } from "lodash";
|
||||||
|
|
||||||
export const AirDCPPSettingsForm = (): ReactElement => {
|
export const AirDCPPSettingsForm = (): ReactElement => {
|
||||||
const airdcppClientSettings = useSelector(
|
const airDCPPClientSettings = useSelector(
|
||||||
(state: RootState) => state.settings.data,
|
(state: RootState) => state.settings.data,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { ADCPPSocket, setADCPPSocket } = useContext(AirDCPPSocketContext);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getSettings());
|
dispatch(getSettings());
|
||||||
}, [dispatch]);
|
}, []);
|
||||||
|
|
||||||
const onSubmit = async (values) => {
|
const onSubmit = async (values) => {
|
||||||
try {
|
try {
|
||||||
const fqdn = values.protocol + values.hostname;
|
const fqdn = values.protocol + values.hostname;
|
||||||
const airdcppResponse = await axios({
|
const airDCPPResponse = await axios({
|
||||||
url: `${fqdn}/api/v1/sessions/authorize`,
|
url: `${fqdn}/api/v1/sessions/authorize`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: {
|
data: {
|
||||||
@@ -27,18 +33,37 @@ export const AirDCPPSettingsForm = (): ReactElement => {
|
|||||||
password: values.password,
|
password: values.password,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (airdcppResponse.status === 200) {
|
if (airDCPPResponse.status === 200) {
|
||||||
dispatch(saveSettings(values, airdcppResponse.data));
|
dispatch(saveSettings(values, airDCPPResponse.data));
|
||||||
|
setADCPPSocket(
|
||||||
|
new AirDCPPSocket({
|
||||||
|
hostname: `${values.hostname}`,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const hubList = await axios({
|
||||||
|
url: `${fqdn}/api/v1/hubs`,
|
||||||
|
method: "GET",
|
||||||
|
params: {
|
||||||
|
username: values.username,
|
||||||
|
password: values.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeSettings = useCallback(async () => {
|
||||||
|
dispatch(deleteSettings());
|
||||||
|
setADCPPSocket({});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const validate = async () => {};
|
const validate = async () => {};
|
||||||
const initFormData =
|
const initFormData =
|
||||||
!isEmpty(airdcppClientSettings.directConnect) ||
|
!isEmpty(airDCPPClientSettings.directConnect) ||
|
||||||
!isUndefined(airdcppClientSettings.directConnect)
|
!isUndefined(airDCPPClientSettings.directConnect)
|
||||||
? airdcppClientSettings.directConnect.client
|
? airDCPPClientSettings.directConnect.client
|
||||||
: {};
|
: {};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -112,17 +137,19 @@ export const AirDCPPSettingsForm = (): ReactElement => {
|
|||||||
{!isEmpty(initFormData) ? "Update" : "Save"}
|
{!isEmpty(initFormData) ? "Update" : "Save"}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
{!isUndefined(airdcppClientSettings) ? (
|
|
||||||
<p className="control">
|
|
||||||
<button className="button is-danger">Delete</button>
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{!isEmpty(airdcppClientSettings) ? (
|
{!isEmpty(airDCPPClientSettings) ? (
|
||||||
<AirDCPPSettingsConfirmation settings={airdcppClientSettings} />
|
<p className="control">
|
||||||
|
<button className="button is-danger" onClick={removeSettings}>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
{!isEmpty(airDCPPClientSettings) ? (
|
||||||
|
<AirDCPPSettingsConfirmation settings={airDCPPClientSettings} />
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement, useEffect, useState } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { hot } from "react-hot-loader";
|
import { hot } from "react-hot-loader";
|
||||||
import Dashboard from "./Dashboard";
|
import Dashboard from "./Dashboard";
|
||||||
|
|
||||||
@@ -14,6 +14,10 @@ import { Switch, Route } from "react-router";
|
|||||||
import Navbar from "./Navbar";
|
import Navbar from "./Navbar";
|
||||||
import "../assets/scss/App.scss";
|
import "../assets/scss/App.scss";
|
||||||
import Notifications from "react-notification-system-redux";
|
import Notifications from "react-notification-system-redux";
|
||||||
|
import { getSettings } from "../actions/settings.actions";
|
||||||
|
import { AirDCPPSocketContext } from "../context/AirDCPPSocket";
|
||||||
|
import { isEmpty, isUndefined } from "lodash";
|
||||||
|
import AirDCPPSocket from "../services/DcppSearchService";
|
||||||
|
|
||||||
//Optional styling
|
//Optional styling
|
||||||
const style = {
|
const style = {
|
||||||
@@ -58,39 +62,48 @@ const style = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const App = (): ReactElement => {
|
export const App = (): ReactElement => {
|
||||||
const notifications = useSelector((state: RootState) => state.notifications);
|
const notifications = useSelector((state: RootState) => state.notifications);
|
||||||
|
|
||||||
|
const [ADCPPSocket, setADCPPSocket] = useState({});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<AirDCPPSocketContext.Provider value={{ ADCPPSocket, setADCPPSocket }}>
|
||||||
<Navbar />
|
<div>
|
||||||
<Notifications
|
<Navbar />
|
||||||
notifications={notifications}
|
<Notifications
|
||||||
style={style}
|
notifications={notifications}
|
||||||
newOnTop={true}
|
style={style}
|
||||||
allowHTML={true}
|
newOnTop={true}
|
||||||
/>
|
allowHTML={true}
|
||||||
<Switch>
|
/>
|
||||||
<Route exact path="/">
|
<Switch>
|
||||||
<Dashboard />
|
<Route exact path="/">
|
||||||
</Route>
|
<Dashboard />
|
||||||
<Route path="/import">
|
</Route>
|
||||||
<Import path={"./comics"} />
|
<Route path="/import">
|
||||||
</Route>
|
<Import path={"./comics"} />
|
||||||
<Route path="/library">
|
</Route>
|
||||||
<Library />
|
<Route path="/library">
|
||||||
</Route>
|
<Library />
|
||||||
<Route path="/library-grid">
|
</Route>
|
||||||
<LibraryGrid />
|
<Route path="/library-grid">
|
||||||
</Route>
|
<LibraryGrid />
|
||||||
<Route path="/search">
|
</Route>
|
||||||
<Search />
|
<Route path="/search">
|
||||||
</Route>
|
<Search />
|
||||||
<Route path={"/comic/details/:comicObjectId"} component={ComicDetail} />
|
</Route>
|
||||||
<Route path="/settings">
|
<Route
|
||||||
<Settings />
|
path={"/comic/details/:comicObjectId"}
|
||||||
</Route>
|
component={ComicDetail}
|
||||||
</Switch>
|
/>
|
||||||
</div>
|
<Route path="/settings">
|
||||||
|
<Settings />
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
</AirDCPPSocketContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
} from "../actions/airdcpp.actions";
|
} from "../actions/airdcpp.actions";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { RootState } from "threetwo-ui-typings";
|
import { RootState } from "threetwo-ui-typings";
|
||||||
import { SocketContext } from "../context/AirDCPPSocket";
|
|
||||||
import { isNil, map } from "lodash";
|
import { isNil, map } from "lodash";
|
||||||
import prettyBytes from "pretty-bytes";
|
import prettyBytes from "pretty-bytes";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
@@ -27,6 +26,7 @@ export const DownloadsPanel = (
|
|||||||
});
|
});
|
||||||
console.log("BANDYA", bundles);
|
console.log("BANDYA", bundles);
|
||||||
const ADCPPSocket = useContext(SocketContext);
|
const ADCPPSocket = useContext(SocketContext);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getBundlesForComic(props.comicObjectId, ADCPPSocket));
|
dispatch(getBundlesForComic(props.comicObjectId, ADCPPSocket));
|
||||||
|
|||||||
@@ -67,3 +67,4 @@ export const LS_COVER_EXTRACTED = "LS_COVER_EXTRACTED";
|
|||||||
export const SETTINGS_CALL_IN_PROGRESS = "SETTINGS_CALL_IN_PROGRESS";
|
export const SETTINGS_CALL_IN_PROGRESS = "SETTINGS_CALL_IN_PROGRESS";
|
||||||
export const SETTINGS_OBJECT_FETCHED = "SETTINGS_OBJECT_FETCHED";
|
export const SETTINGS_OBJECT_FETCHED = "SETTINGS_OBJECT_FETCHED";
|
||||||
export const SETTINGS_CALL_FAILED = "SETTINGS_CALL_FAILED";
|
export const SETTINGS_CALL_FAILED = "SETTINGS_CALL_FAILED";
|
||||||
|
export const SETTINGS_OBJECT_DELETED = "SETTINGS_OBJECT_DELETED";
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import AirDCPPSocket from "../services/DcppSearchService";
|
|
||||||
import axios from "axios";
|
|
||||||
import { SETTINGS_SERVICE_BASE_URI } from "../constants/endpoints";
|
|
||||||
|
|
||||||
const socketInitConfiguration = await axios({
|
const AirDCPPSocketContext = React.createContext(null);
|
||||||
url: `${SETTINGS_SERVICE_BASE_URI}/getSettings`,
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const airDCPPSocket = new AirDCPPSocket({
|
export { AirDCPPSocketContext };
|
||||||
hostname: `${socketInitConfiguration.data.directConnect.client.hostname}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const SocketContext = React.createContext(airDCPPSocket);
|
|
||||||
|
|||||||
@@ -3,18 +3,17 @@ import { render } from "react-dom";
|
|||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import { ConnectedRouter } from "connected-react-router";
|
import { ConnectedRouter } from "connected-react-router";
|
||||||
import configureStore, { history } from "./store/index";
|
import configureStore, { history } from "./store/index";
|
||||||
import { SocketContext, airDCPPSocket } from "./context/AirDCPPSocket";
|
|
||||||
import App from "./components/App";
|
import App from "./components/App";
|
||||||
const store = configureStore({});
|
const store = configureStore({});
|
||||||
|
|
||||||
const rootEl = document.getElementById("root");
|
const rootEl = document.getElementById("root");
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<SocketContext.Provider value={airDCPPSocket}>
|
<Provider store={store}>
|
||||||
<Provider store={store}>
|
<ConnectedRouter history={history}>
|
||||||
<ConnectedRouter history={history}>
|
<App />
|
||||||
<App />
|
</ConnectedRouter>
|
||||||
</ConnectedRouter>
|
</Provider>,
|
||||||
</Provider>
|
|
||||||
</SocketContext.Provider>,
|
|
||||||
rootEl,
|
rootEl,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
SETTINGS_CALL_FAILED,
|
SETTINGS_CALL_FAILED,
|
||||||
SETTINGS_OBJECT_FETCHED,
|
SETTINGS_OBJECT_FETCHED,
|
||||||
|
SETTINGS_OBJECT_DELETED,
|
||||||
SETTINGS_CALL_IN_PROGRESS,
|
SETTINGS_CALL_IN_PROGRESS,
|
||||||
} from "../constants/action-types";
|
} from "../constants/action-types";
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@@ -23,6 +24,13 @@ function settingsReducer(state = initialState, action) {
|
|||||||
inProgress: false,
|
inProgress: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case SETTINGS_OBJECT_DELETED:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
data: action.data,
|
||||||
|
inProgress: false,
|
||||||
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return { ...state };
|
return { ...state };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user