reworked frontend config handling to reduce api calls

This commit is contained in:
CyferShepard
2024-07-16 19:17:05 +02:00
parent d58807ca39
commit e41baac27a
26 changed files with 169 additions and 124 deletions

View File

@@ -98,7 +98,7 @@ function App() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
if (!newConfig.response) {
setConfig(newConfig);
} else {

View File

@@ -1,20 +1,46 @@
import axios from 'axios';
import axios from "axios";
async function Config() {
const token = localStorage.getItem('token');
try {
const response = await axios.get('/api/getconfig', {
headers: {
Authorization: `Bearer ${token}`,
},
});
const { JF_HOST, APP_USER,REQUIRE_LOGIN, settings, IS_JELLYFIN } = response.data;
return { hostUrl: JF_HOST, username: APP_USER, token:token, requireLogin:REQUIRE_LOGIN, settings:settings, IS_JELLYFIN:IS_JELLYFIN };
class Config {
async fetchConfig() {
const token = localStorage.getItem("token");
try {
const response = await axios.get("/api/getconfig", {
headers: {
Authorization: `Bearer ${token}`,
},
});
const { JF_HOST, APP_USER, REQUIRE_LOGIN, settings, IS_JELLYFIN } = response.data;
return {
hostUrl: JF_HOST,
username: APP_USER,
token: token,
requireLogin: REQUIRE_LOGIN,
settings: settings,
IS_JELLYFIN: IS_JELLYFIN,
};
} catch (error) {
// console.log(error);
return error;
}
}
} catch (error) {
// console.log(error);
return error;
async setConfig(config) {
if (config == undefined) {
config = await this.fetchConfig();
}
localStorage.setItem("config", JSON.stringify(config));
return config;
}
async getConfig(refreshConfig) {
let config = localStorage.getItem("config");
if (config != undefined && !refreshConfig) {
return JSON.parse(config);
} else {
return await this.setConfig();
}
}
}
export default Config;
export default new Config();

View File

@@ -20,7 +20,7 @@ function Activity() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {
@@ -116,7 +116,7 @@ function Activity() {
</div>
<FormControl
type="text"
placeholder= {i18next.t("SEARCH")}
placeholder={i18next.t("SEARCH")}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="ms-md-3 my-3 w-sm-100 w-md-75"

View File

@@ -83,7 +83,7 @@ function ItemInfo() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
console.log(error);
@@ -226,7 +226,12 @@ function ItemInfo() {
</h1>
<Link
className="px-2"
to={config.hostUrl + `/web/index.html#!/${config.IS_JELLYFIN? "details" : "item"}?id=` + (data.EpisodeId || data.Id) + (config.settings.ServerID ? "&serverId=" + config.settings.ServerID : "")}
to={
config.hostUrl +
`/web/index.html#!/${config.IS_JELLYFIN ? "details" : "item"}?id=` +
(data.EpisodeId || data.Id) +
(config.settings.ServerID ? "&serverId=" + config.settings.ServerID : "")
}
title={i18next.t("ITEM_INFO.OPEN_IN_JELLYFIN")}
target="_blank"
>

View File

@@ -16,7 +16,7 @@ function MoreItems(props) {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
console.log(error);

View File

@@ -19,7 +19,7 @@ function LibraryLastWatched(props) {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
console.log(error);

View File

@@ -30,7 +30,7 @@ function LibraryItems(props) {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
console.log(error);

View File

@@ -62,7 +62,7 @@ function Sessions() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
console.log(error);

View File

@@ -31,7 +31,7 @@ export default function SettingsConfig() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setuse_password(newConfig.requireLogin);
setFormValues({ JS_USERNAME: newConfig.username });
} catch (error) {
@@ -84,6 +84,7 @@ export default function SettingsConfig() {
}
)
.then((data) => {
Config.setConfig();
setuse_password(requireLogin);
})
.catch((error) => {
@@ -126,6 +127,7 @@ export default function SettingsConfig() {
// let result = await updatePassword(hashedOldPassword, hashedNewPassword);
let result = await updateUser(username, hashedOldPassword, hashedNewPassword);
Config.setConfig();
if (result.isValid) {
setisSubmitted("Success");
setsubmissionMessage(i18next.t("PASSWORD_UPDATE_SUCCESS"));

View File

@@ -42,7 +42,7 @@ export default function SettingsConfig() {
}
useEffect(() => {
Config()
Config.getConfig()
.then((config) => {
setFormValues({ JF_HOST: config.hostUrl });
setConfig(config);
@@ -94,6 +94,7 @@ export default function SettingsConfig() {
setisSubmitted("Failed");
setsubmissionMessage(`Error Updating Configuration: ${errorMessage}`);
});
Config.setConfig();
}
function handleFormChange(event) {
@@ -129,6 +130,7 @@ export default function SettingsConfig() {
setisSubmitted("Failed");
setsubmissionMessage("Error Updating Configuration: ", error);
});
Config.setConfig();
}
function updateLanguage(event) {

View File

@@ -17,7 +17,7 @@ function MostActiveUsers(props) {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {

View File

@@ -18,7 +18,7 @@ function MPMovies(props) {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {

View File

@@ -16,7 +16,7 @@ function MPMusic(props) {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {

View File

@@ -14,7 +14,7 @@ function MPSeries(props) {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {

View File

@@ -19,7 +19,7 @@ function MVMusic(props) {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {

View File

@@ -14,7 +14,7 @@ function MVMovies(props) {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {

View File

@@ -17,7 +17,7 @@ function MVSeries(props) {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {

View File

@@ -1,60 +1,58 @@
import { useParams } from 'react-router-dom';
import { useParams } from "react-router-dom";
import { useState, useEffect } from "react";
import axios from "axios";
import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon";
import Config from "../../lib/config";
import {Tabs, Tab, Button, ButtonGroup } from 'react-bootstrap';
import { Tabs, Tab, Button, ButtonGroup } from "react-bootstrap";
import GlobalStats from './user-info/globalStats';
import LastPlayed from './user-info/lastplayed';
import UserActivity from './user-info/user-activity';
import GlobalStats from "./user-info/globalStats";
import LastPlayed from "./user-info/lastplayed";
import UserActivity from "./user-info/user-activity";
import "../css/users/user-details.css";
import { Trans } from 'react-i18next';
import { Trans } from "react-i18next";
function UserInfo() {
const { UserId } = useParams();
const [data, setData] = useState();
const [imgError, setImgError] = useState(false);
const [config, setConfig] = useState();
const [activeTab, setActiveTab] = useState('tabOverview');
const [activeTab, setActiveTab] = useState("tabOverview");
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
setConfig(newConfig);
} catch (error) {
console.log(error);
}
};
const fetchData = async () => {
if(config){
try {
const userData = await axios.post(`/api/getUserDetails`, {
userid: UserId,
}, {
headers: {
Authorization: `Bearer ${config.token}`,
"Content-Type": "application/json",
},
});
setData(userData.data);
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
console.log(error);
}
}
};
const fetchData = async () => {
if (config) {
try {
const userData = await axios.post(
`/api/getUserDetails`,
{
userid: UserId,
},
{
headers: {
Authorization: `Bearer ${config.token}`,
"Content-Type": "application/json",
},
}
);
setData(userData.data);
} catch (error) {
console.log(error);
}
}
};
fetchData();
if (!config) {
fetchConfig();
fetchConfig();
}
const intervalId = setInterval(fetchData, 60000 * 5);
@@ -69,48 +67,54 @@ function UserInfo() {
return <></>;
}
return (
<div>
<div className="user-detail-container">
<div className="user-image-container">
{imgError ? (
<AccountCircleFillIcon size={"100%"} />
) : (
<img
className="user-image"
src={
"/proxy/Users/Images/Primary?id=" +
UserId+
"&quality=100"
}
onError={handleImageError}
alt=""
></img>
)}
</div>
<div className="user-image-container">
{imgError ? (
<AccountCircleFillIcon size={"100%"} />
) : (
<img
className="user-image"
src={"/proxy/Users/Images/Primary?id=" + UserId + "&quality=100"}
onError={handleImageError}
alt=""
></img>
)}
</div>
<div>
<p className="user-name">{data.Name}</p>
<div>
<p className="user-name">{data.Name}</p>
<ButtonGroup>
<Button onClick={() => setActiveTab('tabOverview')} active={activeTab==='tabOverview'} variant='outline-primary' type='button'><Trans i18nKey="TAB_CONTROLS.OVERVIEW"/></Button>
<Button onClick={() => setActiveTab('tabActivity')} active={activeTab==='tabActivity'} variant='outline-primary' type='button'><Trans i18nKey="TAB_CONTROLS.ACTIVITY"/></Button>
<Button
onClick={() => setActiveTab("tabOverview")}
active={activeTab === "tabOverview"}
variant="outline-primary"
type="button"
>
<Trans i18nKey="TAB_CONTROLS.OVERVIEW" />
</Button>
<Button
onClick={() => setActiveTab("tabActivity")}
active={activeTab === "tabActivity"}
variant="outline-primary"
type="button"
>
<Trans i18nKey="TAB_CONTROLS.ACTIVITY" />
</Button>
</ButtonGroup>
</div>
</div>
</div>
<Tabs defaultActiveKey="tabOverview" activeKey={activeTab} variant='pills'>
<Tab eventKey="tabOverview" className='bg-transparent'>
<GlobalStats UserId={UserId}/>
<LastPlayed UserId={UserId}/>
</Tab>
<Tab eventKey="tabActivity" className='bg-transparent'>
<UserActivity UserId={UserId}/>
</Tab>
</Tabs>
<Tabs defaultActiveKey="tabOverview" activeKey={activeTab} variant="pills">
<Tab eventKey="tabOverview" className="bg-transparent">
<GlobalStats UserId={UserId} />
<LastPlayed UserId={UserId} />
</Tab>
<Tab eventKey="tabActivity" className="bg-transparent">
<UserActivity UserId={UserId} />
</Tab>
</Tabs>
</div>
);
}

View File

@@ -15,7 +15,7 @@ function LastPlayed(props) {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
console.log(error);

View File

@@ -60,7 +60,7 @@ function Sessions() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
console.log(error);

View File

@@ -21,7 +21,7 @@ function Libraries() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {

View File

@@ -11,7 +11,6 @@ import InformationLineIcon from "remixicon-react/InformationLineIcon";
import { Tooltip } from "@mui/material";
import { Trans } from "react-i18next";
function LibrarySelector() {
const [data, setData] = useState();
const [config, setConfig] = useState(null);
@@ -19,7 +18,7 @@ function LibrarySelector() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {
@@ -29,8 +28,7 @@ function LibrarySelector() {
};
const fetchLibraries = () => {
if(config)
{
if (config) {
const url = `/api/TrackedLibraries`;
axios
.get(url, {
@@ -45,11 +43,9 @@ function LibrarySelector() {
.catch((error) => {
console.log(error);
});
}
};
if (!config) {
fetchConfig();
}
@@ -57,7 +53,7 @@ function LibrarySelector() {
fetchLibraries();
const intervalId = setInterval(fetchLibraries, 60000 * 60);
return () => clearInterval(intervalId);
}, [ config]);
}, [config]);
if (!data) {
return <Loading />;
@@ -65,18 +61,24 @@ function LibrarySelector() {
return (
<div className="libraries">
<h1 className="py-4"><Trans i18nKey={"SETTINGS_PAGE.SELECT_LIBRARIES_TO_IMPORT"}/> <Tooltip title={<Trans i18nKey={"SETTINGS_PAGE.SELECT_LIBRARIES_TO_IMPORT_TOOLTIP"}/>}><span> <InformationLineIcon/></span></Tooltip></h1>
<h1 className="py-4">
<Trans i18nKey={"SETTINGS_PAGE.SELECT_LIBRARIES_TO_IMPORT"} />{" "}
<Tooltip title={<Trans i18nKey={"SETTINGS_PAGE.SELECT_LIBRARIES_TO_IMPORT_TOOLTIP"} />}>
<span>
{" "}
<InformationLineIcon />
</span>
</Tooltip>
</h1>
<div xs={1} md={2} lg={4} className="g-0 libraries-container">
{data &&
{data &&
data.map((item) => (
<ErrorBoundary key={item.Id} >
<SelectionCard data={item} base_url={config.hostUrl}/>
</ErrorBoundary>
))}
<ErrorBoundary key={item.Id}>
<SelectionCard data={item} base_url={config.hostUrl} />
</ErrorBoundary>
))}
</div>
</div>
);
}

View File

@@ -80,7 +80,7 @@ function Login() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {

View File

@@ -76,7 +76,7 @@ function Setup() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {

View File

@@ -70,7 +70,7 @@ function Signup() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {

View File

@@ -206,7 +206,11 @@ function Row(row) {
<TableCell>{data.TotalPlays}</TableCell>
<TableCell>{formatTotalWatchTime(data.TotalWatchTime) || `0 ${i18next.t("UNITS.MINUTES")}`}</TableCell>
<TableCell style={{ textTransform: data.LastSeen ? "none" : "lowercase" }}>
{data.LastSeen ? `${i18next.t("USERS_PAGE.AGO_ALT")} ${formatLastSeenTime(data.LastSeen)} ${i18next.t("USERS_PAGE.AGO").toLocaleLowerCase()}` : i18next.t("ERROR_MESSAGES.NEVER")}
{data.LastSeen
? `${i18next.t("USERS_PAGE.AGO_ALT")} ${formatLastSeenTime(data.LastSeen)} ${i18next
.t("USERS_PAGE.AGO")
.toLocaleLowerCase()}`
: i18next.t("ERROR_MESSAGES.NEVER")}
</TableCell>
</TableRow>
</React.Fragment>
@@ -227,7 +231,7 @@ function Users() {
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
if (error.code === "ERR_NETWORK") {
@@ -431,7 +435,7 @@ function Users() {
</div>
<FormControl
type="text"
placeholder= {i18next.t("SEARCH")}
placeholder={i18next.t("SEARCH")}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="ms-md-3 my-3 w-sm-100 w-md-75"