mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
users page + db auto create(WIP)
Created users info page when viewing user data on user tab Added DB Procs to retrieve data for those components Added db auto initialize to the backend in preparation for docker images, this needs work as it still crashes if it cant connect to the db
This commit is contained in:
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
FROM node:14-alpine
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY ./ ./
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["npm", "run", "start-app"]
|
||||||
|
|
||||||
106
backend/api.js
106
backend/api.js
@@ -1,6 +1,6 @@
|
|||||||
// api.js
|
// api.js
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
// const pgp = require("pg-promise")();
|
const ActivityMonitor=require('./watchdog/ActivityMonitor');
|
||||||
const db = require("./db");
|
const db = require("./db");
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@@ -11,61 +11,93 @@ router.get("/test", async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get("/getconfig", async (req, res) => {
|
router.get("/getconfig", async (req, res) => {
|
||||||
const { rows } = await db.query('SELECT * FROM app_config where "ID"=1');
|
try{
|
||||||
// console.log(`ENDPOINT CALLED: /getconfig: ` + rows);
|
const { rows } = await db.query('SELECT * FROM app_config where "ID"=1');
|
||||||
// console.log(`ENDPOINT CALLED: /setconfig: `+rows.length);
|
res.send(rows);
|
||||||
res.send(rows);
|
|
||||||
|
}catch(error)
|
||||||
|
{
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/setconfig", async (req, res) => {
|
router.post("/setconfig", async (req, res) => {
|
||||||
const { JF_HOST, JF_API_KEY } = req.body;
|
try{
|
||||||
|
const { JF_HOST, JF_API_KEY } = req.body;
|
||||||
|
|
||||||
const { rows:getConfig } = await db.query('SELECT * FROM app_config where "ID"=1');
|
const { rows:getConfig } = await db.query('SELECT * FROM app_config where "ID"=1');
|
||||||
|
|
||||||
let query='UPDATE app_config SET "JF_HOST"=$1, "JF_API_KEY"=$2 where "ID"=1';
|
let query='UPDATE app_config SET "JF_HOST"=$1, "JF_API_KEY"=$2 where "ID"=1';
|
||||||
if(getConfig.length===0)
|
if(getConfig.length===0)
|
||||||
|
{
|
||||||
|
query='INSERT INTO app_config ("JF_HOST","JF_API_KEY","APP_USER","APP_PASSWORD") VALUES ($1,$2,null,null)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const { rows } = await db.query(
|
||||||
|
query,
|
||||||
|
[JF_HOST, JF_API_KEY]
|
||||||
|
);
|
||||||
|
console.log({ JF_HOST: JF_HOST, JF_API_KEY: JF_API_KEY });
|
||||||
|
res.send(rows);
|
||||||
|
}catch(error)
|
||||||
{
|
{
|
||||||
query='INSERT INTO app_config ("JF_HOST","JF_API_KEY","APP_USER","APP_PASSWORD") VALUES ($1,$2,null,null)';
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const { rows } = await db.query(
|
|
||||||
query,
|
|
||||||
[JF_HOST, JF_API_KEY]
|
|
||||||
);
|
|
||||||
console.log({ JF_HOST: JF_HOST, JF_API_KEY: JF_API_KEY });
|
|
||||||
res.send(rows);
|
|
||||||
|
|
||||||
console.log(`ENDPOINT CALLED: /setconfig: `);
|
console.log(`ENDPOINT CALLED: /setconfig: `);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/getAllFromJellyfin", async (req, res) => {
|
// router.get("/getAllFromJellyfin", async (req, res) => {
|
||||||
const sync = require("./sync");
|
// const sync = require("./sync");
|
||||||
const { rows } = await db.query('SELECT * FROM app_config where "ID"=1');
|
// const { rows } = await db.query('SELECT * FROM app_config where "ID"=1');
|
||||||
if (rows[0].JF_HOST === null || rows[0].JF_API_KEY === null) {
|
// if (rows[0].JF_HOST === null || rows[0].JF_API_KEY === null) {
|
||||||
res.send({ error: "Config Details Not Found" });
|
// res.send({ error: "Config Details Not Found" });
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const _sync = new sync(rows[0].JF_HOST, rows[0].JF_API_KEY);
|
// const _sync = new sync(rows[0].JF_HOST, rows[0].JF_API_KEY);
|
||||||
const results = await _sync.getAllItems();
|
// const results = await _sync.getAllItems();
|
||||||
|
|
||||||
res.send(results);
|
// res.send(results);
|
||||||
|
|
||||||
// console.log(`ENDPOINT CALLED: /getAllFromJellyfin: `);
|
// // console.log(`ENDPOINT CALLED: /getAllFromJellyfin: `);
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
|
||||||
router.post("/getLibraryItems", async (req, res) => {
|
router.post("/getLibraryItems", async (req, res) => {
|
||||||
const Id = req.headers['id'];
|
try{
|
||||||
|
const Id = req.headers['id'];
|
||||||
|
|
||||||
const { rows } = await db.query(
|
const { rows } = await db.query(
|
||||||
`SELECT * FROM jf_library_items where "ParentId"='${Id}'`
|
`SELECT * FROM jf_library_items where "ParentId"='${Id}'`
|
||||||
);
|
);
|
||||||
console.log({ Id: Id });
|
console.log({ Id: Id });
|
||||||
res.send(rows);
|
res.send(rows);
|
||||||
|
|
||||||
|
|
||||||
|
}catch(error)
|
||||||
|
{
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`ENDPOINT CALLED: /getLibraryItems: `);
|
console.log(`ENDPOINT CALLED: /getLibraryItems: `);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get("/runWatchdog", async (req, res) => {
|
||||||
|
let message='Watchdog Started';
|
||||||
|
if(!process.env.WatchdogRunning )
|
||||||
|
{
|
||||||
|
ActivityMonitor.startWatchdog(1000);
|
||||||
|
console.log(message);
|
||||||
|
res.send(message);
|
||||||
|
}else{
|
||||||
|
message=`Watchdog Already Running`;
|
||||||
|
console.log(message);
|
||||||
|
res.send(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,15 +1,87 @@
|
|||||||
// db.js
|
// db.js
|
||||||
const { Pool } = require('pg');
|
const { Pool } = require('pg');
|
||||||
|
const fs = require('fs');
|
||||||
const pgp = require("pg-promise")();
|
const pgp = require("pg-promise")();
|
||||||
|
|
||||||
|
|
||||||
|
const _POSTGRES_USER=process.env.POSTGRES_USER;
|
||||||
|
const _POSTGRES_PASSWORD = process.env.POSTGRES_PASSWORD;
|
||||||
|
const _POSTGRES_IP=process.env.POSTGRES_IP;
|
||||||
|
const _POSTGRES_PORT = process.env.POSTGRES_PORT;
|
||||||
|
|
||||||
|
if([_POSTGRES_USER,_POSTGRES_PASSWORD,_POSTGRES_IP,_POSTGRES_PORT].includes(undefined))
|
||||||
|
{
|
||||||
|
console.log('Postgres details not defined');
|
||||||
|
//return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const development=true;
|
||||||
|
const _DEV_USER='jfstat';
|
||||||
|
const _DEV_PASSWORD = '123456';
|
||||||
|
const _DEV_IP='10.0.0.99';
|
||||||
|
const _DEV_PORT = 32778;
|
||||||
|
|
||||||
const pool = new Pool({
|
const pool = new Pool({
|
||||||
user: 'jfstat',
|
user: (development ? _DEV_USER: _POSTGRES_USER),
|
||||||
host: '10.0.0.99',
|
host:(development ? _DEV_IP: _POSTGRES_IP),
|
||||||
database: 'jfstat',
|
database: 'jfstat',
|
||||||
password: '123456',
|
password:(development ? _DEV_PASSWORD: _POSTGRES_PASSWORD),
|
||||||
port: 32778, // or your PostgreSQL port number
|
port: (development ? _DEV_PORT: _POSTGRES_PORT),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pool.on('error', (err, client) => {
|
||||||
|
console.error('Unexpected error on idle client', err);
|
||||||
|
return;
|
||||||
|
//process.exit(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function initDB()
|
||||||
|
{
|
||||||
|
const checkPool = new Pool({
|
||||||
|
user: (development ? _DEV_USER: _POSTGRES_USER),
|
||||||
|
host:(development ? _DEV_IP: _POSTGRES_IP),
|
||||||
|
database: 'postgres',
|
||||||
|
password:(development ? _DEV_PASSWORD: _POSTGRES_PASSWORD),
|
||||||
|
port: (development ? _DEV_PORT: _POSTGRES_PORT),
|
||||||
|
});
|
||||||
|
|
||||||
|
checkPool.connect((err, client, done) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error connecting to PostgreSQL database', err.stack);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.query("SELECT 1 FROM pg_database WHERE datname = 'jfstat'", (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error executing query to check if database exists', err.stack);
|
||||||
|
//return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.rows.length === 0) {
|
||||||
|
const dbName = 'jfstat';
|
||||||
|
const sql = fs.readFileSync('./init.sql').toString();
|
||||||
|
client.query(`CREATE DATABASE ${dbName}`, (err, res) => {
|
||||||
|
if (err) throw err;
|
||||||
|
console.log(`Database ${dbName} created`);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
checkPool.end(() => {
|
||||||
|
pool.query(sql, (err, res) => {
|
||||||
|
if (err) throw err;
|
||||||
|
console.log('Database and table created');
|
||||||
|
pool.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('Database exists');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
async function deleteBulk(table_name, data) {
|
async function deleteBulk(table_name, data) {
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
let result='SUCCESS';
|
let result='SUCCESS';
|
||||||
@@ -75,8 +147,19 @@ async function insertBulk(table_name, data,columns) {
|
|||||||
return ({Result:result,message:message});
|
return ({Result:result,message:message});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function query(text, params) {
|
||||||
|
try {
|
||||||
|
const result = await pool.query(text, params);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error occurred while executing query:', error);
|
||||||
|
// throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
query: (text, params) => pool.query(text, params),
|
query:query,
|
||||||
deleteBulk: deleteBulk,
|
deleteBulk: deleteBulk,
|
||||||
insertBulk: insertBulk,
|
insertBulk: insertBulk,
|
||||||
|
initDB: initDB,
|
||||||
};
|
};
|
||||||
|
|||||||
593
backend/init.sql
Normal file
593
backend/init.sql
Normal file
@@ -0,0 +1,593 @@
|
|||||||
|
--
|
||||||
|
-- PostgreSQL database dump
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Dumped from database version 15.2 (Debian 15.2-1.pgdg110+1)
|
||||||
|
-- Dumped by pg_dump version 15.1
|
||||||
|
|
||||||
|
-- Started on 2023-03-23 20:33:40 UTC
|
||||||
|
|
||||||
|
SET statement_timeout = 0;
|
||||||
|
SET lock_timeout = 0;
|
||||||
|
SET idle_in_transaction_session_timeout = 0;
|
||||||
|
SET client_encoding = 'UTF8';
|
||||||
|
SET standard_conforming_strings = on;
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
SET check_function_bodies = false;
|
||||||
|
SET xmloption = content;
|
||||||
|
SET client_min_messages = warning;
|
||||||
|
SET row_security = off;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 232 (class 1255 OID 41783)
|
||||||
|
-- Name: fs_most_active_user(integer); Type: FUNCTION; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.fs_most_active_user(days integer) RETURNS TABLE("Plays" bigint, "UserId" text, "Name" text)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT count(*) AS "Plays",
|
||||||
|
jf_playback_activity."UserId",
|
||||||
|
jf_playback_activity."UserName" AS "Name"
|
||||||
|
FROM jf_playback_activity
|
||||||
|
WHERE jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - MAKE_INTERVAL(days => days) AND NOW()
|
||||||
|
GROUP BY jf_playback_activity."UserId", jf_playback_activity."UserName"
|
||||||
|
ORDER BY (count(*)) DESC;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER FUNCTION public.fs_most_active_user(days integer) OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 247 (class 1255 OID 41695)
|
||||||
|
-- Name: fs_most_played_items(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.fs_most_played_items(days integer, itemtype text) RETURNS TABLE("Plays" bigint, total_playback_duration numeric, "Name" text, "Id" text)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT
|
||||||
|
t.plays,
|
||||||
|
t.total_playback_duration,
|
||||||
|
i."Name",
|
||||||
|
i."Id"
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
count(*) AS plays,
|
||||||
|
sum(jf_playback_activity."PlaybackDuration") AS total_playback_duration,
|
||||||
|
jf_playback_activity."NowPlayingItemId"
|
||||||
|
FROM
|
||||||
|
jf_playback_activity
|
||||||
|
WHERE
|
||||||
|
jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - MAKE_INTERVAL(days => days) and NOW()
|
||||||
|
GROUP BY
|
||||||
|
jf_playback_activity."NowPlayingItemId"
|
||||||
|
ORDER BY
|
||||||
|
count(*) DESC
|
||||||
|
) t
|
||||||
|
JOIN jf_library_items i
|
||||||
|
ON t."NowPlayingItemId" = i."Id"
|
||||||
|
AND i."Type" = itemType
|
||||||
|
ORDER BY
|
||||||
|
t.plays DESC;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER FUNCTION public.fs_most_played_items(days integer, itemtype text) OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 246 (class 1255 OID 41690)
|
||||||
|
-- Name: fs_most_popular_items(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.fs_most_popular_items(days integer, itemtype text) RETURNS TABLE(unique_viewers bigint, latest_activity_date timestamp with time zone, "Name" text, "Id" text)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT
|
||||||
|
t.unique_viewers,
|
||||||
|
t.latest_activity_date,
|
||||||
|
i."Name",
|
||||||
|
i."Id"
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
jf_playback_activity."NowPlayingItemId",
|
||||||
|
count(DISTINCT jf_playback_activity."UserId") AS unique_viewers,
|
||||||
|
latest_activity_date.latest_date AS latest_activity_date
|
||||||
|
FROM
|
||||||
|
jf_playback_activity
|
||||||
|
JOIN (
|
||||||
|
SELECT
|
||||||
|
jf_playback_activity_1."NowPlayingItemId",
|
||||||
|
max(jf_playback_activity_1."ActivityDateInserted") AS latest_date
|
||||||
|
FROM
|
||||||
|
jf_playback_activity jf_playback_activity_1
|
||||||
|
GROUP BY jf_playback_activity_1."NowPlayingItemId"
|
||||||
|
) latest_activity_date
|
||||||
|
ON jf_playback_activity."NowPlayingItemId" = latest_activity_date."NowPlayingItemId"
|
||||||
|
WHERE
|
||||||
|
jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - MAKE_INTERVAL(days => days) and NOW()
|
||||||
|
GROUP BY
|
||||||
|
jf_playback_activity."NowPlayingItemId", latest_activity_date.latest_date
|
||||||
|
) t
|
||||||
|
JOIN jf_library_items i
|
||||||
|
ON t."NowPlayingItemId" = i."Id"
|
||||||
|
AND i."Type" = itemType
|
||||||
|
ORDER BY
|
||||||
|
t.unique_viewers DESC, t.latest_activity_date DESC;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER FUNCTION public.fs_most_popular_items(days integer, itemtype text) OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 231 (class 1255 OID 41730)
|
||||||
|
-- Name: fs_most_used_clients(integer); Type: FUNCTION; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.fs_most_used_clients(days integer) RETURNS TABLE("Plays" bigint, "Client" text)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT count(*) AS "Plays",
|
||||||
|
jf_playback_activity."Client"
|
||||||
|
FROM jf_playback_activity
|
||||||
|
WHERE jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - MAKE_INTERVAL(days => days) AND NOW()
|
||||||
|
GROUP BY jf_playback_activity."Client"
|
||||||
|
ORDER BY (count(*)) DESC;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER FUNCTION public.fs_most_used_clients(days integer) OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 245 (class 1255 OID 41701)
|
||||||
|
-- Name: fs_most_viewed_libraries(integer); Type: FUNCTION; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.fs_most_viewed_libraries(days integer) RETURNS TABLE("Plays" numeric, "Id" text, "Name" text, "ServerId" text, "IsFolder" boolean, "Type" text, "CollectionType" text, "ImageTagsPrimary" text)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT
|
||||||
|
sum(t."Plays"),
|
||||||
|
l."Id",
|
||||||
|
l."Name",
|
||||||
|
l."ServerId",
|
||||||
|
l."IsFolder",
|
||||||
|
l."Type",
|
||||||
|
l."CollectionType",
|
||||||
|
l."ImageTagsPrimary"
|
||||||
|
FROM (
|
||||||
|
SELECT count(*) AS "Plays",
|
||||||
|
sum(jf_playback_activity."PlaybackDuration") AS "TotalPlaybackDuration",
|
||||||
|
jf_playback_activity."NowPlayingItemId"
|
||||||
|
FROM jf_playback_activity
|
||||||
|
WHERE
|
||||||
|
jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - MAKE_INTERVAL(days => days) and NOW()
|
||||||
|
|
||||||
|
GROUP BY jf_playback_activity."NowPlayingItemId"
|
||||||
|
ORDER BY "Plays" DESC
|
||||||
|
) t
|
||||||
|
JOIN jf_library_items i
|
||||||
|
ON i."Id" = t."NowPlayingItemId"
|
||||||
|
JOIN jf_libraries l
|
||||||
|
ON l."Id" = i."ParentId"
|
||||||
|
GROUP BY
|
||||||
|
l."Id"
|
||||||
|
ORDER BY
|
||||||
|
(sum( t."Plays")) DESC;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER FUNCTION public.fs_most_viewed_libraries(days integer) OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 244 (class 1255 OID 49350)
|
||||||
|
-- Name: fs_user_stats(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE FUNCTION public.fs_user_stats(days integer, userid text) RETURNS TABLE("Plays" bigint, total_playback_duration numeric, "UserId" text, "Name" text)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT count(*) AS "Plays",
|
||||||
|
sum(jf_playback_activity."PlaybackDuration") AS total_playback_duration,
|
||||||
|
jf_playback_activity."UserId",
|
||||||
|
jf_playback_activity."UserName" AS "Name"
|
||||||
|
FROM jf_playback_activity
|
||||||
|
WHERE jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - MAKE_INTERVAL(days => days) AND NOW()
|
||||||
|
and jf_playback_activity."UserId"=userid
|
||||||
|
GROUP BY jf_playback_activity."UserId", jf_playback_activity."UserName"
|
||||||
|
ORDER BY (count(*)) DESC;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
ALTER FUNCTION public.fs_user_stats(days integer, userid text) OWNER TO postgres;
|
||||||
|
|
||||||
|
SET default_tablespace = '';
|
||||||
|
|
||||||
|
SET default_table_access_method = heap;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 220 (class 1259 OID 16395)
|
||||||
|
-- Name: app_config; Type: TABLE; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.app_config (
|
||||||
|
"ID" integer NOT NULL,
|
||||||
|
"JF_HOST" text,
|
||||||
|
"JF_API_KEY" text,
|
||||||
|
"APP_USER" text,
|
||||||
|
"APP_PASSWORD" text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE public.app_config OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 221 (class 1259 OID 16402)
|
||||||
|
-- Name: app_config_ID_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE public.app_config ALTER COLUMN "ID" ADD GENERATED ALWAYS AS IDENTITY (
|
||||||
|
SEQUENCE NAME public."app_config_ID_seq"
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 228 (class 1259 OID 41300)
|
||||||
|
-- Name: jf_activity_watchdog; Type: TABLE; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.jf_activity_watchdog (
|
||||||
|
"Id" text NOT NULL,
|
||||||
|
"IsPaused" boolean DEFAULT false,
|
||||||
|
"UserId" text,
|
||||||
|
"UserName" text,
|
||||||
|
"Client" text,
|
||||||
|
"DeviceName" text,
|
||||||
|
"DeviceId" text,
|
||||||
|
"ApplicationVersion" text,
|
||||||
|
"NowPlayingItemId" text,
|
||||||
|
"NowPlayingItemName" text,
|
||||||
|
"SeasonId" text,
|
||||||
|
"SeriesName" text,
|
||||||
|
"EpisodeId" text,
|
||||||
|
"PlaybackDuration" bigint,
|
||||||
|
"ActivityDateInserted" timestamp with time zone,
|
||||||
|
"PlayMethod" text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE public.jf_activity_watchdog OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 227 (class 1259 OID 41294)
|
||||||
|
-- Name: jf_playback_activity; Type: TABLE; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.jf_playback_activity (
|
||||||
|
"Id" text NOT NULL,
|
||||||
|
"IsPaused" boolean DEFAULT false,
|
||||||
|
"UserId" text,
|
||||||
|
"UserName" text,
|
||||||
|
"Client" text,
|
||||||
|
"DeviceName" text,
|
||||||
|
"DeviceId" text,
|
||||||
|
"ApplicationVersion" text,
|
||||||
|
"NowPlayingItemId" text,
|
||||||
|
"NowPlayingItemName" text,
|
||||||
|
"SeasonId" text,
|
||||||
|
"SeriesName" text,
|
||||||
|
"EpisodeId" text,
|
||||||
|
"PlaybackDuration" bigint,
|
||||||
|
"ActivityDateInserted" timestamp with time zone,
|
||||||
|
"PlayMethod" text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE public.jf_playback_activity OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 229 (class 1259 OID 41731)
|
||||||
|
-- Name: jf_users; Type: TABLE; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.jf_users (
|
||||||
|
"Id" text NOT NULL,
|
||||||
|
"Name" text,
|
||||||
|
"PrimaryImageTag" text,
|
||||||
|
"LastLoginDate" timestamp with time zone,
|
||||||
|
"LastActivityDate" timestamp with time zone,
|
||||||
|
"IsAdministrator" boolean
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE public.jf_users OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 230 (class 1259 OID 41771)
|
||||||
|
-- Name: jf_all_user_activity; Type: VIEW; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE VIEW public.jf_all_user_activity AS
|
||||||
|
SELECT u."Id" AS "UserId",
|
||||||
|
u."PrimaryImageTag",
|
||||||
|
u."Name" AS "UserName",
|
||||||
|
CASE
|
||||||
|
WHEN (j."SeriesName" IS NULL) THEN j."NowPlayingItemName"
|
||||||
|
ELSE ((j."SeriesName" || ' - '::text) || j."NowPlayingItemName")
|
||||||
|
END AS "LastWatched",
|
||||||
|
j."ActivityDateInserted" AS "LastActivityDate",
|
||||||
|
((j."Client" || ' - '::text) || j."DeviceName") AS "LastClient",
|
||||||
|
plays."TotalPlays",
|
||||||
|
plays."TotalWatchTime",
|
||||||
|
(now() - j."ActivityDateInserted") AS "LastSeen"
|
||||||
|
FROM ((( SELECT jf_users."Id",
|
||||||
|
jf_users."Name",
|
||||||
|
jf_users."PrimaryImageTag",
|
||||||
|
jf_users."LastLoginDate",
|
||||||
|
jf_users."LastActivityDate",
|
||||||
|
jf_users."IsAdministrator"
|
||||||
|
FROM public.jf_users) u
|
||||||
|
LEFT JOIN LATERAL ( 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"
|
||||||
|
FROM public.jf_playback_activity
|
||||||
|
WHERE (jf_playback_activity."UserId" = u."Id")
|
||||||
|
ORDER BY jf_playback_activity."ActivityDateInserted" DESC
|
||||||
|
LIMIT 1) j ON (true))
|
||||||
|
LEFT JOIN LATERAL ( SELECT count(*) AS "TotalPlays",
|
||||||
|
sum(jf_playback_activity."PlaybackDuration") AS "TotalWatchTime"
|
||||||
|
FROM public.jf_playback_activity
|
||||||
|
WHERE (jf_playback_activity."UserId" = u."Id")) plays ON (true))
|
||||||
|
ORDER BY j."ActivityDateInserted";
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE public.jf_all_user_activity OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 222 (class 1259 OID 16411)
|
||||||
|
-- Name: jf_libraries; Type: TABLE; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.jf_libraries (
|
||||||
|
"Id" text NOT NULL,
|
||||||
|
"Name" text NOT NULL,
|
||||||
|
"ServerId" text,
|
||||||
|
"IsFolder" boolean DEFAULT true NOT NULL,
|
||||||
|
"Type" text DEFAULT 'CollectionFolder'::text NOT NULL,
|
||||||
|
"CollectionType" text NOT NULL,
|
||||||
|
"ImageTagsPrimary" text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE public.jf_libraries OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 226 (class 1259 OID 25160)
|
||||||
|
-- Name: jf_library_count_view; Type: VIEW; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE VIEW public.jf_library_count_view AS
|
||||||
|
SELECT
|
||||||
|
NULL::text AS "Id",
|
||||||
|
NULL::text AS "Name",
|
||||||
|
NULL::text AS "CollectionType",
|
||||||
|
NULL::bigint AS "Library_Count",
|
||||||
|
NULL::bigint AS "Season_Count",
|
||||||
|
NULL::bigint AS "Episode_Count";
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE public.jf_library_count_view OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 225 (class 1259 OID 24906)
|
||||||
|
-- Name: jf_library_episodes; Type: TABLE; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.jf_library_episodes (
|
||||||
|
"Id" text NOT NULL,
|
||||||
|
"EpisodeId" text NOT NULL,
|
||||||
|
"Name" text,
|
||||||
|
"ServerId" text,
|
||||||
|
"PremiereDate" timestamp with time zone,
|
||||||
|
"OfficialRating" text,
|
||||||
|
"CommunityRating" double precision,
|
||||||
|
"RunTimeTicks" bigint,
|
||||||
|
"ProductionYear" integer,
|
||||||
|
"IndexNumber" integer,
|
||||||
|
"ParentIndexNumber" integer,
|
||||||
|
"Type" text,
|
||||||
|
"ParentLogoItemId" text,
|
||||||
|
"ParentBackdropItemId" text,
|
||||||
|
"ParentBackdropImageTags" text,
|
||||||
|
"SeriesId" text,
|
||||||
|
"SeasonId" text,
|
||||||
|
"SeasonName" text,
|
||||||
|
"SeriesName" text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE public.jf_library_episodes OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 223 (class 1259 OID 24599)
|
||||||
|
-- Name: jf_library_items; Type: TABLE; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.jf_library_items (
|
||||||
|
"Id" text NOT NULL,
|
||||||
|
"Name" text NOT NULL,
|
||||||
|
"ServerId" text,
|
||||||
|
"PremiereDate" timestamp with time zone,
|
||||||
|
"EndDate" timestamp with time zone,
|
||||||
|
"CommunityRating" double precision,
|
||||||
|
"RunTimeTicks" bigint,
|
||||||
|
"ProductionYear" integer,
|
||||||
|
"IsFolder" boolean,
|
||||||
|
"Type" text,
|
||||||
|
"Status" text,
|
||||||
|
"ImageTagsPrimary" text,
|
||||||
|
"ImageTagsBanner" text,
|
||||||
|
"ImageTagsLogo" text,
|
||||||
|
"ImageTagsThumb" text,
|
||||||
|
"BackdropImageTags" text,
|
||||||
|
"ParentId" text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE public.jf_library_items OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 224 (class 1259 OID 24731)
|
||||||
|
-- Name: jf_library_seasons; Type: TABLE; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.jf_library_seasons (
|
||||||
|
"Id" text NOT NULL,
|
||||||
|
"Name" text,
|
||||||
|
"ServerId" text,
|
||||||
|
"IndexNumber" integer,
|
||||||
|
"Type" text,
|
||||||
|
"ParentLogoItemId" text,
|
||||||
|
"ParentBackdropItemId" text,
|
||||||
|
"ParentBackdropImageTags" text,
|
||||||
|
"SeriesName" text,
|
||||||
|
"SeriesId" text,
|
||||||
|
"SeriesPrimaryImageTag" text
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE public.jf_library_seasons OWNER TO postgres;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 3229 (class 2606 OID 16401)
|
||||||
|
-- Name: app_config app_config_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.app_config
|
||||||
|
ADD CONSTRAINT app_config_pkey PRIMARY KEY ("ID");
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 3231 (class 2606 OID 16419)
|
||||||
|
-- Name: jf_libraries jf_libraries_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.jf_libraries
|
||||||
|
ADD CONSTRAINT jf_libraries_pkey PRIMARY KEY ("Id");
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 3237 (class 2606 OID 24912)
|
||||||
|
-- Name: jf_library_episodes jf_library_episodes_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.jf_library_episodes
|
||||||
|
ADD CONSTRAINT jf_library_episodes_pkey PRIMARY KEY ("Id");
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 3233 (class 2606 OID 24605)
|
||||||
|
-- Name: jf_library_items jf_library_items_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.jf_library_items
|
||||||
|
ADD CONSTRAINT jf_library_items_pkey PRIMARY KEY ("Id");
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 3235 (class 2606 OID 24737)
|
||||||
|
-- Name: jf_library_seasons jf_library_seasons_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.jf_library_seasons
|
||||||
|
ADD CONSTRAINT jf_library_seasons_pkey PRIMARY KEY ("Id");
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 3239 (class 2606 OID 41737)
|
||||||
|
-- Name: jf_users jf_users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.jf_users
|
||||||
|
ADD CONSTRAINT jf_users_pkey PRIMARY KEY ("Id");
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 3383 (class 2618 OID 25163)
|
||||||
|
-- Name: jf_library_count_view _RETURN; Type: RULE; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW public.jf_library_count_view AS
|
||||||
|
SELECT l."Id",
|
||||||
|
l."Name",
|
||||||
|
l."CollectionType",
|
||||||
|
count(DISTINCT i."Id") AS "Library_Count",
|
||||||
|
count(DISTINCT s."Id") AS "Season_Count",
|
||||||
|
count(DISTINCT e."Id") AS "Episode_Count"
|
||||||
|
FROM (((public.jf_libraries l
|
||||||
|
JOIN public.jf_library_items i ON ((i."ParentId" = l."Id")))
|
||||||
|
LEFT JOIN public.jf_library_seasons s ON ((s."SeriesId" = i."Id")))
|
||||||
|
LEFT JOIN public.jf_library_episodes e ON ((e."SeasonId" = s."Id")))
|
||||||
|
GROUP BY l."Id", l."Name"
|
||||||
|
ORDER BY (count(DISTINCT i."Id")) DESC;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 3240 (class 2606 OID 24617)
|
||||||
|
-- Name: jf_library_items jf_library_items_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.jf_library_items
|
||||||
|
ADD CONSTRAINT jf_library_items_fkey FOREIGN KEY ("ParentId") REFERENCES public.jf_libraries("Id") ON DELETE SET NULL NOT VALID;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TOC entry 3390 (class 0 OID 0)
|
||||||
|
-- Dependencies: 3240
|
||||||
|
-- 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-23 20:33:45 UTC
|
||||||
|
|
||||||
|
--
|
||||||
|
-- PostgreSQL database dump complete
|
||||||
|
--
|
||||||
|
|
||||||
@@ -5,7 +5,9 @@ const apiRouter = require('./api');
|
|||||||
const syncRouter = require('./sync');
|
const syncRouter = require('./sync');
|
||||||
const statsRouter = require('./stats');
|
const statsRouter = require('./stats');
|
||||||
const ActivityMonitor=require('./watchdog/ActivityMonitor');
|
const ActivityMonitor=require('./watchdog/ActivityMonitor');
|
||||||
ActivityMonitor.ActivityMonitor(1000);
|
const db = require("./db");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3003;
|
const PORT = process.env.PORT || 3003;
|
||||||
@@ -13,10 +15,19 @@ const LISTEN_IP = '127.0.0.1';
|
|||||||
|
|
||||||
app.use(express.json()); // middleware to parse JSON request bodies
|
app.use(express.json()); // middleware to parse JSON request bodies
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
app.use('/api', apiRouter); // mount the API router at /api
|
app.use('/api', apiRouter); // mount the API router at /api
|
||||||
app.use('/sync', syncRouter); // mount the API router at /sync
|
app.use('/sync', syncRouter); // mount the API router at /sync
|
||||||
app.use('/stats', statsRouter); // mount the API router at /stats
|
app.use('/stats', statsRouter); // mount the API router at /stats
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server listening on http://${LISTEN_IP}:${PORT}`);
|
console.log(`Server listening on http://${LISTEN_IP}:${PORT}`);
|
||||||
|
try{
|
||||||
|
db.initDB();
|
||||||
|
ActivityMonitor.ActivityMonitor(1000);
|
||||||
|
}catch(error)
|
||||||
|
{
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
272
backend/stats.js
272
backend/stats.js
@@ -10,160 +10,208 @@ router.get("/test", async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get("/getLibraryOverview", async (req, res) => {
|
router.get("/getLibraryOverview", async (req, res) => {
|
||||||
const { rows } = await db.query('SELECT * FROM jf_library_count_view');
|
try {
|
||||||
res.send(rows);
|
const { rows } = await db.query("SELECT * FROM jf_library_count_view");
|
||||||
console.log(`ENDPOINT CALLED: /getLibraryOverview`);
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
res.send(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
router.post("/getMostViewedSeries", async (req, res) => {
|
router.post("/getMostViewedSeries", async (req, res) => {
|
||||||
const {days} = req.body;
|
try {
|
||||||
let _days=days;
|
const { days } = req.body;
|
||||||
if(days===undefined)
|
let _days = days;
|
||||||
{
|
if (days === undefined) {
|
||||||
_days=30;
|
_days = 30;
|
||||||
|
}
|
||||||
|
const { rows } = await db.query(
|
||||||
|
`select * from fs_most_played_items(${_days-1},'Series') limit 5`
|
||||||
|
);
|
||||||
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
res.send(error);
|
||||||
}
|
}
|
||||||
const { rows } = await db.query(
|
|
||||||
`select * from fs_most_played_items(${_days},'Series') limit 5`
|
|
||||||
);
|
|
||||||
res.send(rows);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
router.post("/getMostViewedMovies", async (req, res) => {
|
router.post("/getMostViewedMovies", async (req, res) => {
|
||||||
const {days} = req.body;
|
try {
|
||||||
let _days=days;
|
const { days } = req.body;
|
||||||
if(days===undefined)
|
let _days = days;
|
||||||
{
|
if (days === undefined) {
|
||||||
_days=30;
|
_days = 30;
|
||||||
|
}
|
||||||
|
const { rows } = await db.query(
|
||||||
|
`select * from fs_most_played_items(${_days-1},'Movie') limit 5`
|
||||||
|
);
|
||||||
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('/getMostViewedMovies');
|
||||||
|
console.log(error);
|
||||||
|
res.send(error);
|
||||||
}
|
}
|
||||||
const { rows } = await db.query(
|
|
||||||
`select * from fs_most_played_items(${_days},'Movie') limit 5`
|
|
||||||
);
|
|
||||||
res.send(rows);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/getMostViewedMusic", async (req, res) => {
|
router.post("/getMostViewedMusic", async (req, res) => {
|
||||||
const {days} = req.body;
|
try {
|
||||||
let _days=days;
|
const { days } = req.body;
|
||||||
if(days===undefined)
|
let _days = days;
|
||||||
{
|
if (days === undefined) {
|
||||||
_days=30;
|
_days = 30;
|
||||||
|
}
|
||||||
|
const { rows } = await db.query(
|
||||||
|
`select * from fs_most_played_items(${_days-1},'Audio') limit 5`
|
||||||
|
);
|
||||||
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
res.send(error);
|
||||||
}
|
}
|
||||||
const { rows } = await db.query(
|
|
||||||
`select * from fs_most_played_items(${_days},'Audio') limit 5`
|
|
||||||
);
|
|
||||||
res.send(rows);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
router.post("/getMostViewedLibraries", async (req, res) => {
|
router.post("/getMostViewedLibraries", async (req, res) => {
|
||||||
const {days} = req.body;
|
try {
|
||||||
let _days=days;
|
const { days } = req.body;
|
||||||
if(days===undefined)
|
let _days = days;
|
||||||
{
|
if (days === undefined) {
|
||||||
_days=30;
|
_days = 30;
|
||||||
|
}
|
||||||
|
const { rows } = await db.query(
|
||||||
|
`select * from fs_most_viewed_libraries(${_days-1})`
|
||||||
|
);
|
||||||
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
res.send(error);
|
||||||
}
|
}
|
||||||
const { rows } = await db.query(
|
|
||||||
`select * from fs_most_viewed_libraries(${_days})`
|
|
||||||
);
|
|
||||||
res.send(rows);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
router.post("/getMostUsedClient", async (req, res) => {
|
router.post("/getMostUsedClient", async (req, res) => {
|
||||||
const {days} = req.body;
|
try {
|
||||||
let _days=days;
|
const { days } = req.body;
|
||||||
if(days===undefined)
|
let _days = days;
|
||||||
{
|
if (days === undefined) {
|
||||||
_days=30;
|
_days = 30;
|
||||||
|
}
|
||||||
|
const { rows } = await db.query(
|
||||||
|
`select * from fs_most_used_clients(${_days-1}) limit 5`
|
||||||
|
);
|
||||||
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
res.send(error);
|
||||||
}
|
}
|
||||||
const { rows } = await db.query(
|
|
||||||
`select * from fs_most_used_clients(${_days}) limit 5`
|
|
||||||
);
|
|
||||||
res.send(rows);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
router.post("/getMostActiveUsers", async (req, res) => {
|
router.post("/getMostActiveUsers", async (req, res) => {
|
||||||
const {days} = req.body;
|
try {
|
||||||
let _days=days;
|
const { days } = req.body;
|
||||||
if(days===undefined)
|
let _days = days;
|
||||||
{
|
if (days === undefined) {
|
||||||
_days=30;
|
_days = 30;
|
||||||
|
}
|
||||||
|
const { rows } = await db.query(
|
||||||
|
`select * from fs_most_active_user(${_days-1}) limit 5`
|
||||||
|
);
|
||||||
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
res.send(error);
|
||||||
}
|
}
|
||||||
const { rows } = await db.query(
|
|
||||||
`select * from fs_most_active_user(${_days}) limit 5`
|
|
||||||
);
|
|
||||||
res.send(rows);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
router.post("/getMostPopularMovies", async (req, res) => {
|
router.post("/getMostPopularMovies", async (req, res) => {
|
||||||
const {days} = req.body;
|
try {
|
||||||
let _days=days;
|
const { days } = req.body;
|
||||||
if(days===undefined)
|
let _days = days;
|
||||||
{
|
if (days === undefined) {
|
||||||
_days=30;
|
_days = 30;
|
||||||
|
}
|
||||||
|
const { rows } = await db.query(
|
||||||
|
`select * from fs_most_popular_items(${_days-1},'Movie') limit 5`
|
||||||
|
);
|
||||||
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
res.send(error);
|
||||||
}
|
}
|
||||||
const { rows } = await db.query(
|
|
||||||
`select * from fs_most_popular_items(${_days},'Movie') limit 5`
|
|
||||||
);
|
|
||||||
res.send(rows);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
router.post("/getMostPopularSeries", async (req, res) => {
|
router.post("/getMostPopularSeries", async (req, res) => {
|
||||||
const {days} = req.body;
|
try {
|
||||||
let _days=days;
|
const { days } = req.body;
|
||||||
if(days===undefined)
|
let _days = days;
|
||||||
{
|
if (days === undefined) {
|
||||||
_days=30;
|
_days = 30;
|
||||||
|
}
|
||||||
|
const { rows } = await db.query(
|
||||||
|
`select * from fs_most_popular_items(${_days-1},'Series') limit 5`
|
||||||
|
);
|
||||||
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
res.send(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rows } = await db.query(
|
|
||||||
`select * from fs_most_popular_items(${_days},'Series') limit 5`
|
|
||||||
);
|
|
||||||
res.send(rows);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/getMostPopularMusic", async (req, res) => {
|
router.post("/getMostPopularMusic", async (req, res) => {
|
||||||
const {days} = req.body;
|
try {
|
||||||
let _days=days;
|
const { days } = req.body;
|
||||||
if(days===undefined)
|
let _days = days;
|
||||||
{
|
if (days === undefined) {
|
||||||
_days=30;
|
_days = 30;
|
||||||
|
}
|
||||||
|
const { rows } = await db.query(
|
||||||
|
`select * from fs_most_popular_items(${_days-1},'Audio') limit 5`
|
||||||
|
);
|
||||||
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
res.send(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rows } = await db.query(
|
|
||||||
`select * from fs_most_popular_items(${_days},'Audio') limit 5`
|
|
||||||
);
|
|
||||||
res.send(rows);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
router.get("/getPlaybackActivity", async (req, res) => {
|
router.get("/getPlaybackActivity", async (req, res) => {
|
||||||
const { rows } = await db.query('SELECT * FROM jf_playback_activity');
|
try {
|
||||||
res.send(rows);
|
const { rows } = await db.query("SELECT * FROM jf_playback_activity");
|
||||||
// console.log(`ENDPOINT CALLED: /getPlaybackActivity`);
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
res.send(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/getAllUserActivity", async (req, res) => {
|
router.get("/getAllUserActivity", async (req, res) => {
|
||||||
const { rows } = await db.query('SELECT * FROM jf_all_user_activity');
|
try {
|
||||||
res.send(rows);
|
const { rows } = await db.query("SELECT * FROM jf_all_user_activity");
|
||||||
// console.log(`ENDPOINT CALLED: /getPlaybackActivity`);
|
res.send(rows);
|
||||||
|
} catch (error) {
|
||||||
|
res.send(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post("/getUserDetails", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { userid } = req.body;
|
||||||
|
const { rows } = await db.query(
|
||||||
|
`select * from jf_users where "Id"='${userid}'`
|
||||||
|
);
|
||||||
|
res.send(rows[0]);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.send(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/getGlobalUserStats", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { days,userid } = req.body;
|
||||||
|
let _days = days;
|
||||||
|
if (days === undefined) {
|
||||||
|
_days = 1;
|
||||||
|
}
|
||||||
|
console.log(`select * from fs_user_stats(${_days-1},'${userid}')`);
|
||||||
|
const { rows } = await db.query(
|
||||||
|
`select * from fs_user_stats(${_days-1},'${userid}')`
|
||||||
|
);
|
||||||
|
res.send(rows[0]);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.send(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ const { jf_activity_watchdog_columns, jf_activity_watchdog_mapping } = require('
|
|||||||
async function ActivityMonitor(interval) {
|
async function ActivityMonitor(interval) {
|
||||||
console.log("Activity Interval: " + interval);
|
console.log("Activity Interval: " + interval);
|
||||||
|
|
||||||
|
|
||||||
const { rows: config } = await db.query(
|
const { rows: config } = await db.query(
|
||||||
'SELECT * FROM app_config where "ID"=1'
|
'SELECT * FROM app_config where "ID"=1'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
if(config.length===0)
|
if(config.length===0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
20
docker-compose.yml
Normal file
20
docker-compose.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
jellystat-db:
|
||||||
|
image: postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: mypassword
|
||||||
|
jellystat:
|
||||||
|
image: jellystat
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: mypassword
|
||||||
|
POSTGRES_IP: jellystat-db
|
||||||
|
POSTGRES_PORT: 5432
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
depends_on:
|
||||||
|
- jellystat-db
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
@@ -28,6 +28,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
"start-server": "cd backend && node server.js",
|
||||||
|
"start-app": "concurrently \"npm run start-server\" \"npm start\"",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import Navbar from './pages/components/navbar';
|
|||||||
import Home from './pages/home';
|
import Home from './pages/home';
|
||||||
import Settings from './pages/settings';
|
import Settings from './pages/settings';
|
||||||
import Users from './pages/users';
|
import Users from './pages/users';
|
||||||
import UserInfo from './pages/user-info';
|
import UserInfo from './pages/components/user-info';
|
||||||
import Libraries from './pages/libraries';
|
import Libraries from './pages/libraries';
|
||||||
import ErrorPage from './pages/components/error';
|
import ErrorPage from './pages/components/error';
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ function App() {
|
|||||||
const fetchConfig = async () => {
|
const fetchConfig = async () => {
|
||||||
try {
|
try {
|
||||||
const newConfig = await Config();
|
const newConfig = await Config();
|
||||||
if(newConfig !== 'ERR_NETWORK'){
|
if(!newConfig.response){
|
||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
}else{
|
}else{
|
||||||
seterrorFlag(true);
|
seterrorFlag(true);
|
||||||
@@ -45,7 +45,7 @@ function App() {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import axios from 'axios';
|
|||||||
|
|
||||||
async function Config() {
|
async function Config() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('http://10.0.0.20:3003/api/getconfig');
|
const response = await axios.get('/api/getconfig');
|
||||||
const { JF_HOST, JF_API_KEY, APP_USER, APP_PASSWORD } = response.data[0];
|
const { JF_HOST, JF_API_KEY, APP_USER, APP_PASSWORD } = response.data[0];
|
||||||
return { hostUrl: JF_HOST, apiKey: JF_API_KEY, username: APP_USER, password: APP_PASSWORD };
|
return { hostUrl: JF_HOST, apiKey: JF_API_KEY, username: APP_USER, password: APP_PASSWORD };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
// console.log(error);
|
||||||
return error.code;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
import HomeFillIcon from 'remixicon-react/HomeFillIcon';
|
import HomeFillIcon from 'remixicon-react/HomeFillIcon';
|
||||||
import FileListFillIcon from 'remixicon-react/FileListFillIcon';
|
// import FileListFillIcon from 'remixicon-react/FileListFillIcon';
|
||||||
import BarChartFillIcon from 'remixicon-react/BarChartFillIcon';
|
// import BarChartFillIcon from 'remixicon-react/BarChartFillIcon';
|
||||||
import SettingsFillIcon from 'remixicon-react/SettingsFillIcon';
|
import SettingsFillIcon from 'remixicon-react/SettingsFillIcon';
|
||||||
import GalleryFillIcon from 'remixicon-react/GalleryFillIcon';
|
import GalleryFillIcon from 'remixicon-react/GalleryFillIcon';
|
||||||
import UserFillIcon from 'remixicon-react/UserFillIcon';
|
import UserFillIcon from 'remixicon-react/UserFillIcon';
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function WatchStatistics() {
|
|||||||
setInput(1);
|
setInput(1);
|
||||||
setDays(0);
|
setDays(0);
|
||||||
} else {
|
} else {
|
||||||
setDays(parseInt(input) - 1);
|
setDays(parseInt(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(days);
|
console.log(days);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function LibraryStatComponent(props) {
|
|||||||
<div className="library-item" key={item.Id}>
|
<div className="library-item" key={item.Id}>
|
||||||
<p className="library-item-index">{index + 1}</p>
|
<p className="library-item-index">{index + 1}</p>
|
||||||
<p className="library-item-name">{item.Name}</p>
|
<p className="library-item-name">{item.Name}</p>
|
||||||
<p className="library-item-count">{item.CollectionType =='tvshows'? (item.Library_Count+' / '+item.Season_Count+' / '+item.Episode_Count): item.Library_Count}</p>
|
<p className="library-item-count">{item.CollectionType ==='tvshows'? (item.Library_Count+' / '+item.Season_Count+' / '+item.Episode_Count): item.Library_Count}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const TerminalComponent = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// create a new WebSocket connection
|
// create a new WebSocket connection
|
||||||
const socket = new WebSocket('ws://10.0.0.20:8080');
|
const socket = new WebSocket('ws://127.0.0.1:3000/ws');
|
||||||
|
|
||||||
// handle incoming messages
|
// handle incoming messages
|
||||||
socket.addEventListener('message', (event) => {
|
socket.addEventListener('message', (event) => {
|
||||||
|
|||||||
20
src/pages/components/user-info.js
Normal file
20
src/pages/components/user-info.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import GlobalStats from './user-info/globalStats';
|
||||||
|
import UserDetails from './user-info/user-details';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function UserInfo() {
|
||||||
|
const { UserId } = useParams();
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<UserDetails UserId={UserId}/>
|
||||||
|
<GlobalStats UserId={UserId}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default UserInfo;
|
||||||
62
src/pages/components/user-info/globalStats.js
Normal file
62
src/pages/components/user-info/globalStats.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import "../../css/users/globalstats.css";
|
||||||
|
|
||||||
|
import WatchTimeStats from "./globalstats/watchtimestats";
|
||||||
|
|
||||||
|
function GlobalStats(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/getGlobalUserStats`, {
|
||||||
|
days: 1,
|
||||||
|
userid: props.UserId,
|
||||||
|
});
|
||||||
|
setDayStats(dayData.data);
|
||||||
|
|
||||||
|
const weekData = await axios.post(`/stats/getGlobalUserStats`, {
|
||||||
|
days: 7,
|
||||||
|
userid: props.UserId,
|
||||||
|
});
|
||||||
|
setWeekStats(weekData.data);
|
||||||
|
|
||||||
|
const monthData = await axios.post(`/stats/getGlobalUserStats`, {
|
||||||
|
days: 30,
|
||||||
|
userid: props.UserId,
|
||||||
|
});
|
||||||
|
setMonthStats(monthData.data);
|
||||||
|
|
||||||
|
const allData = await axios.post(`/stats/getGlobalUserStats`, {
|
||||||
|
days: 999,
|
||||||
|
userid: props.UserId,
|
||||||
|
});
|
||||||
|
setAllStats(allData.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
const intervalId = setInterval(fetchData, 60000 * 5);
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [props.UserId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>User 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 GlobalStats;
|
||||||
65
src/pages/components/user-info/globalstats/watchtimestats.js
Normal file
65
src/pages/components/user-info/globalstats/watchtimestats.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import "../../../css/users/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;
|
||||||
79
src/pages/components/user-info/user-details.js
Normal file
79
src/pages/components/user-info/user-details.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon";
|
||||||
|
import Config from "../../../lib/config";
|
||||||
|
import "../../css/users/user-details.css";
|
||||||
|
|
||||||
|
function UserDetails(props) {
|
||||||
|
const [data, setData] = useState();
|
||||||
|
const [imgError, setImgError] = useState(false);
|
||||||
|
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 userData = await axios.post(`/stats/getUserDetails`, {
|
||||||
|
userid: props.UserId,
|
||||||
|
});
|
||||||
|
setData(userData.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
fetchConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
const intervalId = setInterval(fetchData, 60000 * 5);
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [data,config, props.UserId]);
|
||||||
|
|
||||||
|
const handleImageError = () => {
|
||||||
|
setImgError(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!data || !config) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="user-detail-container">
|
||||||
|
<div className="user-image-container">
|
||||||
|
{imgError ? (
|
||||||
|
<AccountCircleFillIcon size={"100%"} />
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
className="user-image"
|
||||||
|
src={
|
||||||
|
config.hostUrl +
|
||||||
|
"/Users/" +
|
||||||
|
data.Id +
|
||||||
|
"/Images/Primary?quality=50"
|
||||||
|
}
|
||||||
|
onError={handleImageError}
|
||||||
|
alt=""
|
||||||
|
></img>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="user-name">{data.Name}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserDetails;
|
||||||
@@ -7,9 +7,6 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(520px, 520px));
|
grid-template-columns: repeat(auto-fit, minmax(520px, 520px));
|
||||||
grid-auto-rows: 235px;/* max-width+offset so 215 + 20*/
|
grid-auto-rows: 235px;/* max-width+offset so 215 + 20*/
|
||||||
/* background-color: rgba(0,0,0,0.5); */
|
|
||||||
/* padding: 20px; */
|
|
||||||
/* border-radius: 4px; */
|
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
48
src/pages/css/users/globalstats.css
Normal file
48
src/pages/css/users/globalstats.css
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.global-stats-container
|
||||||
|
{
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(320px, 320px));
|
||||||
|
grid-auto-rows: 120px;
|
||||||
|
background-color: rgb(0, 0, 0,0.2);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-stats
|
||||||
|
{
|
||||||
|
color: white;
|
||||||
|
width: 300px;
|
||||||
|
height: 100px;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-duration-stats
|
||||||
|
{
|
||||||
|
padding-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value
|
||||||
|
{
|
||||||
|
text-align: right;
|
||||||
|
color: #00A4DC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.stat-unit
|
||||||
|
{
|
||||||
|
padding-inline: 5px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-part
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
36
src/pages/css/users/user-details.css
Normal file
36
src/pages/css/users/user-details.css
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
.user-detail-container
|
||||||
|
{
|
||||||
|
color:white;
|
||||||
|
background-color: rgba(0,0,0,0.2);
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name
|
||||||
|
{
|
||||||
|
font-size: 2.5em;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.user-image
|
||||||
|
{
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
box-shadow: 0 0 10px 5px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-image-container
|
||||||
|
{
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
padding-right: 20px;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import axios from "axios";
|
|||||||
import Config from "../lib/config";
|
import Config from "../lib/config";
|
||||||
|
|
||||||
import "./css/libraries.css";
|
import "./css/libraries.css";
|
||||||
import "./css/users.css";
|
import "./css/users/users.css";
|
||||||
|
|
||||||
import Loading from "./components/loading";
|
import Loading from "./components/loading";
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import axios from "axios";
|
|||||||
import Config from "../lib/config";
|
import Config from "../lib/config";
|
||||||
|
|
||||||
import "./css/setup.css";
|
import "./css/setup.css";
|
||||||
|
import LibrarySync from "./components/settings/librarySync";
|
||||||
|
|
||||||
// import Loading from './components/loading';
|
// import Loading from './components/loading';
|
||||||
|
|
||||||
@@ -18,55 +19,9 @@ function Setup() {
|
|||||||
async function beginSync() {
|
async function beginSync() {
|
||||||
|
|
||||||
|
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
|
await LibrarySync.beginSync();
|
||||||
await axios
|
|
||||||
.get("/sync/writeLibraries")
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
// isValid = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get("/sync/writeLibraryItems")
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
// isValid = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get("/sync/writeSeasonsAndEpisodes")
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
// isValid = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
await axios
|
|
||||||
.get("/sync/writeUsers")
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
// isValid = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
// return { isValid: isValid, errorMessage: errorMessage };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validateSettings(_url, _apikey) {
|
async function validateSettings(_url, _apikey) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import './css/libraries.css';
|
|||||||
import Loading from './components/loading';
|
import Loading from './components/loading';
|
||||||
|
|
||||||
// import PlaybackActivity from './components/playbackactivity';
|
// import PlaybackActivity from './components/playbackactivity';
|
||||||
import Activity from './activity';
|
// import Activity from './activity';
|
||||||
// import StatCards from './components/StatsCards';
|
// import StatCards from './components/StatsCards';
|
||||||
|
|
||||||
import LibraryOverView from './components/libraryOverview';
|
import LibraryOverView from './components/libraryOverview';
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import { useParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
function UserInfo() {
|
|
||||||
const { UserId } = useParams();
|
|
||||||
|
|
||||||
// Fetch data for the user with the specified userId
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p>{UserId}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export default UserInfo;
|
|
||||||
@@ -4,7 +4,7 @@ import Config from "../lib/config";
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon";
|
import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon";
|
||||||
|
|
||||||
import "./css/users.css";
|
import "./css/users/users.css";
|
||||||
|
|
||||||
import Loading from "./components/loading";
|
import Loading from "./components/loading";
|
||||||
|
|
||||||
|
|||||||
@@ -4,23 +4,32 @@ module.exports = function(app) {
|
|||||||
app.use(
|
app.use(
|
||||||
'/api',
|
'/api',
|
||||||
createProxyMiddleware({
|
createProxyMiddleware({
|
||||||
target: 'http://localhost:3003',
|
target: 'http://127.0.0.1:3003',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(
|
||||||
'/stats',
|
'/stats',
|
||||||
createProxyMiddleware({
|
createProxyMiddleware({
|
||||||
target: 'http://localhost:3003',
|
target: 'http://127.0.0.1:3003',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(
|
||||||
'/sync',
|
'/sync',
|
||||||
createProxyMiddleware({
|
createProxyMiddleware({
|
||||||
target: 'http://localhost:3003',
|
target: 'http://127.0.0.1:3003',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
console.log('Proxy middleware applied to /api');
|
app.use(
|
||||||
|
'/ws',
|
||||||
|
createProxyMiddleware({
|
||||||
|
target: 'ws://127.0.0.1:8080',
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Proxy middleware applied');
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user