Code clean-up + API Keys

Cleaned up redundant code and moved around classes to be better grouped eg routes folder

Moved endpoints to other route files to better represent their actions

Removed redundant endpoints

Renamed endpoints to be more meaningful

Added API Key authorisations to utilize the API outside of Jellystat UI (Still need to document Endpoints)

Added new column to app_config to store api keys

New API Section under settings

Updated backups route name
This commit is contained in:
Thegan Govender
2023-07-01 22:52:19 +02:00
parent 44643274c3
commit e7912397d2
28 changed files with 1068 additions and 737 deletions

View File

@@ -14,7 +14,7 @@ exports.up = async function(knex) {
exports.down = async function(knex) {
try {
await knex.schema.alterTable('jf_activity_watchdog', function(table) {
await knex.schema.alterTable('app_config', function(table) {
table.dropColumn('settings');
});
} catch (error) {

View File

@@ -0,0 +1,23 @@
exports.up = async function(knex) {
try
{
const hasTable = await knex.schema.hasTable('app_config');
if (hasTable) {
await knex.schema.alterTable('app_config', function(table) {
table.json('api_keys');
});
}
}catch (error) {
console.error(error);
}
};
exports.down = async function(knex) {
try {
await knex.schema.alterTable('app_config', function(table) {
table.dropColumn('api_keys');
});
} catch (error) {
console.error(error);
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
const express = require("express");
const db = require("./db");
const db = require("../db");
const jwt = require('jsonwebtoken');

View File

@@ -240,7 +240,7 @@ async function restore(file,logData,result) {
}
// Route handler for backup endpoint
router.get('/backup', async (req, res) => {
router.get('/beginBackup', async (req, res) => {
try {
let startTime = moment();
let refLog={logData:[],result:'Success'};

View File

@@ -1,9 +1,9 @@
const db = require("./db");
const db = require("../db");
const {jf_logging_columns,jf_logging_mapping,} = require("./models/jf_logging");
const {jf_logging_columns,jf_logging_mapping,} = require("../models/jf_logging");
const express = require("express");
const router = express.Router();

View File

@@ -1,6 +1,6 @@
const express = require('express');
const axios = require("axios");
const db = require("./db");
const db = require("../db");
const https = require('https');
const agent = new https.Agent({
@@ -143,6 +143,141 @@ router.get('/Items/Images/Backdrop/', async(req, res) => {
res.status(500).send('Error fetching image: '+error);
});
});
router.get("/getSessions", async (req, res) => {
try {
const { rows: config } = await db.query(
'SELECT * FROM app_config where "ID"=1'
);
if (
config.length === 0 ||
config[0].JF_HOST === null ||
config[0].JF_API_KEY === null
) {
res.status(503);
res.send({ error: "Config Details Not Found" });
return;
}
let url = `${config[0].JF_HOST}/sessions`;
const response_data = await axios_instance.get(url, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY,
},
});
res.send(response_data.data);
} catch (error) {
res.status(503);
res.send(error);
}
});
router.get("/getAdminUsers", async (req, res) => {
try {
const { rows: config } = await db.query(
'SELECT * FROM app_config where "ID"=1'
);
if (
config.length === 0 ||
config[0].JF_HOST === null ||
config[0].JF_API_KEY === null
) {
res.status(503);
res.send({ error: "Config Details Not Found" });
return;
}
let url = `${config[0].JF_HOST}/Users`;
const response = await axios_instance.get(url, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY,
},
});
if(!response || typeof response.data !== 'object' || !Array.isArray(response.data))
{
res.status(503);
res.send({ error: "Invalid Response from Users API Call.", user_response:response });
return;
}
const adminUser = response.data.filter(
(user) => user.Policy.IsAdministrator === true
);
res.send(adminUser);
} catch (error) {
res.status(503);
res.send(error);
}
});
router.get("/getRecentlyAdded", async (req, res) => {
try {
const { libraryid } = req.query;
const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1');
if (config.length===0 || config[0].JF_HOST === null || config[0].JF_API_KEY === null) {
res.status(503);
res.send({ error: "Config Details Not Found" });
return;
}
let userid=config[0].settings?.preferred_admin?.userid;
if(!userid)
{
const adminurl = `${config[0].JF_HOST}/Users`;
const response = await axios_instance.get(adminurl, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
},
});
if(!response || typeof response.data !== 'object' || !Array.isArray(response.data))
{
res.status(503);
res.send({ error: "Invalid Response from Users API Call.", user_response:response });
return;
}
const admins = response.data.filter(
(user) => user.Policy.IsAdministrator === true
);
userid = admins[0].Id;
}
let url=`${config[0].JF_HOST}/users/${userid}/Items/latest`;
if(libraryid)
{
url+=`?parentId=${libraryid}`;
}
const response_data = await axios_instance.get(url, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
},
});
res.send(response_data.data);
} catch (error) {
res.status(503);
res.send(error);
}
});

View File

@@ -1,19 +1,8 @@
// api.js
const express = require("express");
const db = require("./db");
const axios=require("axios");
const db = require("../db");
const router = express.Router();
const https = require('https');
const agent = new https.Agent({
rejectUnauthorized: (process.env.REJECT_SELF_SIGNED_CERTIFICATES || 'true').toLowerCase() ==='true'
});
const axios_instance = axios.create({
httpsAgent: agent
});
@@ -27,15 +16,26 @@ router.get("/getLibraryOverview", async (req, res) => {
}
});
router.post("/getMostViewedSeries", async (req, res) => {
router.post("/getMostViewedByType", async (req, res) => {
try {
const { days } = req.body;
const { days,type } = req.body;
const valid_types=['Audio','Movie','Series'];
let _days = days;
if (days === undefined) {
_days = 30;
}
if(!valid_types.includes(type))
{
res.status(503);
return res.send('Invalid Type Value');
}
const { rows } = await db.query(
`select * from fs_most_played_items(${_days-1},'Series') limit 5`
`select * from fs_most_played_items(${_days-1},'${type}') limit 5`
);
res.send(rows);
} catch (error) {
@@ -44,41 +44,34 @@ router.post("/getMostViewedSeries", async (req, res) => {
}
});
router.post("/getMostViewedMovies", async (req, res) => {
router.post("/getMostPopularByType", async (req, res) => {
try {
const { days } = req.body;
const { days,type } = req.body;
const valid_types=['Audio','Movie','Series'];
let _days = days;
if (days === undefined) {
_days = 30;
}
if(!valid_types.includes(type))
{
res.status(503);
return res.send('Invalid Type Value');
}
const { rows } = await db.query(
`select * from fs_most_played_items(${_days-1},'Movie') limit 5`
`select * from fs_most_popular_items(${_days-1},'${type}') limit 5`
);
res.send(rows);
} catch (error) {
console.log('/getMostViewedMovies');
console.log(error);
res.status(503);
res.send(error);
}
});
router.post("/getMostViewedMusic", async (req, res) => {
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.status(503);
res.send(error);
}
});
router.post("/getMostViewedLibraries", async (req, res) => {
try {
@@ -131,56 +124,6 @@ router.post("/getMostActiveUsers", async (req, res) => {
}
});
router.post("/getMostPopularMovies", async (req, res) => {
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.status(503);
res.send(error);
}
});
router.post("/getMostPopularSeries", async (req, res) => {
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.status(503);
res.send(error);
}
});
router.post("/getMostPopularMusic", async (req, res) => {
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.status(503);
res.send(error);
}
});
router.get("/getPlaybackActivity", async (req, res) => {
try {
@@ -201,13 +144,14 @@ router.get("/getAllUserActivity", async (req, res) => {
}
});
router.post("/getUserDetails", async (req, res) => {
router.post("/getUserLastPlayed", async (req, res) => {
try {
const { userid } = req.body;
const { rows } = await db.query(
`select * from jf_users where "Id"='${userid}'`
`select * from fs_last_user_activity('${userid}') limit 15`
);
res.send(rows[0]);
res.send(rows);
} catch (error) {
console.log(error);
res.status(503);
@@ -215,6 +159,7 @@ router.post("/getUserDetails", async (req, res) => {
}
});
//Global Stats
router.post("/getGlobalUserStats", async (req, res) => {
try {
const { hours,userid } = req.body;
@@ -233,25 +178,20 @@ router.post("/getGlobalUserStats", async (req, res) => {
}
});
router.post("/getUserLastPlayed", async (req, res) => {
router.post("/getGlobalItemStats", async (req, res) => {
try {
const { userid } = req.body;
const { hours,itemid } = req.body;
let _hours = hours;
if (hours === undefined) {
_hours = 24;
}
const { rows } = await db.query(
`select * from fs_last_user_activity('${userid}') limit 15`
);
res.send(rows);
} catch (error) {
console.log(error);
res.status(503);
res.send(error);
}
});
router.post("/getLibraryDetails", async (req, res) => {
try {
const { libraryid } = req.body;
const { rows } = await db.query(
`select * from jf_libraries where "Id"='${libraryid}'`
`select count(*)"Plays",
sum("PlaybackDuration") total_playback_duration
from jf_playback_activity jf_playback_activity
where
("EpisodeId"='${itemid}' OR "SeasonId"='${itemid}' OR "NowPlayingItemId"='${itemid}')
AND jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - INTERVAL '1 hour' * ${_hours} AND NOW();`
);
res.send(rows[0]);
} catch (error) {
@@ -280,6 +220,8 @@ router.post("/getGlobalLibraryStats", async (req, res) => {
});
router.get("/getLibraryCardStats", async (req, res) => {
try {
const { rows } = await db.query("select * from js_library_stats_overview");
@@ -333,60 +275,6 @@ router.post("/getLibraryLastPlayed", async (req, res) => {
}
});
router.get("/getRecentlyAdded", async (req, res) => {
try {
const { libraryid } = req.query;
const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1');
if (config.length===0 || config[0].JF_HOST === null || config[0].JF_API_KEY === null) {
res.status(503);
res.send({ error: "Config Details Not Found" });
return;
}
const adminurl = `${config[0].JF_HOST}/Users`;
const response = await axios_instance.get(adminurl, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
},
});
if(!response || typeof response.data !== 'object' || !Array.isArray(response.data))
{
res.status(503);
res.send({ error: "Invalid Response from Users API Call.", user_response:response });
return;
}
const adminUser = response.data.filter(
(user) => user.Policy.IsAdministrator === true
);
let url=`${config[0].JF_HOST}/users/${adminUser[0].Id}/Items/latest`;
if(libraryid)
{
url+=`?parentId=${libraryid}`;
}
const response_data = await axios_instance.get(url, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
},
});
res.send(response_data.data);
} catch (error) {
res.status(503);
res.send(error);
}
});
router.post("/getViewsOverTime", async (req, res) => {
try {
@@ -474,7 +362,6 @@ const finalData = {libraries:libraries,stats:Object.values(reorganizedData)};
}
});
router.post("/getViewsByHour", async (req, res) => {
try {
const { days } = req.body;
@@ -516,29 +403,6 @@ const finalData = {libraries:libraries,stats:Object.values(reorganizedData)};
}
});
router.post("/getGlobalItemStats", async (req, res) => {
try {
const { hours,itemid } = req.body;
let _hours = hours;
if (hours === undefined) {
_hours = 24;
}
const { rows } = await db.query(
`select count(*)"Plays",
sum("PlaybackDuration") total_playback_duration
from jf_playback_activity jf_playback_activity
where
("EpisodeId"='${itemid}' OR "SeasonId"='${itemid}' OR "NowPlayingItemId"='${itemid}')
AND jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - INTERVAL '1 hour' * ${_hours} AND NOW();`
);
res.send(rows[0]);
} catch (error) {
console.log(error);
res.status(503);
res.send(error);
}
});

View File

@@ -1,6 +1,6 @@
const express = require("express");
const pgp = require("pg-promise")();
const db = require("./db");
const db = require("../db");
const axios = require("axios");
const https = require('https');
@@ -25,14 +25,14 @@ const { randomUUID } = require('crypto');
const router = express.Router();
const {jf_libraries_columns,jf_libraries_mapping,} = require("./models/jf_libraries");
const {jf_library_items_columns,jf_library_items_mapping,} = require("./models/jf_library_items");
const {jf_library_seasons_columns,jf_library_seasons_mapping,} = require("./models/jf_library_seasons");
const {jf_library_episodes_columns,jf_library_episodes_mapping,} = require("./models/jf_library_episodes");
const {jf_item_info_columns,jf_item_info_mapping,} = require("./models/jf_item_info");
const {columnsPlaybackReporting,mappingPlaybackReporting}= require("./models/jf_playback_reporting_plugin_data");
const {jf_libraries_columns,jf_libraries_mapping,} = require("../models/jf_libraries");
const {jf_library_items_columns,jf_library_items_mapping,} = require("../models/jf_library_items");
const {jf_library_seasons_columns,jf_library_seasons_mapping,} = require("../models/jf_library_seasons");
const {jf_library_episodes_columns,jf_library_episodes_mapping,} = require("../models/jf_library_episodes");
const {jf_item_info_columns,jf_item_info_mapping,} = require("../models/jf_item_info");
const {columnsPlaybackReporting,mappingPlaybackReporting}= require("../models/jf_playback_reporting_plugin_data");
const {jf_users_columns,jf_users_mapping,} = require("./models/jf_users");
const {jf_users_columns,jf_users_mapping,} = require("../models/jf_users");
/////////////////////////////////////////Functions

View File

@@ -5,16 +5,19 @@ const knex = require('knex');
const createdb = require('./create_database');
const knexConfig = require('./migrations');
const authRouter= require('./auth');
const apiRouter = require('./api');
const proxyRouter = require('./proxy');
const {router: syncRouter} = require('./sync');
const statsRouter = require('./stats');
const {router: backupRouter} = require('./backup');
const authRouter= require('./routes/auth');
const apiRouter = require('./routes/api');
const proxyRouter = require('./routes/proxy');
const {router: syncRouter} = require('./routes/sync');
const statsRouter = require('./routes/stats');
const {router: backupRouter} = require('./routes/backup');
const ActivityMonitor = require('./tasks/ActivityMonitor');
const SyncTask = require('./tasks/SyncTask');
const BackupTask = require('./tasks/BackupTask');
const {router: logRouter} = require('./logging');
const {router: logRouter} = require('./routes/logging');
const dbInstance = require("./db");
@@ -36,29 +39,70 @@ app.use(cors());
// JWT middleware
function verifyToken(req, res, next) {
const authHeader = req.headers.authorization;
if (authHeader) {
const token = authHeader.split(' ')[1];
jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) {
return res.sendStatus(403);
}
req.user = user;
async function authenticate (req, res, next) {
const token = req.headers.authorization;
const apiKey = req.headers['x-api-token'] || req.query.apiKey;
if (!token && !apiKey) {
return res.status(401).json({ message: 'Authentication failed. No token or API key provided.' });
}
if (token) {
const extracted_token=token.split(' ')[1];
if(!extracted_token || extracted_token==='null')
{
return res.sendStatus(403);
}
try {
const decoded = jwt.verify(extracted_token, JWT_SECRET);
req.user = decoded.user;
next();
});
} catch (error) {
console.log(error);
return res.status(401).json({ message: 'Invalid token' });
}
} else {
res.sendStatus(401);
if (apiKey) {
const keysjson = await dbInstance
.query('SELECT api_keys FROM app_config where "ID"=1')
.then((res) => res.rows[0].api_keys);
if(!keysjson || Object.keys(keysjson).length===0)
{
return res.status(404).json({ message: 'No API keys configured' });
}
const keys= keysjson || [];
const keyExists = keys.some(obj => obj.key === apiKey);
if(keyExists)
{
next();
}else
{
return res.status(403).json({ message: 'Invalid API key' });
}
}
}
}
app.use('/auth', authRouter); // mount the API router at /api, with JWT middleware
app.use('/api', verifyToken, apiRouter); // mount the API router at /api, with JWT middleware
app.use('/proxy', proxyRouter); // mount the API router at /api, with JWT middleware
app.use('/sync', verifyToken, syncRouter); // mount the API router at /sync, with JWT middleware
app.use('/stats', verifyToken, statsRouter); // mount the API router at /stats, with JWT middleware
app.use('/data', verifyToken, backupRouter); // mount the API router at /stats, with JWT middleware
app.use('/logs', verifyToken, logRouter); // mount the API router at /stats, with JWT middleware
app.use('/api', authenticate , apiRouter); // mount the API router at /api, with JWT middleware
app.use('/sync', authenticate , syncRouter); // mount the API router at /sync, with JWT middleware
app.use('/stats', authenticate , statsRouter); // mount the API router at /stats, with JWT middleware
app.use('/backup', authenticate , backupRouter); // mount the API router at /stats, with JWT middleware
app.use('/logs', authenticate , logRouter); // mount the API router at /stats, with JWT middleware
try{
createdb.createDatabase().then((result) => {

View File

@@ -1,7 +1,7 @@
const db = require("../db");
const Logging = require("../logging");
const Logging = require("../routes/logging");
const backup = require("../backup");
const backup = require("../routes/backup");
const moment = require('moment');
const { randomUUID } = require('crypto');

View File

@@ -1,6 +1,6 @@
const db = require("../db");
const moment = require('moment');
const sync = require("../sync");
const sync = require("../routes/sync");
async function SyncTask() {

View File

@@ -31,7 +31,7 @@ function LibraryInfo() {
const fetchData = async () => {
try {
console.log('getdata');
const libraryrData = await axios.post(`/stats/getLibraryDetails`, {
const libraryrData = await axios.post(`/api/getLibrary`, {
libraryid: LibraryId,
}, {
headers: {

View File

@@ -18,7 +18,7 @@ function RecentlyAdded(props) {
const fetchData = async () => {
try {
let url=`/stats/getRecentlyAdded`;
let url=`/proxy/getRecentlyAdded`;
if(props.LibraryId)
{
url+=`?libraryid=${props.LibraryId}`;

View File

@@ -27,7 +27,7 @@ function Sessions() {
const fetchData = () => {
if (config) {
const url = `/api/getSessions`;
const url = `/proxy/getSessions`;
axios
.get(url, {

View File

@@ -46,7 +46,7 @@ export default function Tasks() {
setProcessing(true);
await axios
.get("/data/backup", {
.get("/backup/beginBackup", {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",

View File

@@ -0,0 +1,229 @@
import React, { useState,useEffect } from "react";
import axios from "axios";
import {Form, Row, Col,ButtonGroup, 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 Alert from "react-bootstrap/Alert";
import "../../css/settings/backups.css";
const token = localStorage.getItem('token');
function CustomRow(key) {
const { data } = key;
async function deleteKey(keyvalue) {
const url=`/api/keys`;
axios
.delete(url, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
data: {
key: keyvalue,
},
})
.then((response) => {
const alert={visible:true,title:'Success',type:'success',message:response.data};
key.handleRowActionMessage(alert);
})
.catch((error) => {
const alert={visible:true,title:'Error',type:'danger',message:error.response.data};
key.handleRowActionMessage(alert);
});
}
return (
<React.Fragment>
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
<TableCell>{data.name}</TableCell>
<TableCell>{data.key}</TableCell>
<TableCell className="">
<div className="d-flex justify-content-center">
<Button variant="primary" onClick={()=>deleteKey(data.key)}>Delete</Button>
</div>
</TableCell>
</TableRow>
</React.Fragment>
);
}
export default function ApiKeys() {
const [keys, setKeys] = useState([]);
const [showAlert, setshowAlert] = useState({visible:false,type:'danger',title:'Error',message:''});
const [rowsPerPage] = React.useState(10);
const [page, setPage] = React.useState(0);
const [formValues, setFormValues] = useState({});
function handleCloseAlert() {
setshowAlert({visible:false});
}
useEffect(() => {
const fetchData = async () => {
try {
const apiKeyData = await axios.get(`/api/keys`, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
});
setKeys(apiKeyData.data);
console.log(apiKeyData);
console.log('KeyData');
} catch (error) {
console.log(error);
}
};
fetchData();
const intervalId = setInterval(fetchData, 1000 * 5);
return () => clearInterval(intervalId);
}, [token]);
const handleNextPageClick = () => {
setPage((prevPage) => prevPage + 1);
};
const handlePreviousPageClick = () => {
setPage((prevPage) => prevPage - 1);
};
const handleRowActionMessage= (alertState) => {
console.log(alertState);
setshowAlert({visible:alertState.visible,title:alertState.title,type:alertState.type,message:alertState.message});
};
function handleFormChange(event) {
setFormValues({ ...formValues, [event.target.name]: event.target.value });
}
async function handleFormSubmit(event) {
event.preventDefault();
axios
.post("/api/keys/", formValues, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
})
.then((response) => {
console.log("API key added successfully:", response.data);
})
.catch((error) => {
console.log("Error adding key:", error);
});
}
return (
<div>
<h1 className="my-2">API Keys</h1>
{showAlert && showAlert.visible && (
<Alert variant={showAlert.type} onClose={handleCloseAlert} dismissible>
<Alert.Heading>{showAlert.title}</Alert.Heading>
<p>
{showAlert.message}
</p>
</Alert>
)}
<Form onSubmit={handleFormSubmit} className="settings-form">
<Form.Group as={Row} className="mb-3" >
<Form.Label column>
KEY NAME
</Form.Label>
<Col sm="6" md="8">
<Form.Control className="w-100" id="name" name="name" value={formValues.name || ""} onChange={handleFormChange} placeholder="API Name" />
</Col>
<Col sm="4" md="2" className="mt-2 mt-sm-0">
<Button variant="outline-primary" type="submit" className="w-100"> Add Key </Button>
</Col>
</Form.Group>
</Form>
<TableContainer className='rounded-2'>
<Table aria-label="collapsible table" >
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell>Key</TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{keys && keys.sort((a, b) => a.name-b.name).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((apikey,index) => (
<CustomRow key={index} data={apikey} handleRowActionMessage={handleRowActionMessage}/>
))}
{keys.length===0 ? <tr><td colSpan="3" style={{ textAlign: "center", fontStyle: "italic" ,color:"grey"}} className='py-2'>No Keys Found</td></tr> :''}
</TableBody>
</Table>
</TableContainer>
<div className='d-flex justify-content-end my-2'>
<ButtonGroup className="pagination-buttons">
<Button className="page-btn" onClick={()=>setPage(0)} disabled={page === 0}>
First
</Button>
<Button className="page-btn" onClick={handlePreviousPageClick} disabled={page === 0}>
Previous
</Button>
<div className="page-number d-flex align-items-center justify-content-center">{`${page *rowsPerPage + 1}-${Math.min((page * rowsPerPage+ 1 ) + (rowsPerPage - 1),keys.length)} of ${keys.length}`}</div>
<Button className="page-btn" onClick={handleNextPageClick} disabled={page >= Math.ceil(keys.length / rowsPerPage) - 1}>
Next
</Button>
<Button className="page-btn" onClick={()=>setPage(Math.ceil(keys.length / rowsPerPage) - 1)} disabled={page >= Math.ceil(keys.length / rowsPerPage) - 1}>
Last
</Button>
</ButtonGroup>
</div>
</div>
);
}

View File

@@ -25,7 +25,7 @@ function Row(file) {
const [disabled, setDisabled] = useState(false);
async function downloadBackup(filename) {
const url=`/data/files/${filename}`;
const url=`/backup/files/${filename}`;
axios({
url: url,
headers: {
@@ -46,7 +46,7 @@ function Row(file) {
}
async function restoreBackup(filename) {
const url=`/data/restore/${filename}`;
const url=`/backup/restore/${filename}`;
setDisabled(true);
axios
.get(url, {
@@ -70,7 +70,7 @@ function Row(file) {
}
async function deleteBackup(filename) {
const url=`/data/files/${filename}`;
const url=`/backup/files/${filename}`;
setDisabled(true);
axios
.delete(url, {
@@ -174,7 +174,7 @@ const uploadFile = (file, onUploadProgress) => {
const formData = new FormData();
formData.append("file", file);
return axios.post("/data/upload", formData, {
return axios.post("/backup/upload", formData, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "multipart/form-data",
@@ -199,7 +199,7 @@ const handleFileSelect = (event) => {
useEffect(() => {
const fetchData = async () => {
try {
const backupFiles = await axios.get(`/data/files`, {
const backupFiles = await axios.get(`/backup/files`, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",

View File

@@ -61,7 +61,7 @@ export default function SettingsConfig() {
const fetchAdmins = async () => {
try {
const adminData = await axios.get(`/api/getAdminUsers`, {
const adminData = await axios.get(`/proxy/getAdminUsers`, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",

View File

@@ -28,10 +28,10 @@ function MPMovies(props) {
const fetchLibraries = () => {
if (config) {
const url = `/stats/getMostPopularMovies`;
const url = `/stats/getMostPopularByType`;
axios
.post(url, {days:props.days}, {
.post(url, {days:props.days, type:'Movie'}, {
headers: {
Authorization: `Bearer ${config.token}`,
"Content-Type": "application/json",

View File

@@ -26,10 +26,10 @@ function MPMusic(props) {
const fetchLibraries = () => {
if (config) {
const url = `/stats/getMostPopularMusic`;
const url = `/stats/getMostPopularByType`;
axios
.post(url, { days: props.days }, {
.post(url, { days: props.days, type:'Audio' }, {
headers: {
Authorization: `Bearer ${config.token}`,
"Content-Type": "application/json",

View File

@@ -24,10 +24,10 @@ function MPSeries(props) {
const fetchLibraries = () => {
if (config) {
const url = `/stats/getMostPopularSeries`;
const url = `/stats/getMostPopularByType`;
axios
.post(url, { days: props.days }, {
.post(url, { days: props.days, type:'Series' }, {
headers: {
Authorization: `Bearer ${config.token}`,
"Content-Type": "application/json",

View File

@@ -29,10 +29,10 @@ function MVMusic(props) {
const fetchLibraries = () => {
if (config) {
const url = `/stats/getMostViewedMovies`;
const url = `/stats/getMostViewedByType`;
axios
.post(url, {days:props.days}, {
.post(url, {days:props.days, type:'Movie'}, {
headers: {
Authorization: `Bearer ${config.token}`,
"Content-Type": "application/json",

View File

@@ -24,10 +24,10 @@ function MVMovies(props) {
const fetchLibraries = () => {
if (config) {
const url = `/stats/getMostViewedMusic`;
const url = `/stats/getMostViewedByType`;
axios
.post(url, {days:props.days}, {
.post(url, {days:props.days, type:'Audio'}, {
headers: {
Authorization: `Bearer ${config.token}`,
"Content-Type": "application/json",

View File

@@ -27,10 +27,10 @@ function MVSeries(props) {
const fetchLibraries = () => {
if (config) {
const url = `/stats/getMostViewedSeries`;
const url = `/stats/getMostViewedByType`;
axios
.post(url, {days:props.days}, {
.post(url, {days:props.days, type:'Series'}, {
headers: {
Authorization: `Bearer ${config.token}`,
"Content-Type": "application/json",

View File

@@ -35,7 +35,7 @@ function UserInfo() {
const fetchData = async () => {
if(config){
try {
const userData = await axios.post(`/stats/getUserDetails`, {
const userData = await axios.post(`/api/getUserDetails`, {
userid: UserId,
}, {
headers: {

View File

@@ -5,6 +5,7 @@ import SettingsConfig from "./components/settings/settingsConfig";
import Tasks from "./components/settings/Tasks";
import BackupFiles from "./components/settings/backupfiles";
import SecuritySettings from "./components/settings/security";
import ApiKeys from "./components/settings/apiKeys";
import Logs from "./components/settings/logs";
@@ -34,6 +35,10 @@ export default function Settings() {
<BackupFiles/>
</Tab>
<Tab eventKey="tabKeys" className='bg-transparent my-2' title='API Keys' style={{minHeight:'500px'}}>
<ApiKeys/>
</Tab>
<Tab eventKey="tabLogs" className='bg-transparent my-2' title='Logs' style={{minHeight:'500px'}}>
<Logs/>
</Tab>

View File

@@ -37,7 +37,7 @@ module.exports = function(app) {
})
);
app.use(
`/data`,
`/backup`,
createProxyMiddleware({
target: `http://127.0.0.1:${process.env.PORT || 3003}`,
changeOrigin: true,