Libraries Page

Basic Libraries and Libraries Info page completed

Auto init still broken

Need to remove redundant components
This commit is contained in:
Thegan Govender
2023-03-26 16:36:52 +02:00
parent 697f721fd6
commit 04e990f522
25 changed files with 728 additions and 77 deletions

View File

@@ -39,7 +39,6 @@ router.post("/setconfig", async (req, res) => {
query,
[JF_HOST, JF_API_KEY]
);
console.log({ JF_HOST: JF_HOST, JF_API_KEY: JF_API_KEY });
res.send(rows);
}catch(error)
{

View File

@@ -5,7 +5,7 @@
-- Dumped from database version 15.2 (Debian 15.2-1.pgdg110+1)
-- Dumped by pg_dump version 15.1
-- Started on 2023-03-25 19:59:41 UTC
-- Started on 2023-03-26 14:21:37 UTC
SET statement_timeout = 0;
SET lock_timeout = 0;
@@ -19,7 +19,45 @@ SET client_min_messages = warning;
SET row_security = off;
--
-- TOC entry 245 (class 1255 OID 49383)
-- TOC entry 251 (class 1255 OID 49412)
-- Name: fs_last_library_activity(text); Type: FUNCTION; Schema: public; Owner: postgres
--
CREATE FUNCTION public.fs_last_library_activity(libraryid text) RETURNS TABLE("Id" text, "Name" text, "EpisodeName" text, "SeasonNumber" integer, "EpisodeNumber" integer, "PrimaryImageHash" text, "UserId" text, "UserName" text, "LastPlayed" interval)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT *
FROM (
SELECT DISTINCT ON (i."Name", e."Name")
i."Id",
i."Name",
e."Name" AS "EpisodeName",
CASE WHEN a."SeasonId" IS NOT NULL THEN s."IndexNumber" ELSE NULL END AS "SeasonNumber",
CASE WHEN a."SeasonId" IS NOT NULL THEN e."IndexNumber" ELSE NULL END AS "EpisodeNumber",
i."PrimaryImageHash",
a."UserId",
a."UserName",
(NOW() - a."ActivityDateInserted") as "LastPlayed"
FROM jf_playback_activity a
JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId"
JOIN jf_libraries l ON i."ParentId" = l."Id"
LEFT JOIN jf_library_seasons s ON s."Id" = a."SeasonId"
LEFT JOIN jf_library_episodes e ON e."EpisodeId" = a."EpisodeId"
WHERE l."Id" = libraryid
ORDER BY i."Name", e."Name", a."ActivityDateInserted" DESC
) AS latest_distinct_rows
ORDER BY "LastPlayed"
LIMIT 15;
END;
$$;
ALTER FUNCTION public.fs_last_library_activity(libraryid text) OWNER TO postgres;
--
-- TOC entry 247 (class 1255 OID 49383)
-- Name: fs_last_user_activity(text); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -55,7 +93,36 @@ $$;
ALTER FUNCTION public.fs_last_user_activity(userid text) OWNER TO postgres;
--
-- TOC entry 232 (class 1255 OID 41783)
-- TOC entry 245 (class 1255 OID 49411)
-- Name: fs_library_stats(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
--
CREATE FUNCTION public.fs_library_stats(hours integer, libraryid text) RETURNS TABLE("Plays" bigint, total_playback_duration numeric, "Id" text, "Name" text)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT count(*) AS "Plays",
sum(a."PlaybackDuration") AS total_playback_duration,
l."Id",
l."Name"
FROM jf_playback_activity a
join jf_library_items i
on a."NowPlayingItemId"=i."Id"
join jf_libraries l
on i."ParentId"=l."Id"
WHERE a."ActivityDateInserted" BETWEEN CURRENT_DATE - MAKE_INTERVAL(hours => hours) AND NOW()
and l."Id"=libraryid
GROUP BY l."Id", l."Name"
ORDER BY (count(*)) DESC;
END;
$$;
ALTER FUNCTION public.fs_library_stats(hours integer, libraryid text) OWNER TO postgres;
--
-- TOC entry 233 (class 1255 OID 41783)
-- Name: fs_most_active_user(integer); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -78,7 +145,7 @@ $$;
ALTER FUNCTION public.fs_most_active_user(days integer) OWNER TO postgres;
--
-- TOC entry 247 (class 1255 OID 49386)
-- TOC entry 249 (class 1255 OID 49386)
-- Name: fs_most_played_items(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -119,7 +186,7 @@ $$;
ALTER FUNCTION public.fs_most_played_items(days integer, itemtype text) OWNER TO postgres;
--
-- TOC entry 248 (class 1255 OID 49394)
-- TOC entry 250 (class 1255 OID 49394)
-- Name: fs_most_popular_items(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -167,7 +234,7 @@ $$;
ALTER FUNCTION public.fs_most_popular_items(days integer, itemtype text) OWNER TO postgres;
--
-- TOC entry 231 (class 1255 OID 41730)
-- TOC entry 232 (class 1255 OID 41730)
-- Name: fs_most_used_clients(integer); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -189,7 +256,7 @@ $$;
ALTER FUNCTION public.fs_most_used_clients(days integer) OWNER TO postgres;
--
-- TOC entry 246 (class 1255 OID 49385)
-- TOC entry 248 (class 1255 OID 49385)
-- Name: fs_most_viewed_libraries(integer); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -233,7 +300,7 @@ $$;
ALTER FUNCTION public.fs_most_viewed_libraries(days integer) OWNER TO postgres;
--
-- TOC entry 244 (class 1255 OID 49364)
-- TOC entry 246 (class 1255 OID 49364)
-- Name: fs_user_stats(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -533,7 +600,69 @@ CREATE TABLE public.jf_library_seasons (
ALTER TABLE public.jf_library_seasons OWNER TO postgres;
--
-- TOC entry 3230 (class 2606 OID 16401)
-- TOC entry 231 (class 1259 OID 49405)
-- Name: js_library_stats_overview; Type: VIEW; Schema: public; Owner: postgres
--
CREATE VIEW public.js_library_stats_overview AS
SELECT DISTINCT ON (l."Id") l."Id",
l."Name",
l."ServerId",
l."IsFolder",
l."Type",
l."CollectionType",
l."ImageTagsPrimary",
i."Id" AS "ItemId",
i."Name" AS "ItemName",
i."Type" AS "ItemType",
i."PrimaryImageHash",
s."IndexNumber" AS "SeasonNumber",
e."IndexNumber" AS "EpisodeNumber",
e."Name" AS "EpisodeName",
( SELECT count(*) AS count
FROM (public.jf_playback_activity a
JOIN public.jf_library_items i_1 ON ((a."NowPlayingItemId" = i_1."Id")))
WHERE (i_1."ParentId" = l."Id")) AS "Plays",
( SELECT sum(a."PlaybackDuration") AS sum
FROM (public.jf_playback_activity a
JOIN public.jf_library_items i_1 ON ((a."NowPlayingItemId" = i_1."Id")))
WHERE (i_1."ParentId" = l."Id")) AS total_playback_duration,
cv."Library_Count",
cv."Season_Count",
cv."Episode_Count",
(now() - latest_activity."ActivityDateInserted") AS "LastActivity"
FROM (((((public.jf_libraries l
JOIN public.jf_library_count_view cv ON ((cv."Id" = l."Id")))
LEFT JOIN ( SELECT jf_playback_activity."Id",
jf_playback_activity."IsPaused",
jf_playback_activity."UserId",
jf_playback_activity."UserName",
jf_playback_activity."Client",
jf_playback_activity."DeviceName",
jf_playback_activity."DeviceId",
jf_playback_activity."ApplicationVersion",
jf_playback_activity."NowPlayingItemId",
jf_playback_activity."NowPlayingItemName",
jf_playback_activity."SeasonId",
jf_playback_activity."SeriesName",
jf_playback_activity."EpisodeId",
jf_playback_activity."PlaybackDuration",
jf_playback_activity."ActivityDateInserted",
jf_playback_activity."PlayMethod",
i_1."ParentId"
FROM (public.jf_playback_activity
JOIN public.jf_library_items i_1 ON ((i_1."Id" = jf_playback_activity."NowPlayingItemId")))
ORDER BY jf_playback_activity."ActivityDateInserted" DESC) latest_activity ON ((l."Id" = latest_activity."ParentId")))
LEFT JOIN public.jf_library_items i ON ((i."Id" = latest_activity."NowPlayingItemId")))
LEFT JOIN public.jf_library_seasons s ON ((s."Id" = latest_activity."SeasonId")))
LEFT JOIN public.jf_library_episodes e ON ((e."EpisodeId" = latest_activity."EpisodeId")))
ORDER BY l."Id", latest_activity."ActivityDateInserted" DESC;
ALTER TABLE public.js_library_stats_overview OWNER TO postgres;
--
-- TOC entry 3236 (class 2606 OID 16401)
-- Name: app_config app_config_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
@@ -542,7 +671,7 @@ ALTER TABLE ONLY public.app_config
--
-- TOC entry 3232 (class 2606 OID 16419)
-- TOC entry 3238 (class 2606 OID 16419)
-- Name: jf_libraries jf_libraries_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
@@ -551,7 +680,7 @@ ALTER TABLE ONLY public.jf_libraries
--
-- TOC entry 3238 (class 2606 OID 24912)
-- TOC entry 3244 (class 2606 OID 24912)
-- Name: jf_library_episodes jf_library_episodes_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
@@ -560,7 +689,7 @@ ALTER TABLE ONLY public.jf_library_episodes
--
-- TOC entry 3234 (class 2606 OID 24605)
-- TOC entry 3240 (class 2606 OID 24605)
-- Name: jf_library_items jf_library_items_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
@@ -569,7 +698,7 @@ ALTER TABLE ONLY public.jf_library_items
--
-- TOC entry 3236 (class 2606 OID 24737)
-- TOC entry 3242 (class 2606 OID 24737)
-- Name: jf_library_seasons jf_library_seasons_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
@@ -578,7 +707,7 @@ ALTER TABLE ONLY public.jf_library_seasons
--
-- TOC entry 3240 (class 2606 OID 41737)
-- TOC entry 3246 (class 2606 OID 41737)
-- Name: jf_users jf_users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
@@ -587,7 +716,7 @@ ALTER TABLE ONLY public.jf_users
--
-- TOC entry 3384 (class 2618 OID 25163)
-- TOC entry 3390 (class 2618 OID 25163)
-- Name: jf_library_count_view _RETURN; Type: RULE; Schema: public; Owner: postgres
--
@@ -607,7 +736,7 @@ CREATE OR REPLACE VIEW public.jf_library_count_view AS
--
-- TOC entry 3241 (class 2606 OID 24617)
-- TOC entry 3247 (class 2606 OID 24617)
-- Name: jf_library_items jf_library_items_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
@@ -616,15 +745,15 @@ ALTER TABLE ONLY public.jf_library_items
--
-- TOC entry 3391 (class 0 OID 0)
-- Dependencies: 3241
-- TOC entry 3398 (class 0 OID 0)
-- Dependencies: 3247
-- Name: CONSTRAINT jf_library_items_fkey ON jf_library_items; Type: COMMENT; Schema: public; Owner: postgres
--
COMMENT ON CONSTRAINT jf_library_items_fkey ON public.jf_library_items IS 'jf_library';
-- Completed on 2023-03-25 19:59:42 UTC
-- Completed on 2023-03-26 14:21:38 UTC
--
-- PostgreSQL database dump complete

View File

@@ -226,5 +226,60 @@ router.post("/getUserLastPlayed", async (req, res) => {
}
});
router.post("/getLibraryDetails", async (req, res) => {
try {
const { libraryid } = req.body;
const { rows } = await db.query(
`select * from jf_libraries where "Id"='${libraryid}'`
);
res.send(rows[0]);
} catch (error) {
console.log(error);
res.send(error);
}
});
router.post("/getGlobalLibraryStats", async (req, res) => {
try {
const { hours,libraryid } = req.body;
let _hours = hours;
if (hours === undefined) {
_hours = 24;
}
console.log(`select * from fs_library_stats(${_hours},'${libraryid}')`);
const { rows } = await db.query(
`select * from fs_library_stats(${_hours},'${libraryid}')`
);
res.send(rows[0]);
} catch (error) {
console.log(error);
res.send(error);
}
});
router.get("/getLibraryStats", async (req, res) => {
try {
const { rows } = await db.query("select * from js_library_stats_overview");
res.send(rows);
} catch (error) {
res.send(error);
}
});
router.post("/getLibraryLastPlayed", async (req, res) => {
try {
const { libraryid } = req.body;
const { rows } = await db.query(
`select * from fs_last_library_activity('${libraryid}') limit 15`
);
res.send(rows);
} catch (error) {
console.log(error);
res.send(error);
}
});
module.exports = router;

View File

@@ -21,7 +21,6 @@ async function ActivityMonitor(interval) {
const apiKey = config[0].JF_API_KEY;
if (base_url === null || config[0].JF_API_KEY === null) {
console.log("Config Details Not Found");
return;
}

View File

@@ -19,6 +19,7 @@ import Settings from './pages/settings';
import Users from './pages/users';
import UserInfo from './pages/components/user-info';
import Libraries from './pages/libraries';
import LibraryInfo from './pages/components/library-info';
import ErrorPage from './pages/components/general/error';
@@ -79,6 +80,7 @@ if (!config || config.apiKey ==null) {
<Route path="/users" element={<Users />} />
<Route path="/users/:UserId" element={<UserInfo />} />
<Route path="/libraries" element={<Libraries />} />
<Route path="/libraries/:LibraryId" element={<LibraryInfo />} />
<Route path="/testing" element={<Testing />} />
</Routes>
</main>

View File

@@ -11,6 +11,7 @@ body {
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: white ;
}
code {

View File

@@ -0,0 +1,22 @@
import { useParams } from 'react-router-dom';
import LibraryDetails from './library/library-details';
import LibraryGlobalStats from './library/library-stats';
import LastLibraryPlayed from './library/lastplayed';
function LibraryInfo() {
const { LibraryId } = useParams();
return (
<div>
<LibraryDetails LibraryId={LibraryId}/>
<LibraryGlobalStats LibraryId={LibraryId}/>
<LastLibraryPlayed LibraryId={LibraryId}/>
</div>
);
}
export default LibraryInfo;

View File

@@ -0,0 +1,65 @@
import React from "react";
import "../../../css/globalstats.css";
function WatchTimeStats(props) {
function formatTime(totalSeconds, numberClassName, labelClassName) {
const units = [
{ label: 'Day', seconds: 86400 },
{ label: 'Hour', seconds: 3600 },
{ label: 'Minute', seconds: 60 },
];
const parts = units.reduce((result, { label, seconds }) => {
const value = Math.floor(totalSeconds / seconds);
if (value) {
const formattedValue = <p className={numberClassName}>{value}</p>;
const formattedLabel = (
<span className={labelClassName}>
{label}
{value === 1 ? '' : 's'}
</span>
);
result.push(
<span key={label} className="time-part">
{formattedValue} {formattedLabel}
</span>
);
totalSeconds -= value * seconds;
}
return result;
}, []);
if (parts.length === 0) {
return (
<>
<p className={numberClassName}>0</p>{' '}
<p className={labelClassName}>Minutes</p>
</>
);
}
return parts;
}
return (
<div className="global-stats">
<div className="stats-header">
<div>{props.heading}</div>
</div>
<div className="play-duration-stats" key={props.data.UserId}>
<p className="stat-value"> {props.data.Plays || 0}</p>
<p className="stat-unit" >Plays /</p>
<>{formatTime(props.data.total_playback_duration || 0,'stat-value','stat-unit')}</>
</div>
</div>
);
}
export default WatchTimeStats;

View File

@@ -0,0 +1,68 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import LastPlayedItem from "./lastplayed/last-played-item";
import Config from "../../../lib/config";
import "../../css/users/user-details.css";
function LastLibraryPlayed(props) {
const [data, setData] = useState();
const [config, setConfig] = useState();
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config();
setConfig(newConfig);
} catch (error) {
console.log(error);
}
};
const fetchData = async () => {
try {
const itemData = await axios.post(`/stats/getLibraryLastPlayed`, {
libraryid: props.LibraryId,
});
setData(itemData.data);
} catch (error) {
console.log(error);
}
};
if (!data) {
fetchData();
}
if (!config) {
fetchConfig();
}
const intervalId = setInterval(fetchData, 60000 * 5);
return () => clearInterval(intervalId);
}, [data,config, props.LibraryId]);
console.log(data);
if (!data || !config) {
return <></>;
}
return (
<div className="last-played">
<h1>Last Watched</h1>
<div className="last-played-container">
{data.map((item) => (
<LastPlayedItem data={item} base_url={config.hostUrl} key={item.Id+item.EpisodeNumber}/>
))}
</div>
</div>
);
}
export default LastLibraryPlayed;

View File

@@ -0,0 +1,66 @@
import React, {useState} from "react";
import { Blurhash } from 'react-blurhash';
import "../../../css/lastplayed.css";
function formatTime(time) {
const units = {
days: ['Day', 'Days'],
hours: ['Hour', 'Hours'],
minutes: ['Minute', 'Minutes'],
seconds: ['Second', 'Seconds']
};
let formattedTime = '';
if (time.days) {
formattedTime = `${time.days} ${units.days[time.days > 1 ? 1 : 0]}`;
} else if (time.hours) {
formattedTime = `${time.hours} ${units.hours[time.hours > 1 ? 1 : 0]}`;
} else if (time.minutes) {
formattedTime = `${time.minutes} ${units.minutes[time.minutes > 1 ? 1 : 0]}`;
} else {
formattedTime = `${time.seconds} ${units.seconds[time.seconds > 1 ? 1 : 0]}`;
}
return `${formattedTime} ago`;
}
function LastPlayedItem(props) {
const [loaded, setLoaded] = useState(false);
return (
<div className="last-card">
<div className="last-card-banner">
{loaded ? null : <Blurhash hash={props.data.PrimaryImageHash} width={'100%'} height={'100%'}/>}
<img
src={
`${
props.base_url +
"/Items/" +
props.data.Id +
"/Images/Primary?fillHeight=320&fillWidth=213&quality=50"}`
}
onLoad={() => setLoaded(true)}
style={loaded ? { backgroundImage: `url(path/to/image.jpg)` } : { display: 'none' }}
/>
</div>
<div className="last-item-details">
<div className="last-last-played">
{formatTime(props.data.LastPlayed)}
</div>
<div className="last-item-name"> {props.data.Name}</div>
<div className="last-item-episode"> {props.data.EpisodeName}</div>
</div>
{props.data.SeasonNumber ?
<div className="last-item-episode number"> S{props.data.SeasonNumber} - E{props.data.EpisodeNumber}</div>:
<></>
}
</div>
);
}
export default LastPlayedItem;

View File

@@ -1,22 +1,113 @@
import React from "react";
import { Link } from "react-router-dom";
import "../../css/library/library-card.css";
function LibraryCard(props) {
return (
<div
className="library-card-banner"
style={{
backgroundImage: `url(${
props.base_url +
"/Items/" +
props.data.Id +
"/Images/Primary/?fillWidth=300&quality=90"})`,
}}
>
</div>
function formatTotalWatchTime(seconds) {
const hours = Math.floor(seconds / 3600); // 1 hour = 3600 seconds
const minutes = Math.floor((seconds % 3600) / 60); // 1 minute = 60 seconds
let formattedTime='';
if(hours)
{
formattedTime+=`${hours} hours`;
}
if(minutes)
{
formattedTime+=` ${minutes} minutes`;
}
if(!hours && !minutes)
{
formattedTime=`0 minutes`;
}
return formattedTime ;
}
function formatLastActivityTime(time) {
const units = {
days: ['Day', 'Days'],
hours: ['Hour', 'Hours'],
minutes: ['Minute', 'Minutes']
};
let formattedTime = '';
for (const unit in units) {
if (time[unit]) {
const unitName = units[unit][time[unit] > 1 ? 1 : 0];
formattedTime += `${time[unit]} ${unitName} `;
}
}
return `${formattedTime}ago`;
}
return (
<div className="library-card">
<Link to={`/libraries/${props.data.Id}`}>
<div
className="library-card-banner"
style={{
backgroundImage: `url(${
props.base_url +
"/Items/" +
props.data.Id +
"/Images/Primary/?fillWidth=400&quality=90"
})`,
}}
/>
</Link>
<div className="library-card-details">
<div>
<p className="label">Library</p>
<p>{props.data.Name}</p>
</div>
<div>
<p className="label">Type</p>
<p>{props.data.CollectionType==='tvshows' ? 'Series' : "Movies"}</p>
</div>
<div>
<p className="label">Total Plays</p>
<p>{props.data.Plays}</p>
</div>
<div>
<p className="label">Total Playback</p>
<p>{formatTotalWatchTime(props.data.total_playback_duration)}</p>
</div>
<div>
<p className="label">Last Played</p>
<p>{props.data.ItemName ? props.data.ItemName : 'n/a'}</p>
</div>
<div>
<p className="label">Last Activity</p>
<p>{props.data.LastActivity ? formatLastActivityTime(props.data.LastActivity) : 'never'}</p>
</div>
<div>
<p className="label">{props.data.CollectionType==='tvshows' ? 'Series' : "Movies"}</p>
<p>{props.data.Library_Count}</p>
</div>
<div>
<p className="label">Seasons</p>
<p>{props.data.CollectionType==='tvshows' ? props.data.Season_Count : ''}</p>
</div>
<div>
<p className="label">Episodes</p>
<p>{props.data.CollectionType==='tvshows' ? props.data.Episode_Count : ''}</p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,52 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import TvLineIcon from "remixicon-react/TvLineIcon";
import FilmLineIcon from "remixicon-react/FilmLineIcon";
// import "../../css/users/user-details.css";
function LibraryDetails(props) {
const [data, setData] = useState();
useEffect(() => {
const fetchData = async () => {
try {
const libraryrData = await axios.post(`/stats/getLibraryDetails`, {
libraryid: props.LibraryId,
});
setData(libraryrData.data);
} catch (error) {
console.log(error);
}
};
if (!data) {
fetchData();
}
const intervalId = setInterval(fetchData, 60000 * 5);
return () => clearInterval(intervalId);
}, [data, props.LibraryId]);
if (!data) {
return <></>;
}
return (
<div className="user-detail-container">
<div className="user-image-container">
{data.CollectionType==="tvshows" ?
<TvLineIcon size={'100%'}/>
:
<FilmLineIcon size={'100%'}/>
}
</div>
<p className="user-name">{data.Name}</p>
</div>
);
}
export default LibraryDetails;

View File

@@ -0,0 +1,62 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import "../../css/globalstats.css";
import WatchTimeStats from "./globalstats/watchtimestats";
function LibraryGlobalStats(props) {
const [dayStats, setDayStats] = useState({});
const [weekStats, setWeekStats] = useState({});
const [monthStats, setMonthStats] = useState({});
const [allStats, setAllStats] = useState({});
useEffect(() => {
const fetchData = async () => {
try {
const dayData = await axios.post(`/stats/getGlobalLibraryStats`, {
hours: (24*1),
libraryid: props.LibraryId,
});
setDayStats(dayData.data);
const weekData = await axios.post(`/stats/getGlobalLibraryStats`, {
hours: (24*7),
libraryid: props.LibraryId,
});
setWeekStats(weekData.data);
const monthData = await axios.post(`/stats/getGlobalLibraryStats`, {
hours: (24*30),
libraryid: props.LibraryId,
});
setMonthStats(monthData.data);
const allData = await axios.post(`/stats/getGlobalLibraryStats`, {
hours: (24*999),
libraryid: props.LibraryId,
});
setAllStats(allData.data);
} catch (error) {
console.log(error);
}
};
fetchData();
const intervalId = setInterval(fetchData, 60000 * 5);
return () => clearInterval(intervalId);
}, [props.LibraryId]);
return (
<div>
<h1>Library Stats</h1>
<div className="global-stats-container">
<WatchTimeStats data={dayStats} heading={"Last 24 Hours"} />
<WatchTimeStats data={weekStats} heading={"Last 7 Days"} />
<WatchTimeStats data={monthStats} heading={"Last 30 Days"} />
<WatchTimeStats data={allStats} heading={"All Time"} />
</div>
</div>
);
}
export default LibraryGlobalStats;

View File

@@ -7,7 +7,7 @@ function LibraryStatComponent(props) {
}
return (
<div className="library-card">
<div className="library-stat-card">
<div className="library-image">
<div className="library-icons">

View File

@@ -7,7 +7,7 @@ const TerminalComponent = () => {
useEffect(() => {
// create a new WebSocket connection
const socket = new WebSocket('ws://localhost:8080/ws');
const socket = new WebSocket('ws://localhost:8080');
// handle incoming messages
socket.addEventListener('message', (event) => {

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import "../../css/users/globalstats.css";
import "../../css/globalstats.css";
import WatchTimeStats from "./globalstats/watchtimestats";

View File

@@ -1,6 +1,6 @@
import React from "react";
import "../../../css/users/globalstats.css";
import "../../../css/globalstats.css";
function WatchTimeStats(props) {

View File

@@ -1,11 +0,0 @@
.library-banner
{
border-radius: 5px;
}
.library-banner-image
{
border-radius: 5px;
max-width: 500px;
max-height: 500px;
}

View File

@@ -0,0 +1,12 @@
.libraries-container
{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
}
/* .library-banner-image
{
border-radius: 5px;
max-width: 500px;
max-height: 500px;
} */

View File

@@ -1,9 +1,54 @@
.library-card
{
/* border: solid 1px rgb(87, 87, 87); */
border-radius: 4px;
width: 400px;
/* width: 800px; */
margin-bottom: 40px;
}
.library-card-banner
{
width: 300px;
width: 400px;
height: 170px;
background-color: black;
background-repeat: no-repeat;
background-size: cover;
margin-bottom: 20px;
border-radius: 4px;
}
border-radius: 4px 4px 0 0;
transition: all 0.2s ease-in-out;
}
.library-card-banner:hover
{
opacity: 0.5;
}
.library-card-details
{
background-color: rgb(100, 100, 100,0.2);
border-radius:0 0 4px 4px ;
}
.library-card-details div
{
display: flex;
justify-content: space-between;
width: 100%;
}
.library-card-details div .label
{
color: #00A4DC;
}
.library-card-details div p
{
margin: 10px;
}

View File

@@ -8,7 +8,7 @@
margin-right: 20px;
}
.library-card
.library-stat-card
{
width: 500px;
height: 180px;

View File

@@ -3,14 +3,7 @@
margin-bottom: 20px;;
}
.form-row
{
margin-top: 20px;
display: grid;
grid-template-columns: repeat(4,minmax(0,1fr));
align-items: flex-start;
gap: 20px;
}
.settings-form {
@@ -31,12 +24,14 @@
margin-bottom: 10px;
}
/* .settings-form div {
display: flex;
flex-direction: column;
margin-bottom: 20px;
width: 50%;
} */
.form-row
{
margin-top: 20px;
display: grid;
grid-template-columns: repeat(3,minmax(0,1fr));
align-items: flex-start;
gap: 20px;
}
.form-row label {
font-weight: bold;
@@ -47,7 +42,6 @@
padding: 5px;
border-radius: 5px;
border: none;
grid-column: span 2/span 2;
max-width: 700px;
}

View File

@@ -2,8 +2,8 @@ import React, { useState, useEffect } from "react";
import axios from "axios";
import Config from "../lib/config";
import "./css/libraries.css";
import "./css/users/users.css";
import "./css/library/libraries.css";
// import "./css/users/users.css";
import Loading from "./components/general/loading";
import LibraryCard from "./components/library/library-card";
@@ -28,7 +28,7 @@ function Libraries() {
};
const fetchLibraries = () => {
const url = `/api/getLibraries`;
const url = `/stats/getLibraryStats`;
axios
.get(url)
.then((data) => {
@@ -59,13 +59,13 @@ function Libraries() {
}
return (
<div className="Activity">
<div className="libraries">
<h1>Libraries</h1>
<div>
<div className="libraries-container">
{data &&
data.map((item) => (
<LibraryCard data={item} base_url={config.hostUrl}/>
<LibraryCard key={item.Id} data={item} base_url={config.hostUrl}/>
))}
</div>

View File

@@ -1,6 +1,6 @@
import React from 'react';
import './css/libraries.css';
import './css/library/libraries.css';