mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-03-18 21:30:35 +01:00
Added Data Debugger
This is to assist in the ongoing sync issues by allowing you to export any missing data for inspection
This commit is contained in:
21
Dockerfile-arm
Normal file
21
Dockerfile-arm
Normal file
@@ -0,0 +1,21 @@
|
||||
# Stage 1: Build the application
|
||||
FROM arm32v7/node:14 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm cache clean --force
|
||||
RUN npm install
|
||||
|
||||
COPY ./ ./
|
||||
|
||||
# Stage 2: Create the production image
|
||||
FROM arm32v7/node:14-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "run", "start-app"]
|
||||
138
backend/api.js
138
backend/api.js
@@ -524,6 +524,144 @@ router.post("/updatePassword", async (req, res) => {
|
||||
|
||||
});
|
||||
|
||||
router.post("/getLibraries", async (req, res) => {
|
||||
try {
|
||||
const { itemid } = req.body;
|
||||
const { rows:config } = await db.query('SELECT * FROM app_config where "ID"=1');
|
||||
|
||||
let payload=
|
||||
{
|
||||
existing_library_count:0,
|
||||
existing_item_count:0,
|
||||
existing_season_count:0,
|
||||
existing_episode_count:0,
|
||||
api_library_count:0,
|
||||
api_item_count:0,
|
||||
api_season_count:0,
|
||||
api_episode_count:0,
|
||||
missing_api_library_data:{},
|
||||
missing_api_item_data:{},
|
||||
missing_api_season_data:{},
|
||||
missing_api_episode_data:{},
|
||||
};
|
||||
|
||||
/////////////////////////Get Admin
|
||||
|
||||
const adminurl = `${config[0].JF_HOST}/Users`;
|
||||
const response = await axios_instance.get(adminurl, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": config[0].JF_API_KEY,
|
||||
},
|
||||
});
|
||||
const adminUser = await response.data.filter(
|
||||
(user) => user.Policy.IsAdministrator === true
|
||||
);
|
||||
|
||||
////////////////////////
|
||||
const db_libraries=await db.query('SELECT "Id" FROM jf_libraries').then((res) => res.rows.map((row) => row.Id))
|
||||
const db_items=await db.query('SELECT "Id" FROM jf_library_items').then((res) => res.rows.map((row) => row.Id))
|
||||
const db_seasons=await db.query('SELECT "Id" FROM jf_library_seasons').then((res) => res.rows.map((row) => row.Id))
|
||||
const db_episodes=await db.query('SELECT "EpisodeId" FROM jf_library_episodes').then((res) => res.rows.map((row) => row.EpisodeId))
|
||||
|
||||
|
||||
//get libraries
|
||||
let url=`${config[0].JF_HOST}/Users/${adminUser[0].Id}/Items`;
|
||||
if(itemid)
|
||||
{
|
||||
url+=`/${itemid}`;
|
||||
}
|
||||
|
||||
const response_data = await axios_instance.get(url, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
let libraries=response_data.data.Items;
|
||||
|
||||
//get items
|
||||
const item_data = [];
|
||||
for (let i = 0; i < libraries.length; i++) {
|
||||
const library = libraries[i];
|
||||
|
||||
let item_url=`${config[0].JF_HOST}/Users/${adminUser[0].Id}/Items?ParentID=${library.Id}`;
|
||||
const response_data_item = await axios_instance.get(item_url, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
|
||||
},
|
||||
});
|
||||
|
||||
const libraryItemsWithParent = response_data_item.data.Items.map((items) => ({
|
||||
...items,
|
||||
...{ ParentId: library.Id },
|
||||
}));
|
||||
item_data.push(...libraryItemsWithParent);
|
||||
}
|
||||
|
||||
payload.existing_library_count=db_libraries.length;
|
||||
payload.api_library_count=libraries.length;
|
||||
|
||||
payload.existing_item_count=db_items.length;
|
||||
payload.api_item_count=item_data.length;
|
||||
|
||||
|
||||
|
||||
//SHows
|
||||
let allSeasons = [];
|
||||
let allEpisodes =[];
|
||||
|
||||
const { rows: shows } = await db.query(`SELECT "Id" FROM public.jf_library_items where "Type"='Series'`);
|
||||
//loop for each show
|
||||
|
||||
for (const show of shows) {
|
||||
|
||||
let season_url = `${config[0].JF_HOST}/shows/${show.Id}/Seasons`;
|
||||
let episodes_url = `${config[0].JF_HOST}/shows/${show.Id}/Episodes`;
|
||||
|
||||
const response_data_seasons = await axios_instance.get(season_url, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
|
||||
},
|
||||
});
|
||||
|
||||
const response_data_episodes = await axios_instance.get(episodes_url, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
|
||||
},
|
||||
});
|
||||
|
||||
allSeasons.push(...response_data_seasons.data.Items);
|
||||
allEpisodes.push(...response_data_episodes.data.Items);
|
||||
}
|
||||
|
||||
payload.existing_season_count=db_seasons.length;
|
||||
payload.api_season_count=allSeasons.length;
|
||||
|
||||
payload.existing_episode_count=db_episodes.length;
|
||||
payload.api_episode_count=allEpisodes.length;
|
||||
|
||||
//missing data section
|
||||
let missing_libraries=libraries.filter(library => !db_libraries.includes(library.Id));
|
||||
let missing_items=item_data.filter(item => !db_items.includes(item.Id));
|
||||
let missing_seasons=allSeasons.filter(season => !db_seasons.includes(season.Id));
|
||||
let missing_episodes=allEpisodes.filter(episode => !db_episodes.includes(episode.Id));
|
||||
|
||||
payload.missing_api_library_data=missing_libraries;
|
||||
payload.missing_api_item_data=missing_items;
|
||||
payload.missing_api_season_data=missing_seasons;
|
||||
payload.missing_api_episode_data=missing_episodes;
|
||||
|
||||
|
||||
res.send(payload);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -127,9 +127,9 @@ class sync {
|
||||
try {
|
||||
|
||||
let url = `${this.hostUrl}/shows/${itemID}/${type}`;
|
||||
if (itemID !== undefined) {
|
||||
url += `?ParentID=${itemID}`;
|
||||
}
|
||||
// if (itemID !== undefined) {
|
||||
// url += `?ParentID=${itemID}`;
|
||||
// }
|
||||
const response = await axios_instance.get(url, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": this.apiKey,
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "jfstat",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.4.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "jfstat",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.4.6",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
@@ -26,6 +26,7 @@
|
||||
"concurrently": "^7.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"crypto-js": "^4.1.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"github-api": "^3.4.0",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"knex": "^2.4.2",
|
||||
@@ -9593,6 +9594,11 @@
|
||||
"webpack": "^4.0.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/file-saver": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
|
||||
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"version": "1.0.4",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jfstat",
|
||||
"version": "1.0.4.6",
|
||||
"version": "1.0.4.7",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.6",
|
||||
@@ -21,6 +21,7 @@
|
||||
"concurrently": "^7.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"crypto-js": "^4.1.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"github-api": "^3.4.0",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"knex": "^2.4.2",
|
||||
|
||||
@@ -28,6 +28,7 @@ import About from './pages/about';
|
||||
import Testing from './pages/testing';
|
||||
import Activity from './pages/activity';
|
||||
import Statistics from './pages/statistics';
|
||||
import Datadebugger from './pages/data-debugger';
|
||||
|
||||
function App() {
|
||||
|
||||
@@ -135,6 +136,7 @@ if (config && setupState===2 && token!==null){
|
||||
<Route path="/activity" element={<Activity />} />
|
||||
<Route path="/testing" element={<Testing />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/debugger/data" element={<Datadebugger />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
142
src/pages/data-debugger.js
Normal file
142
src/pages/data-debugger.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from 'axios';
|
||||
|
||||
import {Button } from 'react-bootstrap';
|
||||
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
|
||||
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function Datadebugger() {
|
||||
const [data, setData] = useState();
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
|
||||
const libraryData = await axios.post(`/api/getLibraries`, {
|
||||
itemid: undefined,
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
setData(libraryData.data);
|
||||
console.log(libraryData.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
|
||||
const intervalId = setInterval(fetchData, 60000 * 5);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [token]);
|
||||
|
||||
const handleDownload = (jsonData,filename) => {
|
||||
// const jsonData = { /* Your JSON object */ };
|
||||
|
||||
const jsonString = JSON.stringify(jsonData);
|
||||
const blob = new Blob([jsonString], { type: 'application/json' });
|
||||
|
||||
saveAs(blob, filename+'.json');
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div style={{color:'white '}}>
|
||||
<h1>Data Debugger</h1>
|
||||
<br/>
|
||||
{/* <p>{data? JSON.stringify(data):''}</p> */}
|
||||
|
||||
<TableContainer className='rounded-2'>
|
||||
<Table aria-label="collapsible table" >
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Data Type</TableCell>
|
||||
<TableCell>Database Count</TableCell>
|
||||
<TableCell>API Count</TableCell>
|
||||
<TableCell>Difference</TableCell>
|
||||
<TableCell>Export Missing Data</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Libraries</TableCell>
|
||||
<TableCell>{data ? data.existing_library_count:''}</TableCell>
|
||||
<TableCell>{data ? data.api_library_count:''}</TableCell>
|
||||
<TableCell>{data ? data.api_library_count-data.existing_library_count:''}</TableCell>
|
||||
<TableCell>{data && data.api_library_count!==data.existing_library_count ?
|
||||
<Button onClick={()=>handleDownload(data.missing_api_library_data,'MissingLibraryData')}>
|
||||
Download
|
||||
</Button>:
|
||||
''}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Library Items</TableCell>
|
||||
<TableCell>{data ? data.existing_item_count:''}</TableCell>
|
||||
<TableCell>{data ? data.api_item_count:''}</TableCell>
|
||||
<TableCell>{data ? data.api_item_count-data.existing_item_count:''}</TableCell>
|
||||
<TableCell>{data && data.api_item_count!==data.existing_item_count ?
|
||||
<Button onClick={()=>handleDownload(data.missing_api_item_data,'MissingItemData')}>
|
||||
Download
|
||||
</Button>:
|
||||
''}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Seasons</TableCell>
|
||||
<TableCell>{data ? data.existing_season_count:''}</TableCell>
|
||||
<TableCell>{data ? data.api_season_count:''}</TableCell>
|
||||
<TableCell>{data ? data.api_season_count-data.existing_season_count:''}</TableCell>
|
||||
<TableCell>{data && data.api_season_count!==data.existing_season_count ?
|
||||
<Button onClick={()=>handleDownload(data.missing_api_season_data,'MissingSeasonData')}>
|
||||
Download
|
||||
</Button>:
|
||||
''}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>Episodes</TableCell>
|
||||
<TableCell>{data ? data.existing_episode_count:''}</TableCell>
|
||||
<TableCell>{data ? data.api_episode_count:''}</TableCell>
|
||||
<TableCell>{data ? data.api_episode_count-data.existing_episode_count:''}</TableCell>
|
||||
<TableCell>{data && data.api_episode_count!==data.existing_episode_count ?
|
||||
<Button onClick={()=>handleDownload(data.missing_api_episode_data,'MissingEpisodeData')}>
|
||||
Download
|
||||
</Button>:
|
||||
''}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default Datadebugger;
|
||||
Reference in New Issue
Block a user