stat fixes+dynamic user content

Fixed more sql quereis

Noticed hardcoded userid value for last-played/recently played stats, changed to dynamic admin userid due to full permission access of libraries
This commit is contained in:
Thegan Govender
2023-04-05 23:16:48 +02:00
parent 59e19bc947
commit b313e6e22d
13 changed files with 163 additions and 82 deletions

View File

@@ -1,5 +1,6 @@
// api.js
const express = require("express");
const axios = require("axios");
const ActivityMonitor=require('./watchdog/ActivityMonitor');
const db = require("./db");
@@ -115,6 +116,28 @@ router.get("/getHistory", async (req, res) => {
});
router.get("/getAdminUsers", async (req, res) => {
try {
const { rows:config } = await db.query('SELECT * FROM app_config where "ID"=1');
const url = `${config[0].JF_HOST}/Users`;
const response = await axios.get(url, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY,
},
});
const adminUser = await response.data.filter(
(user) => user.Policy.IsAdministrator === true
);
res.send(adminUser);
} catch (error) {
console.log( error);
res.send(error);
}
});
router.get("/runWatchdog", async (req, res) => {
let message='Watchdog Started';
if(!process.env.WatchdogRunning )
@@ -129,4 +152,7 @@ router.get("/runWatchdog", async (req, res) => {
}
});
module.exports = router;

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-04-04 12:49:03 UTC
-- Started on 2023-04-05 21:14:53 UTC
SET statement_timeout = 0;
SET lock_timeout = 0;
@@ -19,7 +19,7 @@ SET client_min_messages = warning;
SET row_security = off;
--
-- TOC entry 254 (class 1255 OID 49412)
-- TOC entry 253 (class 1255 OID 49412)
-- Name: fs_last_library_activity(text); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -57,7 +57,7 @@ $$;
ALTER FUNCTION public.fs_last_library_activity(libraryid text) OWNER TO postgres;
--
-- TOC entry 250 (class 1255 OID 49383)
-- TOC entry 249 (class 1255 OID 49383)
-- Name: fs_last_user_activity(text); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -93,7 +93,7 @@ $$;
ALTER FUNCTION public.fs_last_user_activity(userid text) OWNER TO postgres;
--
-- TOC entry 247 (class 1255 OID 49411)
-- TOC entry 246 (class 1255 OID 49411)
-- Name: fs_library_stats(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -145,7 +145,7 @@ $$;
ALTER FUNCTION public.fs_most_active_user(days integer) OWNER TO postgres;
--
-- TOC entry 252 (class 1255 OID 49386)
-- TOC entry 251 (class 1255 OID 49386)
-- Name: fs_most_played_items(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -186,7 +186,7 @@ $$;
ALTER FUNCTION public.fs_most_played_items(days integer, itemtype text) OWNER TO postgres;
--
-- TOC entry 253 (class 1255 OID 49394)
-- TOC entry 252 (class 1255 OID 49394)
-- Name: fs_most_popular_items(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -256,7 +256,7 @@ $$;
ALTER FUNCTION public.fs_most_used_clients(days integer) OWNER TO postgres;
--
-- TOC entry 251 (class 1255 OID 49385)
-- TOC entry 250 (class 1255 OID 49385)
-- Name: fs_most_viewed_libraries(integer); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -300,7 +300,7 @@ $$;
ALTER FUNCTION public.fs_most_viewed_libraries(days integer) OWNER TO postgres;
--
-- TOC entry 249 (class 1255 OID 49364)
-- TOC entry 248 (class 1255 OID 49364)
-- Name: fs_user_stats(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -325,7 +325,7 @@ $$;
ALTER FUNCTION public.fs_user_stats(hours integer, userid text) OWNER TO postgres;
--
-- TOC entry 248 (class 1255 OID 49418)
-- TOC entry 247 (class 1255 OID 49418)
-- Name: fs_watch_stats_over_time(integer); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -370,7 +370,7 @@ $$;
ALTER FUNCTION public.fs_watch_stats_over_time(days integer) OWNER TO postgres;
--
-- TOC entry 246 (class 1255 OID 57644)
-- TOC entry 254 (class 1255 OID 57644)
-- Name: fs_watch_stats_popular_days_of_week(integer); Type: FUNCTION; Schema: public; Owner: postgres
--
@@ -379,27 +379,49 @@ CREATE FUNCTION public.fs_watch_stats_popular_days_of_week(days integer) RETURNS
AS $$
BEGIN
RETURN QUERY
WITH library_days AS (
SELECT
l."Name" AS "Library",
d.day_of_week,
d.day_name
FROM
jf_libraries l,
(SELECT 0 AS "day_of_week", 'Sunday' AS "day_name" UNION ALL
SELECT 1 AS "day_of_week", 'Monday' AS "day_name" UNION ALL
SELECT 2 AS "day_of_week", 'Tuesday' AS "day_name" UNION ALL
SELECT 3 AS "day_of_week", 'Wednesday' AS "day_name" UNION ALL
SELECT 4 AS "day_of_week", 'Thursday' AS "day_name" UNION ALL
SELECT 5 AS "day_of_week", 'Friday' AS "day_name" UNION ALL
SELECT 6 AS "day_of_week", 'Saturday' AS "day_name"
) d
)
SELECT
TO_CHAR(d."Day", 'Day') AS "Day",
COUNT(a."NowPlayingItemId") AS "Count",
COALESCE(l."Name", 'Unknown') AS "Library"
FROM (
SELECT
DATE_TRUNC('week', NOW()) + n * INTERVAL '1 day' AS "Day"
FROM generate_series(0, 6) n
) d
CROSS JOIN jf_libraries l
LEFT JOIN (
SELECT
DATE_TRUNC('day', "ActivityDateInserted") AS "Day",
"NowPlayingItemId",
i."ParentId"
FROM jf_playback_activity a
JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId"
WHERE a."ActivityDateInserted" BETWEEN NOW() - CAST(days || ' days' as INTERVAL) AND NOW()
) a ON d."Day" = a."Day" AND l."Id" = a."ParentId"
GROUP BY d."Day", l."Name"
ORDER BY d."Day";
library_days.day_name AS "Day",
COALESCE(SUM(counts."Count"), 0)::bigint AS "Count",
library_days."Library" AS "Library"
FROM
library_days
LEFT JOIN
(SELECT
DATE_TRUNC('day', a."ActivityDateInserted")::DATE AS "Date",
COUNT(*) AS "Count",
EXTRACT(DOW FROM a."ActivityDateInserted") AS "DOW",
l."Name" AS "Library"
FROM
jf_playback_activity a
JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId"
JOIN jf_libraries l ON i."ParentId" = l."Id"
WHERE
a."ActivityDateInserted" BETWEEN NOW() - CAST(days || ' days' as INTERVAL) AND NOW()
GROUP BY
l."Name", EXTRACT(DOW FROM a."ActivityDateInserted"), DATE_TRUNC('day', a."ActivityDateInserted")
) counts
ON counts."DOW" = library_days.day_of_week AND counts."Library" = library_days."Library"
GROUP BY
library_days.day_name, library_days.day_of_week, library_days."Library"
ORDER BY
library_days.day_of_week, library_days."Library";
END;
$$;
@@ -874,7 +896,7 @@ ALTER TABLE ONLY public.jf_library_items
COMMENT ON CONSTRAINT jf_library_items_fkey ON public.jf_library_items IS 'jf_library';
-- Completed on 2023-04-04 12:49:04 UTC
-- Completed on 2023-04-05 21:14:58 UTC
--
-- PostgreSQL database dump complete

View File

@@ -12,7 +12,7 @@ const db = require('./db');
const app = express();
const PORT = process.env.PORT || 3003;
const LISTEN_IP = '127.0.0.1';
const JWT_SECRET = process.env.JWT_SECRET;
const JWT_SECRET = process.env.JWT_SECRET ||'my-secret-jwt-key';
if (JWT_SECRET === undefined) {
console.log('JWT Secret cannot be undefined');

View File

@@ -1,7 +1,7 @@
main{
padding-inline: 10px;
margin-inline: 40px;
}
.App-logo {

3
src/lib/devices.js Normal file
View File

@@ -0,0 +1,3 @@
export const clientData = ["android","ios","safari","chrome","firefox","edge"]

View File

@@ -51,7 +51,6 @@ function LibraryLastPlayed(props) {
return () => clearInterval(intervalId);
}, [data,config, props.LibraryId]);
console.log(data);
if (!data || !config) {
return <></>;
@@ -62,7 +61,7 @@ function LibraryLastPlayed(props) {
<h1>Last Watched</h1>
<div className="last-played-container">
{data.map((item) => (
<ItemCardInfo data={item} base_url={config.hostUrl} key={item.Id+item.EpisodeNumber}/>
<ItemCardInfo data={item} base_url={config.hostUrl} key={item.Id+item.SeasonNumber+item.EpisodeNumber}/>
))}
</div>

View File

@@ -21,23 +21,39 @@ function RecentlyPlayed(props) {
console.log(error);
}
};
const fetchAdmin = async () => {
try {
let url=`/api/getAdminUsers`;
const adminData = await axios.get(url, {
headers: {
Authorization: `Bearer ${config.token}`,
"Content-Type": "application/json",
},
});
return adminData.data[0].Id;
// setData(itemData.data);
} catch (error) {
console.log(error);
}
};
const fetchData = async () => {
try {
let url=`${config.hostUrl}/users/5f63950a2339462196eb8cead70cae7e/Items/latest?parentId=${props.LibraryId}`;
let adminId=await fetchAdmin();
let url=`${config.hostUrl}/users/${adminId}/Items/latest?parentId=${props.LibraryId}`;
const itemData = await axios.get(url, {
headers: {
"X-MediaBrowser-Token": config.apiKey,
},
});
console.log(itemData);
setData(itemData.data);
} catch (error) {
console.log(error);
}
};
if (!data) {
if (!data && config) {
fetchData();
}
@@ -49,7 +65,6 @@ function RecentlyPlayed(props) {
return () => clearInterval(intervalId);
}, [data,config, props.LibraryId]);
console.log(data);
if (!data || !config) {
return <></>;

View File

@@ -5,6 +5,8 @@ import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon";
import PlayFillIcon from "remixicon-react/PlayFillIcon";
import PauseFillIcon from "remixicon-react/PauseFillIcon";
import { clientData } from "../../../lib/devices";
function ticksToTimeString(ticks) {
// Convert ticks to seconds
const seconds = Math.floor(ticks / 10000000);
@@ -37,24 +39,18 @@ function sessionCard(props) {
src={
props.data.base_url +
"/web/assets/img/devices/" +
(props.data.session.DeviceName.toLowerCase().includes("ios") ||
props.data.session.Client.toLowerCase().includes("ios")
? "ios"
: props.data.session.DeviceName.toLowerCase().includes(
"android"
) ||
props.data.session.Client.toLowerCase().includes("android")
? "android"
: props.data.session.DeviceName.replace(
" ",
""
).toLowerCase()) +
+
(props.data.session.Client.toLowerCase().includes("web") ?
( clientData.find(item => props.data.session.DeviceName.toLowerCase().includes(item)) || "other")
:
( clientData.find(item => props.data.session.Client.toLowerCase().includes(item)) || "other")
)
+
".svg"
}
alt=""
></img>
<div className="card-device-name">
{" "}
{props.data.session.DeviceName}
</div>
<div className="card-client">
@@ -133,19 +129,14 @@ function sessionCard(props) {
className="card-device-image"
src={
props.data.base_url +
"/web/assets/img/devices/" +
(props.data.session.DeviceName.toLowerCase().includes("ios") ||
props.data.session.Client.toLowerCase().includes("ios")
? "ios"
: props.data.session.DeviceName.toLowerCase().includes(
"android"
) ||
props.data.session.Client.toLowerCase().includes("android")
? "android"
: props.data.session.DeviceName.replace(
" ",
""
).toLowerCase()) +
"/web/assets/img/devices/"
+
(props.data.session.Client.toLowerCase().includes("web") ?
( clientData.find(item => props.data.session.DeviceName.toLowerCase().includes(item)) || "other")
:
( clientData.find(item => props.data.session.Client.toLowerCase().includes(item)) || "other")
)
+
".svg"
}
alt=""

View File

@@ -50,7 +50,7 @@ function DailyPlayStats(props) {
if (data.length === 0) {
return (
<div className="statistics-widget">
<div className="main-widget">
<h1>Daily Play Count Per Library - {days} Days</h1>
<h1>No Stats to display</h1>
@@ -59,17 +59,17 @@ function DailyPlayStats(props) {
}
return (
<div className="statistics-widget">
<h1>Daily Play Count Per Library - {days} Days</h1>
<div className="main-widget">
<h1>Daily Play Count Per Library - Last {days} Days</h1>
<div className="graph">
<ResponsiveLine
data={data}
margin={{ top: 50, right: 100, bottom: 200, left: 50 }}
margin={{ top: 50, right: 40, bottom: 100, left: 50 }}
xScale={{ type: "point" }}
yScale={{
type: "linear",
min: "auto",
min: 0,
max: "auto",
stacked: false,
reverse: false,

View File

@@ -51,7 +51,7 @@ function PlayStatsByDay(props) {
if (data.length === 0) {
return (
<div className="statistics-widget small">
<h1>Play Count By Day - {days} Days</h1>
<h1>Play Count By Day - Last {days} Days</h1>
<h1>No Stats to display</h1>
</div>
@@ -60,15 +60,15 @@ function PlayStatsByDay(props) {
return (
<div className="statistics-widget">
<h1>Play Count By Day - {days} Days</h1>
<h1>Play Count By Day - Last {days} Days</h1>
<div className="graph small">
<ResponsiveLine
data={data}
margin={{ top: 50, right: 100, bottom: 200, left: 50 }}
margin={{ top: 50, right: 40, bottom: 100, left: 50 }}
xScale={{ type: "point" }}
yScale={{
type: "linear",
min: "auto",
min: 0,
max: "auto",
stacked: false,
reverse: false,
@@ -101,7 +101,7 @@ function PlayStatsByDay(props) {
orient: "bottom",
tickSize: 5,
tickPadding: 10,
tickRotation: -45,
tickRotation: 0,
legend: "Days",
legendOffset: 36,
legendPosition: "middle",

View File

@@ -51,7 +51,7 @@ function PlayStatsByHour(props) {
if (data.length === 0) {
return (
<div className="statistics-widget small">
<h1>Play Count By Hour - {days} Days</h1>
<h1>Play Count By Hour - Last {days} Days</h1>
<h1>No Stats to display</h1>
</div>
@@ -62,15 +62,15 @@ function PlayStatsByHour(props) {
return (
<div className="statistics-widget">
<h1>Play Count By Hour - {days} Days</h1>
<h1>Play Count By Hour - Last {days} Days</h1>
<div className="graph small">
<ResponsiveLine
data={data}
margin={{ top: 50, right: 100, bottom: 200, left: 50 }}
margin={{ top: 50, right: 40, bottom: 100, left: 50 }}
xScale={{ type: "point" }}
yScale={{
type: "linear",
min: "auto",
min: 0,
max: "auto",
stacked: false,
reverse: false,
@@ -103,7 +103,7 @@ function PlayStatsByHour(props) {
orient: "bottom",
tickSize: 5,
tickPadding: 10,
tickRotation: -45,
tickRotation: 0,
legend: "Days",
legendOffset: 36,
legendPosition: "middle",

View File

@@ -4,7 +4,7 @@
height: 700px;
color:black !important;
background-color:rgba(100,100,100,0.2);
padding:20px;
padding:10px;
border-radius:4px;
/* text-align: center; */
@@ -22,11 +22,36 @@
{
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.main-widget
{
flex: 1;
}
.statistics-widget
{
flex: 1;
max-width: 49.5%;
/* margin-right: 20px; */
}
.chart-canvas {
width: 100%;
height: 400px;
}
@media (min-width: 768px) {
.chart-canvas {
height: 500px;
}
}
@media (min-width: 992px) {
.chart-canvas {
height: 600px;
}
}

View File

@@ -9,7 +9,7 @@ import PlayStatsByHour from "./components/statistics/play-stats-by-hour";
function Statistics() {
const [days, setDays] = useState(60);
const [input, setInput] = useState(60);
const [input, setInput] = useState(60);
const handleKeyDown = (event) => {
if (event.key === "Enter") {