diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4cd9f2e --- /dev/null +++ b/Dockerfile @@ -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"] + diff --git a/backend/api.js b/backend/api.js index b16b5e9..cd1c2b5 100644 --- a/backend/api.js +++ b/backend/api.js @@ -1,6 +1,6 @@ // api.js const express = require("express"); -// const pgp = require("pg-promise")(); +const ActivityMonitor=require('./watchdog/ActivityMonitor'); const db = require("./db"); const router = express.Router(); @@ -11,61 +11,93 @@ router.get("/test", async (req, res) => { }); router.get("/getconfig", async (req, res) => { - const { rows } = await db.query('SELECT * FROM app_config where "ID"=1'); - // console.log(`ENDPOINT CALLED: /getconfig: ` + rows); - // console.log(`ENDPOINT CALLED: /setconfig: `+rows.length); - res.send(rows); + try{ + const { rows } = await db.query('SELECT * FROM app_config where "ID"=1'); + res.send(rows); + + }catch(error) + { + console.log(error); + } + }); 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'); - - let query='UPDATE app_config SET "JF_HOST"=$1, "JF_API_KEY"=$2 where "ID"=1'; - if(getConfig.length===0) + 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'; + 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: `); }); -router.get("/getAllFromJellyfin", async (req, res) => { - const sync = require("./sync"); - const { rows } = await db.query('SELECT * FROM app_config where "ID"=1'); - if (rows[0].JF_HOST === null || rows[0].JF_API_KEY === null) { - res.send({ error: "Config Details Not Found" }); - return; - } +// router.get("/getAllFromJellyfin", async (req, res) => { +// const sync = require("./sync"); +// const { rows } = await db.query('SELECT * FROM app_config where "ID"=1'); +// if (rows[0].JF_HOST === null || rows[0].JF_API_KEY === null) { +// res.send({ error: "Config Details Not Found" }); +// return; +// } - const _sync = new sync(rows[0].JF_HOST, rows[0].JF_API_KEY); - const results = await _sync.getAllItems(); +// const _sync = new sync(rows[0].JF_HOST, rows[0].JF_API_KEY); +// 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) => { - const Id = req.headers['id']; + try{ + const Id = req.headers['id']; - const { rows } = await db.query( - `SELECT * FROM jf_library_items where "ParentId"='${Id}'` - ); - console.log({ Id: Id }); - res.send(rows); + const { rows } = await db.query( + `SELECT * FROM jf_library_items where "ParentId"='${Id}'` + ); + console.log({ Id: Id }); + res.send(rows); + + + }catch(error) + { + console.log(error); + } 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; diff --git a/backend/db.js b/backend/db.js index f5ef9ca..400bbca 100644 --- a/backend/db.js +++ b/backend/db.js @@ -1,15 +1,87 @@ // db.js const { Pool } = require('pg'); +const fs = require('fs'); 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({ - user: 'jfstat', - host: '10.0.0.99', + user: (development ? _DEV_USER: _POSTGRES_USER), + host:(development ? _DEV_IP: _POSTGRES_IP), database: 'jfstat', - password: '123456', - port: 32778, // or your PostgreSQL port number + password:(development ? _DEV_PASSWORD: _POSTGRES_PASSWORD), + 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) { const client = await pool.connect(); let result='SUCCESS'; @@ -75,8 +147,19 @@ async function insertBulk(table_name, data,columns) { 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 = { - query: (text, params) => pool.query(text, params), + query:query, deleteBulk: deleteBulk, insertBulk: insertBulk, + initDB: initDB, }; diff --git a/backend/init.sql b/backend/init.sql new file mode 100644 index 0000000..fbe1b36 --- /dev/null +++ b/backend/init.sql @@ -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 +-- + diff --git a/backend/server.js b/backend/server.js index d373ca0..5f73c37 100644 --- a/backend/server.js +++ b/backend/server.js @@ -5,7 +5,9 @@ const apiRouter = require('./api'); const syncRouter = require('./sync'); const statsRouter = require('./stats'); const ActivityMonitor=require('./watchdog/ActivityMonitor'); -ActivityMonitor.ActivityMonitor(1000); +const db = require("./db"); + + const app = express(); 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(cors()); + app.use('/api', apiRouter); // mount the API router at /api app.use('/sync', syncRouter); // mount the API router at /sync app.use('/stats', statsRouter); // mount the API router at /stats app.listen(PORT, () => { console.log(`Server listening on http://${LISTEN_IP}:${PORT}`); + try{ + db.initDB(); + ActivityMonitor.ActivityMonitor(1000); + }catch(error) + { + console.log(error); + } + }); diff --git a/backend/stats.js b/backend/stats.js index 2da83e5..f8b6e16 100644 --- a/backend/stats.js +++ b/backend/stats.js @@ -10,160 +10,208 @@ router.get("/test", async (req, res) => { }); router.get("/getLibraryOverview", async (req, res) => { - const { rows } = await db.query('SELECT * FROM jf_library_count_view'); - res.send(rows); - console.log(`ENDPOINT CALLED: /getLibraryOverview`); + try { + const { rows } = await db.query("SELECT * FROM jf_library_count_view"); + res.send(rows); + } catch (error) { + res.send(error); + } }); - router.post("/getMostViewedSeries", async (req, res) => { - const {days} = req.body; - let _days=days; - if(days===undefined) - { - _days=30; + try { + const { days } = req.body; + let _days = days; + if (days === undefined) { + _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) => { - const {days} = req.body; - let _days=days; - if(days===undefined) - { - _days=30; + try { + const { days } = req.body; + let _days = days; + if (days === undefined) { + _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) => { - const {days} = req.body; - let _days=days; - if(days===undefined) - { - _days=30; + try { + const { days } = req.body; + let _days = days; + if (days === undefined) { + _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) => { - const {days} = req.body; - let _days=days; - if(days===undefined) - { - _days=30; + try { + const { days } = req.body; + let _days = days; + if (days === undefined) { + _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) => { - const {days} = req.body; - let _days=days; - if(days===undefined) - { - _days=30; + try { + const { days } = req.body; + let _days = days; + if (days === undefined) { + _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) => { - const {days} = req.body; - let _days=days; - if(days===undefined) - { - _days=30; + try { + const { days } = req.body; + let _days = days; + if (days === undefined) { + _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) => { - const {days} = req.body; - let _days=days; - if(days===undefined) - { - _days=30; + try { + const { days } = req.body; + let _days = days; + if (days === undefined) { + _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) => { - const {days} = req.body; - let _days=days; - if(days===undefined) - { - _days=30; + try { + const { days } = req.body; + let _days = days; + if (days === undefined) { + _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) => { - const {days} = req.body; - let _days=days; - if(days===undefined) - { - _days=30; + try { + const { days } = req.body; + let _days = days; + if (days === undefined) { + _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) => { - const { rows } = await db.query('SELECT * FROM jf_playback_activity'); - res.send(rows); - // console.log(`ENDPOINT CALLED: /getPlaybackActivity`); + try { + const { rows } = await db.query("SELECT * FROM jf_playback_activity"); + res.send(rows); + } catch (error) { + res.send(error); + } }); router.get("/getAllUserActivity", async (req, res) => { - const { rows } = await db.query('SELECT * FROM jf_all_user_activity'); - res.send(rows); - // console.log(`ENDPOINT CALLED: /getPlaybackActivity`); + try { + const { rows } = await db.query("SELECT * FROM jf_all_user_activity"); + 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; diff --git a/backend/watchdog/ActivityMonitor.js b/backend/watchdog/ActivityMonitor.js index 90009f9..a9668a1 100644 --- a/backend/watchdog/ActivityMonitor.js +++ b/backend/watchdog/ActivityMonitor.js @@ -7,9 +7,12 @@ const { jf_activity_watchdog_columns, jf_activity_watchdog_mapping } = require(' async function ActivityMonitor(interval) { console.log("Activity Interval: " + interval); + const { rows: config } = await db.query( 'SELECT * FROM app_config where "ID"=1' ); + + if(config.length===0) { return; diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..667064d --- /dev/null +++ b/build.sh @@ -0,0 +1 @@ +docker build -t jellystat . \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cf5f2a1 --- /dev/null +++ b/docker-compose.yml @@ -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: \ No newline at end of file diff --git a/package.json b/package.json index 0ee5f96..99a5e30 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ }, "scripts": { "start": "react-scripts start", + "start-server": "cd backend && node server.js", + "start-app": "concurrently \"npm run start-server\" \"npm start\"", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/src/App.js b/src/App.js index e3aa058..db36457 100644 --- a/src/App.js +++ b/src/App.js @@ -17,7 +17,7 @@ import Navbar from './pages/components/navbar'; import Home from './pages/home'; import Settings from './pages/settings'; import Users from './pages/users'; -import UserInfo from './pages/user-info'; +import UserInfo from './pages/components/user-info'; import Libraries from './pages/libraries'; import ErrorPage from './pages/components/error'; @@ -37,7 +37,7 @@ function App() { const fetchConfig = async () => { try { const newConfig = await Config(); - if(newConfig !== 'ERR_NETWORK'){ + if(!newConfig.response){ setConfig(newConfig); }else{ seterrorFlag(true); @@ -45,7 +45,7 @@ function App() { setLoading(false); } catch (error) { - + console.log(error); } }; diff --git a/src/lib/config.js b/src/lib/config.js index c85986e..bdb7f96 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -2,12 +2,12 @@ import axios from 'axios'; async function Config() { 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]; return { hostUrl: JF_HOST, apiKey: JF_API_KEY, username: APP_USER, password: APP_PASSWORD }; } catch (error) { - console.log(error); - return error.code; + // console.log(error); + return error; } } diff --git a/src/lib/navdata.js b/src/lib/navdata.js index 251a59a..659d7d8 100644 --- a/src/lib/navdata.js +++ b/src/lib/navdata.js @@ -1,8 +1,8 @@ import HomeFillIcon from 'remixicon-react/HomeFillIcon'; -import FileListFillIcon from 'remixicon-react/FileListFillIcon'; -import BarChartFillIcon from 'remixicon-react/BarChartFillIcon'; +// import FileListFillIcon from 'remixicon-react/FileListFillIcon'; +// import BarChartFillIcon from 'remixicon-react/BarChartFillIcon'; import SettingsFillIcon from 'remixicon-react/SettingsFillIcon'; import GalleryFillIcon from 'remixicon-react/GalleryFillIcon'; import UserFillIcon from 'remixicon-react/UserFillIcon'; diff --git a/src/pages/components/WatchStatistics.js b/src/pages/components/WatchStatistics.js index d00eab2..a8e13e6 100644 --- a/src/pages/components/WatchStatistics.js +++ b/src/pages/components/WatchStatistics.js @@ -22,7 +22,7 @@ function WatchStatistics() { setInput(1); setDays(0); } else { - setDays(parseInt(input) - 1); + setDays(parseInt(input)); } console.log(days); diff --git a/src/pages/components/libraryStatCard/library-stat-component.js b/src/pages/components/libraryStatCard/library-stat-component.js index 6bb3d71..5b45446 100644 --- a/src/pages/components/libraryStatCard/library-stat-component.js +++ b/src/pages/components/libraryStatCard/library-stat-component.js @@ -27,7 +27,7 @@ function LibraryStatComponent(props) {

{index + 1}

{item.Name}

-

{item.CollectionType =='tvshows'? (item.Library_Count+' / '+item.Season_Count+' / '+item.Episode_Count): item.Library_Count}

+

{item.CollectionType ==='tvshows'? (item.Library_Count+' / '+item.Season_Count+' / '+item.Episode_Count): item.Library_Count}

))} diff --git a/src/pages/components/settings/TerminalComponent.js b/src/pages/components/settings/TerminalComponent.js index d13a1ed..c5a0770 100644 --- a/src/pages/components/settings/TerminalComponent.js +++ b/src/pages/components/settings/TerminalComponent.js @@ -7,7 +7,7 @@ const TerminalComponent = () => { useEffect(() => { // 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 socket.addEventListener('message', (event) => { diff --git a/src/pages/components/user-info.js b/src/pages/components/user-info.js new file mode 100644 index 0000000..5c339ba --- /dev/null +++ b/src/pages/components/user-info.js @@ -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 ( +
+ + +
+ ); +} +export default UserInfo; diff --git a/src/pages/components/user-info/globalStats.js b/src/pages/components/user-info/globalStats.js new file mode 100644 index 0000000..e9c7a2b --- /dev/null +++ b/src/pages/components/user-info/globalStats.js @@ -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 ( +
+

User Stats

+
+ + + + +
+
+ ); +} + +export default GlobalStats; diff --git a/src/pages/components/user-info/globalstats/watchtimestats.js b/src/pages/components/user-info/globalstats/watchtimestats.js new file mode 100644 index 0000000..a77a6d9 --- /dev/null +++ b/src/pages/components/user-info/globalstats/watchtimestats.js @@ -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 =

{value}

; + const formattedLabel = ( + + {label} + {value === 1 ? '' : 's'} + + ); + result.push( + + {formattedValue} {formattedLabel} + + ); + totalSeconds -= value * seconds; + } + return result; + }, []); + + if (parts.length === 0) { + return ( + <> +

0

{' '} +

Minutes

+ + ); + } + + return parts; + } + + + + return ( +
+
+
{props.heading}
+
+ +
+

{props.data.Plays || 0}

+

Plays /

+ + <>{formatTime(props.data.total_playback_duration || 0,'stat-value','stat-unit')} + +
+
+ ); +} + +export default WatchTimeStats; diff --git a/src/pages/components/user-info/user-details.js b/src/pages/components/user-info/user-details.js new file mode 100644 index 0000000..1387067 --- /dev/null +++ b/src/pages/components/user-info/user-details.js @@ -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 ( +
+
+ {imgError ? ( + + ) : ( + + )} +
+

{data.Name}

+
+ ); +} + +export default UserDetails; diff --git a/src/pages/css/sessions.css b/src/pages/css/sessions.css index a27138b..cf6361b 100644 --- a/src/pages/css/sessions.css +++ b/src/pages/css/sessions.css @@ -7,9 +7,6 @@ display: grid; grid-template-columns: repeat(auto-fit, minmax(520px, 520px)); 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; } diff --git a/src/pages/css/users/globalstats.css b/src/pages/css/users/globalstats.css new file mode 100644 index 0000000..6517b2d --- /dev/null +++ b/src/pages/css/users/globalstats.css @@ -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; +} \ No newline at end of file diff --git a/src/pages/css/users/user-details.css b/src/pages/css/users/user-details.css new file mode 100644 index 0000000..1660646 --- /dev/null +++ b/src/pages/css/users/user-details.css @@ -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; + + +} \ No newline at end of file diff --git a/src/pages/css/users.css b/src/pages/css/users/users.css similarity index 100% rename from src/pages/css/users.css rename to src/pages/css/users/users.css diff --git a/src/pages/libraries.js b/src/pages/libraries.js index e963d8a..a46b65e 100644 --- a/src/pages/libraries.js +++ b/src/pages/libraries.js @@ -3,7 +3,7 @@ import axios from "axios"; import Config from "../lib/config"; import "./css/libraries.css"; -import "./css/users.css"; +import "./css/users/users.css"; import Loading from "./components/loading"; diff --git a/src/pages/setup.js b/src/pages/setup.js index 93a41e6..f79e085 100644 --- a/src/pages/setup.js +++ b/src/pages/setup.js @@ -3,6 +3,7 @@ import axios from "axios"; import Config from "../lib/config"; import "./css/setup.css"; +import LibrarySync from "./components/settings/librarySync"; // import Loading from './components/loading'; @@ -18,55 +19,9 @@ function Setup() { async function beginSync() { - setProcessing(true); - - 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(true); + await LibrarySync.beginSync(); setProcessing(false); - // return { isValid: isValid, errorMessage: errorMessage }; } async function validateSettings(_url, _apikey) { diff --git a/src/pages/testing.js b/src/pages/testing.js index bf85914..34c5bba 100644 --- a/src/pages/testing.js +++ b/src/pages/testing.js @@ -4,7 +4,7 @@ import './css/libraries.css'; import Loading from './components/loading'; // import PlaybackActivity from './components/playbackactivity'; -import Activity from './activity'; +// import Activity from './activity'; // import StatCards from './components/StatsCards'; import LibraryOverView from './components/libraryOverview'; diff --git a/src/pages/user-info.js b/src/pages/user-info.js deleted file mode 100644 index f076800..0000000 --- a/src/pages/user-info.js +++ /dev/null @@ -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 ( -
-

{UserId}

-
- ); -} -export default UserInfo; diff --git a/src/pages/users.js b/src/pages/users.js index eb08a28..0eaaf46 100644 --- a/src/pages/users.js +++ b/src/pages/users.js @@ -4,7 +4,7 @@ import Config from "../lib/config"; import { Link } from 'react-router-dom'; import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon"; -import "./css/users.css"; +import "./css/users/users.css"; import Loading from "./components/loading"; diff --git a/src/setupProxy.js b/src/setupProxy.js index 7785242..e77b0e7 100644 --- a/src/setupProxy.js +++ b/src/setupProxy.js @@ -4,23 +4,32 @@ module.exports = function(app) { app.use( '/api', createProxyMiddleware({ - target: 'http://localhost:3003', + target: 'http://127.0.0.1:3003', changeOrigin: true, }) ); app.use( '/stats', createProxyMiddleware({ - target: 'http://localhost:3003', + target: 'http://127.0.0.1:3003', changeOrigin: true, }) ); app.use( '/sync', createProxyMiddleware({ - target: 'http://localhost:3003', + target: 'http://127.0.0.1:3003', 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'); };