mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
Updated swagger docs
added basic constrains to api endpoint added translations added purge option for Entire archived library or just archived items inside library Fixed some pages that where throwing virtualDom errors added filter to show archived, not archived and all items under library items fixed no title error for tab views by giving them titles and hiding it with css
This commit is contained in:
@@ -46,6 +46,40 @@ function groupActivity(rows) {
|
||||
return groupedResults;
|
||||
}
|
||||
|
||||
async function purgeLibraryItems(id, withActivity) {
|
||||
const { rows: items } = await db.query(`select * from jf_library_items where "ParentId"=$1 and archived=true`, [id]);
|
||||
let seasonIds = [];
|
||||
let episodeIds = [];
|
||||
|
||||
for (const item of items) {
|
||||
const { rows: seasons } = await db.query(`select * from jf_library_seasons where "SeriesId"=$1 and archived=true`, [item.Id]);
|
||||
seasonIds.push(...seasons.map((item) => item.Id));
|
||||
const { rows: episodes } = await db.query(`select * from jf_library_episodes where "SeriesId"=$1 and archived=true`, [
|
||||
item.Id,
|
||||
]);
|
||||
episodeIds.push(...episodes.map((item) => item.Id));
|
||||
}
|
||||
|
||||
if (episodeIds.length > 0) {
|
||||
await db.deleteBulk("jf_library_episodes", episodeIds);
|
||||
}
|
||||
|
||||
if (seasonIds.length > 0) {
|
||||
await db.deleteBulk("jf_library_seasons", seasonIds);
|
||||
}
|
||||
|
||||
await db.query(`delete from jf_library_items where "ParentId"=$1 and archived=true`, [id]);
|
||||
|
||||
if (withActivity) {
|
||||
const deleteQuery = {
|
||||
text: `DELETE FROM jf_playback_activity WHERE${
|
||||
episodeIds.length > 0 ? ` "EpisodeId" IN (${pgp.as.csv(episodeIds)}) OR` : ""
|
||||
}${seasonIds.length > 0 ? ` "SeasonId" IN (${pgp.as.csv(seasonIds)}) OR` : ""} "NowPlayingItemId"='${id}'`,
|
||||
};
|
||||
await db.query(deleteQuery);
|
||||
}
|
||||
}
|
||||
|
||||
router.get("/getconfig", async (req, res) => {
|
||||
try {
|
||||
const config = await new configClass().getConfig();
|
||||
@@ -72,6 +106,12 @@ router.post("/setconfig", async (req, res) => {
|
||||
try {
|
||||
const { JF_HOST, JF_API_KEY } = req.body;
|
||||
|
||||
if (JF_HOST === undefined && JF_API_KEY === undefined) {
|
||||
res.status(400);
|
||||
res.send("JF_HOST and JF_API_KEY are required for configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
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';
|
||||
@@ -89,6 +129,12 @@ router.post("/setPreferredAdmin", async (req, res) => {
|
||||
try {
|
||||
const { userid, username } = req.body;
|
||||
|
||||
if (userid === undefined && username === undefined) {
|
||||
res.status(400);
|
||||
res.send("A valid userid and username is required for preferred admin");
|
||||
return;
|
||||
}
|
||||
|
||||
const settingsjson = await db.query('SELECT settings FROM app_config where "ID"=1').then((res) => res.rows);
|
||||
|
||||
if (settingsjson.length > 0) {
|
||||
@@ -116,15 +162,14 @@ router.post("/setRequireLogin", async (req, res) => {
|
||||
try {
|
||||
const { REQUIRE_LOGIN } = req.body;
|
||||
|
||||
if (REQUIRE_LOGIN === undefined) {
|
||||
res.status(503);
|
||||
res.send(rows);
|
||||
if (REQUIRE_LOGIN === undefined || typeof REQUIRE_LOGIN !== "boolean") {
|
||||
res.status(400);
|
||||
res.send("A valid value(true/false) is required for REQUIRE_LOGIN");
|
||||
return;
|
||||
}
|
||||
|
||||
let query = 'UPDATE app_config SET "REQUIRE_LOGIN"=$1 where "ID"=1';
|
||||
|
||||
console.log(`ENDPOINT CALLED: /setRequireLogin: ` + REQUIRE_LOGIN);
|
||||
|
||||
const { rows } = await db.query(query, [REQUIRE_LOGIN]);
|
||||
res.send(rows);
|
||||
} catch (error) {
|
||||
@@ -252,6 +297,12 @@ router.get("/TrackedLibraries", async (req, res) => {
|
||||
router.post("/setExcludedLibraries", async (req, res) => {
|
||||
const { libraryID } = req.body;
|
||||
|
||||
if (libraryID === undefined) {
|
||||
res.status(400);
|
||||
res.send("No Library Id provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const settingsjson = await db.query('SELECT settings FROM app_config where "ID"=1').then((res) => res.rows);
|
||||
|
||||
if (settingsjson.length > 0) {
|
||||
@@ -357,6 +408,13 @@ router.delete("/keys", async (req, res) => {
|
||||
|
||||
router.post("/keys", async (req, res) => {
|
||||
const { name } = req.body;
|
||||
|
||||
if (name === undefined) {
|
||||
res.status(400);
|
||||
res.send("Key Name is required to generate a key");
|
||||
return;
|
||||
}
|
||||
|
||||
const config = await new configClass().getConfig();
|
||||
|
||||
if (!name) {
|
||||
@@ -400,6 +458,12 @@ router.get("/getTaskSettings", async (req, res) => {
|
||||
router.post("/setTaskSettings", async (req, res) => {
|
||||
const { taskname, Interval } = req.body;
|
||||
|
||||
if (taskname === undefined || Interval === undefined) {
|
||||
res.status(400);
|
||||
res.send("Task Name and Interval are required");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const settingsjson = await db.query('SELECT settings FROM app_config where "ID"=1').then((res) => res.rows);
|
||||
|
||||
@@ -446,6 +510,13 @@ router.get("/CheckForUpdates", async (req, res) => {
|
||||
router.post("/getUserDetails", async (req, res) => {
|
||||
try {
|
||||
const { userid } = req.body;
|
||||
|
||||
if (userid === undefined) {
|
||||
res.status(400);
|
||||
res.send("No User Id provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const { rows } = await db.query(`select * from jf_users where "Id"='${userid}'`);
|
||||
res.send(rows[0]);
|
||||
} catch (error) {
|
||||
@@ -467,6 +538,13 @@ router.get("/getLibraries", async (req, res) => {
|
||||
router.post("/getLibrary", async (req, res) => {
|
||||
try {
|
||||
const { libraryid } = req.body;
|
||||
|
||||
if (libraryid === undefined) {
|
||||
res.status(400);
|
||||
res.send("No Library Id provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const { rows } = await db.query(`select * from jf_libraries where "Id"='${libraryid}'`);
|
||||
res.send(rows[0]);
|
||||
} catch (error) {
|
||||
@@ -479,6 +557,13 @@ router.post("/getLibrary", async (req, res) => {
|
||||
router.post("/getLibraryItems", async (req, res) => {
|
||||
try {
|
||||
const { libraryid } = req.body;
|
||||
|
||||
if (libraryid === undefined) {
|
||||
res.status(400);
|
||||
res.send("No Library Id provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const { rows } = await db.query(`SELECT * FROM jf_library_items where "ParentId"=$1`, [libraryid]);
|
||||
res.send(rows);
|
||||
} catch (error) {
|
||||
@@ -490,6 +575,12 @@ router.post("/getSeasons", async (req, res) => {
|
||||
try {
|
||||
const { Id } = req.body;
|
||||
|
||||
if (Id === undefined) {
|
||||
res.status(400);
|
||||
res.send("No Season Id provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const { rows } = await db.query(
|
||||
`SELECT s.*,i.archived, i."PrimaryImageHash", (select count(e.*) "Episodes" from jf_library_episodes e where e."SeasonId"=s."Id") ,(select sum(ii."Size") "Size" from jf_library_episodes e join jf_item_info ii on ii."Id"=e."EpisodeId" where e."SeasonId"=s."Id") FROM jf_library_seasons s left join jf_library_items i on i."Id"=s."SeriesId" where "SeriesId"=$1`,
|
||||
[Id]
|
||||
@@ -503,6 +594,13 @@ router.post("/getSeasons", async (req, res) => {
|
||||
router.post("/getEpisodes", async (req, res) => {
|
||||
try {
|
||||
const { Id } = req.body;
|
||||
|
||||
if (Id === undefined) {
|
||||
res.status(400);
|
||||
res.send("No Episode Id provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const { rows } = await db.query(
|
||||
`SELECT e.*,i.archived, i."PrimaryImageHash" FROM jf_library_episodes e left join jf_library_items i on i."Id"=e."SeriesId" where "SeasonId"=$1`,
|
||||
[Id]
|
||||
@@ -516,6 +614,11 @@ router.post("/getEpisodes", async (req, res) => {
|
||||
router.post("/getItemDetails", async (req, res) => {
|
||||
try {
|
||||
const { Id } = req.body;
|
||||
if (Id === undefined) {
|
||||
res.status(400);
|
||||
res.send("No ID provided");
|
||||
return;
|
||||
}
|
||||
// let query = `SELECT im."Name" "FileName",im.*,i.* FROM jf_library_items i left join jf_item_info im on i."Id" = im."Id" where i."Id"=$1`;
|
||||
let query = `SELECT im."Name" "FileName",im."Id",im."Path",im."Name",im."Bitrate",im."MediaStreams",im."Type", COALESCE(im."Size" ,(SELECT SUM(im."Size") FROM jf_library_seasons s JOIN jf_library_episodes e on s."Id"=e."SeasonId" JOIN jf_item_info im ON im."Id" = e."EpisodeId" WHERE s."SeriesId" = i."Id")) "Size",i.*, (select "Name" from jf_libraries l where l."Id"=i."ParentId") "LibraryName" FROM jf_library_items i left join jf_item_info im on i."Id" = im."Id" where i."Id"=$1`;
|
||||
|
||||
@@ -550,6 +653,12 @@ router.delete("/item/purge", async (req, res) => {
|
||||
try {
|
||||
const { id, withActivity } = req.body;
|
||||
|
||||
if (id === undefined) {
|
||||
res.status(400);
|
||||
res.send("No Item ID provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const { rows: episodes } = await db.query(`select * from jf_library_episodes where "SeriesId"=$1`, [id]);
|
||||
if (episodes.length > 0) {
|
||||
await db.query(`delete from jf_library_episodes where "SeriesId"=$1`, [id]);
|
||||
@@ -587,6 +696,59 @@ router.delete("/item/purge", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/library/purge", async (req, res) => {
|
||||
try {
|
||||
const { id, withActivity } = req.body;
|
||||
|
||||
if (id === undefined) {
|
||||
res.status(400);
|
||||
res.send("No Library ID provided");
|
||||
return;
|
||||
}
|
||||
|
||||
await purgeLibraryItems(id, withActivity);
|
||||
|
||||
await db.query(`delete from jf_libraries where "Id"=$1`, [id]);
|
||||
|
||||
sendUpdate("GeneralAlert", {
|
||||
type: "Success",
|
||||
message: `Library ${withActivity ? "with Playback Activity" : ""} has been Purged`,
|
||||
});
|
||||
res.send("Item purged succesfully");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
sendUpdate("GeneralAlert", { type: "Error", message: `There was an error Purging the Data` });
|
||||
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/libraryItems/purge", async (req, res) => {
|
||||
try {
|
||||
const { id, withActivity } = req.body;
|
||||
if (id === undefined) {
|
||||
res.status(400);
|
||||
res.send("No Library ID provided");
|
||||
return;
|
||||
}
|
||||
|
||||
await purgeLibraryItems(id, withActivity);
|
||||
|
||||
sendUpdate("GeneralAlert", {
|
||||
type: "Success",
|
||||
message: `Library Items ${withActivity ? "with Playback Activity" : ""} has been Purged`,
|
||||
});
|
||||
res.send("Item purged succesfully");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
sendUpdate("GeneralAlert", { type: "Error", message: `There was an error Purging the Data` });
|
||||
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
//DB Queries - History
|
||||
router.get("/getHistory", async (req, res) => {
|
||||
try {
|
||||
@@ -603,6 +765,13 @@ router.get("/getHistory", async (req, res) => {
|
||||
router.post("/getLibraryHistory", async (req, res) => {
|
||||
try {
|
||||
const { libraryid } = req.body;
|
||||
|
||||
if (libraryid === undefined) {
|
||||
res.status(400);
|
||||
res.send("No Library ID provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const { rows } = await db.query(
|
||||
`select a.* from jf_playback_activity a join jf_library_items i on i."Id"=a."NowPlayingItemId" where i."ParentId"=$1 order by "ActivityDateInserted" desc`,
|
||||
[libraryid]
|
||||
@@ -620,6 +789,12 @@ router.post("/getItemHistory", async (req, res) => {
|
||||
try {
|
||||
const { itemid } = req.body;
|
||||
|
||||
if (itemid === undefined) {
|
||||
res.status(400);
|
||||
res.send("No Item ID provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const { rows } = await db.query(
|
||||
`select jf_playback_activity.*
|
||||
from jf_playback_activity jf_playback_activity
|
||||
@@ -645,6 +820,12 @@ router.post("/getUserHistory", async (req, res) => {
|
||||
try {
|
||||
const { userid } = req.body;
|
||||
|
||||
if (userid === undefined) {
|
||||
res.status(400);
|
||||
res.send("No User ID provided");
|
||||
return;
|
||||
}
|
||||
|
||||
const { rows } = await db.query(
|
||||
`select jf_playback_activity.*
|
||||
from jf_playback_activity jf_playback_activity
|
||||
@@ -667,6 +848,12 @@ router.post("/getUserHistory", async (req, res) => {
|
||||
router.post("/validateSettings", async (req, res) => {
|
||||
const { url, apikey } = req.body;
|
||||
|
||||
if (url === undefined || apikey === undefined) {
|
||||
res.status(400);
|
||||
res.send("URL or API Key not provided");
|
||||
return;
|
||||
}
|
||||
|
||||
var _url = url;
|
||||
_url = _url.replace(/\/web\/index\.html#!\/home\.html$/, "");
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
|
||||
@@ -396,6 +396,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -450,6 +453,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -501,6 +507,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -509,9 +518,6 @@
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,6 +720,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1049,6 +1058,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1142,6 +1154,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1235,6 +1250,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1289,6 +1307,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1340,6 +1361,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1391,6 +1415,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1442,6 +1469,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1496,6 +1526,129 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/library/purge": {
|
||||
"delete": {
|
||||
"tags": [
|
||||
"API"
|
||||
],
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "authorization",
|
||||
"in": "header",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "x-api-token",
|
||||
"in": "header",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "req",
|
||||
"in": "query",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"example": "any"
|
||||
},
|
||||
"withActivity": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/libraryItems/purge": {
|
||||
"delete": {
|
||||
"tags": [
|
||||
"API"
|
||||
],
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "authorization",
|
||||
"in": "header",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "x-api-token",
|
||||
"in": "header",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "req",
|
||||
"in": "query",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"example": "any"
|
||||
},
|
||||
"withActivity": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1589,6 +1742,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1643,6 +1799,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1697,6 +1856,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
@@ -1754,6 +1916,9 @@
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
|
||||
@@ -92,7 +92,11 @@
|
||||
},
|
||||
"PURGE_OPTIONS": {
|
||||
"PURGE_CACHE": "Purge Cached Item",
|
||||
"PURGE_CACHE_WITH_ACTIVITY": "Purge Cached Item and Playback Activity"
|
||||
"PURGE_CACHE_WITH_ACTIVITY": "Purge Cached Item and Playback Activity",
|
||||
"PURGE_LIBRARY_CACHE": "Purge Cached Library and Items",
|
||||
"PURGE_LIBRARY_CACHE_WITH_ACTIVITY": "Purge Cached Library, Items and Activity",
|
||||
"PURGE_LIBRARY_ITEMS_CACHE": "Purge Cached Library Items Only",
|
||||
"PURGE_LIBRARY_ITEMS_CACHE_WITH_ACTIVITY": "Purge Cached Library Items and Activity Only"
|
||||
},
|
||||
"ERROR_MESSAGES": {
|
||||
"FETCH_THIS_ITEM": "Fetch this item from Jellyfin",
|
||||
@@ -211,6 +215,8 @@
|
||||
"TYPE": "Type",
|
||||
"NEW_VERSION_AVAILABLE": "New version available",
|
||||
"ARCHIVED": "Archived",
|
||||
"NOT_ARCHIVED": "Not Archived",
|
||||
"ALL": "All",
|
||||
"CLOSE": "Close",
|
||||
"TOTAL_PLAYS": "Total Plays",
|
||||
"TITLE": "Title",
|
||||
|
||||
@@ -147,3 +147,7 @@ h2 {
|
||||
.bg-primary-1 {
|
||||
background-color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.hide-tab-titles {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -1,95 +1,99 @@
|
||||
import {useState} from "react";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Blurhash } from 'react-blurhash';
|
||||
import ArchiveDrawerFillIcon from 'remixicon-react/ArchiveDrawerFillIcon';
|
||||
import { Blurhash } from "react-blurhash";
|
||||
import ArchiveDrawerFillIcon from "remixicon-react/ArchiveDrawerFillIcon";
|
||||
|
||||
import "../../css/lastplayed.css";
|
||||
import { Trans } from "react-i18next";
|
||||
import i18next from "i18next";
|
||||
|
||||
function formatTime(time) {
|
||||
const units = {
|
||||
days: [i18next.t("UNITS.DAY"), i18next.t("UNITS.DAYS")],
|
||||
hours: [i18next.t("UNITS.HOUR"), i18next.t("UNITS.HOUR")],
|
||||
minutes: [i18next.t("UNITS.MINUTE"), i18next.t("UNITS.MINUTES")],
|
||||
seconds: [i18next.t("UNITS.SECOND"), i18next.t("UNITS.SECONDS")],
|
||||
};
|
||||
|
||||
const units = {
|
||||
days: [i18next.t("UNITS.DAY"), i18next.t("UNITS.DAYS")],
|
||||
hours: [i18next.t("UNITS.HOUR"), i18next.t("UNITS.HOUR")],
|
||||
minutes: [i18next.t("UNITS.MINUTE"), i18next.t("UNITS.MINUTES")],
|
||||
seconds: [i18next.t("UNITS.SECOND"), i18next.t("UNITS.SECONDS")]
|
||||
};
|
||||
let formattedTime = "";
|
||||
|
||||
let formattedTime = '';
|
||||
|
||||
if (time.days) {
|
||||
formattedTime = `${time.days} ${units.days[time.days > 1 ? 1 : 0]}`;
|
||||
} else if (time.hours) {
|
||||
formattedTime = `${time.hours} ${units.hours[time.hours > 1 ? 1 : 0]}`;
|
||||
} else if (time.minutes) {
|
||||
formattedTime = `${time.minutes} ${units.minutes[time.minutes > 1 ? 1 : 0]}`;
|
||||
} else {
|
||||
formattedTime = `${time.seconds} ${units.seconds[time.seconds > 1 ? 1 : 0]}`;
|
||||
}
|
||||
|
||||
return `${formattedTime+' '+i18next.t("AGO").toLowerCase()}`;
|
||||
if (time.days) {
|
||||
formattedTime = `${time.days} ${units.days[time.days > 1 ? 1 : 0]}`;
|
||||
} else if (time.hours) {
|
||||
formattedTime = `${time.hours} ${units.hours[time.hours > 1 ? 1 : 0]}`;
|
||||
} else if (time.minutes) {
|
||||
formattedTime = `${time.minutes} ${units.minutes[time.minutes > 1 ? 1 : 0]}`;
|
||||
} else {
|
||||
formattedTime = `${time.seconds} ${units.seconds[time.seconds > 1 ? 1 : 0]}`;
|
||||
}
|
||||
|
||||
return `${formattedTime + " " + i18next.t("AGO").toLowerCase()}`;
|
||||
}
|
||||
|
||||
function LastWatchedCard(props) {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="last-card">
|
||||
<Link to={`/libraries/item/${props.data.EpisodeId||props.data.Id}`}>
|
||||
<div className="last-card-banner">
|
||||
{props.data.archived && loaded && props.data.PrimaryImageHash && props.data.PrimaryImageHash!=null ? <Blurhash hash={props.data.PrimaryImageHash} width={'100%'} height={'100%'} className="rounded-3 overflow-hidden"/> : null}
|
||||
{!props.data.archived ?
|
||||
<img
|
||||
src={
|
||||
`${
|
||||
"/proxy/Items/Images/Primary?id=" +
|
||||
props.data.Id +
|
||||
"&fillHeight=320&fillWidth=213&quality=50"}`
|
||||
}
|
||||
alt=""
|
||||
onLoad={() => setLoaded(true)}
|
||||
style={loaded ? { backgroundImage: `url(path/to/image.jpg)` } : { display: 'none' }}
|
||||
/>
|
||||
:
|
||||
<div className="d-flex flex-column justify-content-center align-items-center position-relative" style={{height: '100%'}}>
|
||||
{((props.data.ImageBlurHashes && props.data.ImageBlurHashes!=null) || (props.data.PrimaryImageHash && props.data.PrimaryImageHash!=null) )?
|
||||
<Blurhash hash={props.data.PrimaryImageHash || props.data.ImageBlurHashes.Primary[props.data.ImageTags.Primary] } width={'100%'} height={'100%'} className="rounded-top-3 overflow-hidden position-absolute"/>
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className="d-flex flex-column justify-content-center align-items-center position-absolute">
|
||||
<ArchiveDrawerFillIcon className="w-100 h-100 mb-2"/>
|
||||
<span><Trans i18nKey="ARCHIVED"/></span>
|
||||
<Link to={`/libraries/item/${props.data.EpisodeId || props.data.Id}`}>
|
||||
<div className="last-card-banner">
|
||||
{props.data.archived && loaded && props.data.PrimaryImageHash && props.data.PrimaryImageHash != null ? (
|
||||
<Blurhash hash={props.data.PrimaryImageHash} width={"100%"} height={"100%"} className="rounded-3 overflow-hidden" />
|
||||
) : null}
|
||||
{!props.data.archived ? (
|
||||
<img
|
||||
src={`${"/proxy/Items/Images/Primary?id=" + props.data.Id + "&fillHeight=320&fillWidth=213&quality=50"}`}
|
||||
alt=""
|
||||
onLoad={() => setLoaded(true)}
|
||||
style={loaded ? { display: "block" } : { display: "none" }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="d-flex flex-column justify-content-center align-items-center position-relative"
|
||||
style={{ height: "100%" }}
|
||||
>
|
||||
{(props.data.ImageBlurHashes && props.data.ImageBlurHashes != null) ||
|
||||
(props.data.PrimaryImageHash && props.data.PrimaryImageHash != null) ? (
|
||||
<Blurhash
|
||||
hash={props.data.PrimaryImageHash || props.data.ImageBlurHashes.Primary[props.data.ImageTags.Primary]}
|
||||
width={"100%"}
|
||||
height={"100%"}
|
||||
className="rounded-top-3 overflow-hidden position-absolute"
|
||||
/>
|
||||
) : null}
|
||||
<div className="d-flex flex-column justify-content-center align-items-center position-absolute">
|
||||
<ArchiveDrawerFillIcon className="w-100 h-100 mb-2" />
|
||||
<span>
|
||||
<Trans i18nKey="ARCHIVED" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Link>
|
||||
</Link>
|
||||
|
||||
<div className="last-item-details">
|
||||
<div className="last-last-played">
|
||||
{formatTime(props.data.LastPlayed)}
|
||||
</div>
|
||||
<div className="last-last-played">{formatTime(props.data.LastPlayed)}</div>
|
||||
|
||||
<div className="pb-2">
|
||||
<Link to={`/users/${props.data.UserId}`}>
|
||||
{props.data.UserName}
|
||||
</Link>
|
||||
<Link to={`/users/${props.data.UserId}`}>{props.data.UserName}</Link>
|
||||
</div>
|
||||
|
||||
<div className="last-item-name">
|
||||
<Link to={`/libraries/item/${props.data.Id}`}>{props.data.Name}</Link>
|
||||
</div>
|
||||
<div className="last-item-episode">
|
||||
<Link to={`/libraries/item/${props.data.EpisodeId}`}>{props.data.EpisodeName}</Link></div>
|
||||
<Link to={`/libraries/item/${props.data.EpisodeId}`}>{props.data.EpisodeName}</Link>
|
||||
</div>
|
||||
</div>
|
||||
{props.data.SeasonNumber ?
|
||||
<div className="last-item-episode number"> S{props.data.SeasonNumber} - E{props.data.EpisodeNumber}</div>:
|
||||
<></>
|
||||
}
|
||||
|
||||
{props.data.SeasonNumber ? (
|
||||
<div className="last-item-episode number">
|
||||
{" "}
|
||||
S{props.data.SeasonNumber} - E{props.data.EpisodeNumber}
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,20 +101,18 @@ export default function IpInfoModal(props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal show={props.show} onHide={() => props.onHide()}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<Trans i18nKey={"GEOLOCATION_INFO_FOR"} /> {props.ipAddress}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
{modalBody}
|
||||
<Modal.Footer>
|
||||
<Button variant="outline-primary" onClick={() => props.onHide()}>
|
||||
<Trans i18nKey={"CLOSE"} />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
<Modal show={props.show} onHide={() => props.onHide()}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<Trans i18nKey={"GEOLOCATION_INFO_FOR"} /> {props.ipAddress}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
{modalBody}
|
||||
<Modal.Footer>
|
||||
<Button variant="outline-primary" onClick={() => props.onHide()}>
|
||||
<Trans i18nKey={"CLOSE"} />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ function ItemInfo() {
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log(itemData.data[0]);
|
||||
|
||||
setData(itemData.data[0]);
|
||||
} catch (error) {
|
||||
setData({ notfound: true, message: error.response.data });
|
||||
@@ -312,14 +312,14 @@ function ItemInfo() {
|
||||
</div>
|
||||
|
||||
<Tabs defaultActiveKey="tabOverview" activeKey={activeTab} variant="pills" className="hide-tab-titles">
|
||||
<Tab eventKey="tabOverview" title="" className="bg-transparent">
|
||||
<Tab eventKey="tabOverview" title="Overview" className="bg-transparent">
|
||||
<GlobalStats ItemId={Id} />
|
||||
{["Series", "Season"].includes(data && data.Type) ? <MoreItems data={data} /> : <></>}
|
||||
</Tab>
|
||||
<Tab eventKey="tabActivity" title="" className="bg-transparent">
|
||||
<Tab eventKey="tabActivity" title="Activity" className="bg-transparent">
|
||||
<ItemActivity itemid={Id} />
|
||||
</Tab>
|
||||
<Tab eventKey="tabOptions" title="" className="bg-transparent">
|
||||
<Tab eventKey="tabOptions" title="Options" className="bg-transparent">
|
||||
<ItemOptions itemid={Id} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -45,7 +45,7 @@ function MoreItemCards(props) {
|
||||
src={`${"/proxy/Items/Images/Primary?id=" + Id + "&fillHeight=320&fillWidth=213&quality=50"}`}
|
||||
alt=""
|
||||
onLoad={() => setLoaded(true)}
|
||||
style={loaded ? { backgroundImage: `url(path/to/image.jpg)` } : { display: "none" }}
|
||||
style={loaded ? { display: "block" } : { display: "none" }}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
@@ -57,7 +57,7 @@ function MoreItemCards(props) {
|
||||
alt=""
|
||||
onLoad={() => setLoaded(true)}
|
||||
onError={() => setFallback(true)}
|
||||
style={loaded ? { backgroundImage: `url(path/to/image.jpg)` } : { display: "none" }}
|
||||
style={loaded ? { display: "block" } : { display: "none" }}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
|
||||
@@ -1,45 +1,43 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import TvLineIcon from "remixicon-react/TvLineIcon";
|
||||
import FilmLineIcon from "remixicon-react/FilmLineIcon";
|
||||
|
||||
|
||||
// import LibraryDetails from './library/library-details';
|
||||
import Loading from './general/loading';
|
||||
import LibraryGlobalStats from './library/library-stats';
|
||||
import LibraryLastWatched from './library/last-watched';
|
||||
import RecentlyAdded from './library/recently-added';
|
||||
import LibraryActivity from './library/library-activity';
|
||||
import LibraryItems from './library/library-items';
|
||||
import ErrorBoundary from './general/ErrorBoundary';
|
||||
|
||||
import { Tabs, Tab, Button, ButtonGroup } from 'react-bootstrap';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
|
||||
|
||||
import Loading from "./general/loading";
|
||||
import LibraryGlobalStats from "./library/library-stats";
|
||||
import LibraryLastWatched from "./library/last-watched";
|
||||
import RecentlyAdded from "./library/recently-added";
|
||||
import LibraryActivity from "./library/library-activity";
|
||||
import LibraryItems from "./library/library-items";
|
||||
import ErrorBoundary from "./general/ErrorBoundary";
|
||||
|
||||
import { Tabs, Tab, Button, ButtonGroup } from "react-bootstrap";
|
||||
import { Trans } from "react-i18next";
|
||||
import LibraryOptions from "./library/library-options";
|
||||
|
||||
function LibraryInfo() {
|
||||
const { LibraryId } = useParams();
|
||||
const [activeTab, setActiveTab] = useState('tabOverview');
|
||||
const [activeTab, setActiveTab] = useState("tabOverview");
|
||||
const [data, setData] = useState();
|
||||
const token = localStorage.getItem('token');
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
console.log('getdata');
|
||||
const libraryrData = await axios.post(`/api/getLibrary`, {
|
||||
libraryid: LibraryId,
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
const libraryrData = await axios.post(
|
||||
`/api/getLibrary`,
|
||||
{
|
||||
libraryid: LibraryId,
|
||||
},
|
||||
});
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
setData(libraryrData.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -50,55 +48,79 @@ function LibraryInfo() {
|
||||
|
||||
const intervalId = setInterval(fetchData, 60000 * 5);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [LibraryId,token]);
|
||||
}, [LibraryId, token]);
|
||||
|
||||
if(!data)
|
||||
{
|
||||
return <Loading/>;
|
||||
if (!data) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
|
||||
<div className="user-detail-container">
|
||||
<div className="user-image-container">
|
||||
{data.CollectionType==="tvshows" ?
|
||||
|
||||
<TvLineIcon size={'100%'}/>
|
||||
:
|
||||
<FilmLineIcon size={'100%'}/>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<p className="user-name">{data.Name}</p>
|
||||
<div className="user-detail-container">
|
||||
<div className="user-image-container">
|
||||
{data.CollectionType === "tvshows" ? <TvLineIcon size={"100%"} /> : <FilmLineIcon size={"100%"} />}
|
||||
</div>
|
||||
<div>
|
||||
<p className="user-name">{data.Name}</p>
|
||||
<ButtonGroup>
|
||||
<Button onClick={() => setActiveTab('tabOverview')} active={activeTab==='tabOverview'} variant='outline-primary' type='button'><Trans i18nKey="TAB_CONTROLS.OVERVIEW"/></Button>
|
||||
<Button onClick={() => setActiveTab('tabItems')} active={activeTab==='tabItems'} variant='outline-primary' type='button'><Trans i18nKey="MEDIA"/></Button>
|
||||
<Button onClick={() => setActiveTab('tabActivity')} active={activeTab==='tabActivity'} variant='outline-primary' type='button'><Trans i18nKey="TAB_CONTROLS.ACTIVITY"/></Button>
|
||||
<Button
|
||||
onClick={() => setActiveTab("tabOverview")}
|
||||
active={activeTab === "tabOverview"}
|
||||
variant="outline-primary"
|
||||
type="button"
|
||||
>
|
||||
<Trans i18nKey="TAB_CONTROLS.OVERVIEW" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setActiveTab("tabItems")}
|
||||
active={activeTab === "tabItems"}
|
||||
variant="outline-primary"
|
||||
type="button"
|
||||
>
|
||||
<Trans i18nKey="MEDIA" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setActiveTab("tabActivity")}
|
||||
active={activeTab === "tabActivity"}
|
||||
variant="outline-primary"
|
||||
type="button"
|
||||
>
|
||||
<Trans i18nKey="TAB_CONTROLS.ACTIVITY" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => setActiveTab("tabOptions")}
|
||||
active={activeTab === "tabOptions"}
|
||||
variant="outline-primary"
|
||||
type="button"
|
||||
>
|
||||
<Trans i18nKey="TAB_CONTROLS.OPTIONS" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs defaultActiveKey="tabOverview" activeKey={activeTab} variant="pills" className="hide-tab-titles">
|
||||
<Tab eventKey="tabOverview" title="Overview" className="bg-transparent">
|
||||
<LibraryGlobalStats LibraryId={LibraryId} />
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<Tabs defaultActiveKey="tabOverview" activeKey={activeTab} variant='pills'>
|
||||
<Tab eventKey="tabOverview" className='bg-transparent'>
|
||||
<LibraryGlobalStats LibraryId={LibraryId}/>
|
||||
{!data.archived && (
|
||||
<ErrorBoundary>
|
||||
<RecentlyAdded LibraryId={LibraryId}/>
|
||||
<RecentlyAdded LibraryId={LibraryId} />
|
||||
</ErrorBoundary>
|
||||
|
||||
<LibraryLastWatched LibraryId={LibraryId}/>
|
||||
</Tab>
|
||||
<Tab eventKey="tabItems" className='bg-transparent'>
|
||||
<LibraryItems LibraryId={LibraryId}/>
|
||||
</Tab>
|
||||
<Tab eventKey="tabActivity" className='bg-transparent'>
|
||||
<LibraryActivity LibraryId={LibraryId}/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
)}
|
||||
|
||||
<LibraryLastWatched LibraryId={LibraryId} />
|
||||
</Tab>
|
||||
<Tab eventKey="tabItems" title="Items" className="bg-transparent">
|
||||
<LibraryItems LibraryId={LibraryId} />
|
||||
</Tab>
|
||||
<Tab eventKey="tabActivity" title="Activity" className="bg-transparent">
|
||||
<LibraryActivity LibraryId={LibraryId} />
|
||||
</Tab>
|
||||
<Tab eventKey="tabOptions" title="Options" className="bg-transparent">
|
||||
<LibraryOptions LibraryId={LibraryId} isArchived={data.archived} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,14 @@ function LibraryItems(props) {
|
||||
const [config, setConfig] = useState();
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [sortOrder, setSortOrder] = useState("Title");
|
||||
const [sortAsc, setSortAsc] = useState(true);
|
||||
const [sortAsc, setSortAsc] = useState("all");
|
||||
|
||||
const archive = {
|
||||
all: "all",
|
||||
archived: "true",
|
||||
not_archived: "false",
|
||||
};
|
||||
const [showArchived, setShowArchived] = useState(archive.all);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchConfig = async () => {
|
||||
@@ -69,9 +76,19 @@ function LibraryItems(props) {
|
||||
}
|
||||
|
||||
let filteredData = data;
|
||||
|
||||
if (searchQuery) {
|
||||
filteredData = data.filter((item) => item.Name.toLowerCase().includes(searchQuery.toLowerCase()));
|
||||
if (data) {
|
||||
filteredData = data.filter((item) => {
|
||||
let match = false;
|
||||
if (showArchived == archive.all || item.archived == (showArchived == "true")) {
|
||||
match = true;
|
||||
}
|
||||
if (searchQuery) {
|
||||
match =
|
||||
item.Name.toLowerCase().includes(searchQuery.toLowerCase()) &&
|
||||
(showArchived == archive.all || item.archived == (showArchived == "true"));
|
||||
}
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
if (!data || !config) {
|
||||
@@ -87,7 +104,22 @@ function LibraryItems(props) {
|
||||
|
||||
<div className="d-flex flex-column flex-md-row">
|
||||
<div className="d-flex flex-row w-100">
|
||||
<FormSelect onChange={(e) => sortOrderLogic(e.target.value)} className="my-md-3 w-100 rounded-0 rounded-start">
|
||||
<FormSelect onChange={(e) => setShowArchived(e.target.value)} className="my-md-3 w-100 rounded">
|
||||
<option value="all">
|
||||
<Trans i18nKey="ALL" />
|
||||
</option>
|
||||
<option value="true">
|
||||
<Trans i18nKey="ARCHIVED" />
|
||||
</option>
|
||||
<option value="false">
|
||||
<Trans i18nKey="NOT_ARCHIVED" />
|
||||
</option>
|
||||
</FormSelect>
|
||||
|
||||
<FormSelect
|
||||
onChange={(e) => sortOrderLogic(e.target.value)}
|
||||
className="ms-md-3 my-md-3 w-100 rounded-0 rounded-start"
|
||||
>
|
||||
<option value="Title">
|
||||
<Trans i18nKey="TITLE" />
|
||||
</option>
|
||||
|
||||
137
src/pages/components/library/library-options.jsx
Normal file
137
src/pages/components/library/library-options.jsx
Normal file
@@ -0,0 +1,137 @@
|
||||
import axios from "axios";
|
||||
import i18next from "i18next";
|
||||
import { useState } from "react";
|
||||
import { Container, Row, Col, Modal } from "react-bootstrap";
|
||||
import { Trans } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
function LibraryOptions(props) {
|
||||
const token = localStorage.getItem("token");
|
||||
const [show, setShow] = useState(false);
|
||||
const options = [
|
||||
{
|
||||
description: i18next.t("PURGE_OPTIONS.PURGE_LIBRARY_CACHE"),
|
||||
withActivity: false,
|
||||
isLibraryArchived: true,
|
||||
url: `/api/library/purge`,
|
||||
},
|
||||
{
|
||||
description: i18next.t("PURGE_OPTIONS.PURGE_LIBRARY_CACHE_WITH_ACTIVITY"),
|
||||
withActivity: true,
|
||||
isLibraryArchived: true,
|
||||
url: `/api/library/purge`,
|
||||
},
|
||||
{
|
||||
description: i18next.t("PURGE_OPTIONS.PURGE_LIBRARY_ITEMS_CACHE"),
|
||||
withActivity: false,
|
||||
isLibraryArchived: false,
|
||||
url: `/api/libraryItems/purge`,
|
||||
},
|
||||
{
|
||||
description: i18next.t("PURGE_OPTIONS.PURGE_LIBRARY_ITEMS_CACHE_WITH_ACTIVITY"),
|
||||
withActivity: true,
|
||||
isLibraryArchived: false,
|
||||
url: `/api/libraryItems/purge`,
|
||||
},
|
||||
];
|
||||
const [selectedOption, setSelectedOption] = useState(options[0]);
|
||||
const navigate = useNavigate();
|
||||
|
||||
async function execPurge(url, withActivity) {
|
||||
return await axios
|
||||
.delete(
|
||||
url,
|
||||
|
||||
{
|
||||
data: {
|
||||
id: props.LibraryId,
|
||||
withActivity: withActivity,
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
setShow(false);
|
||||
navigate(-1);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log({ error: error, token: token });
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="Activity">
|
||||
<div className="Heading mb-3">
|
||||
<h1>
|
||||
<Trans i18nKey="ITEM_INFO.ARCHIVED_DATA_OPTIONS" />
|
||||
</h1>
|
||||
</div>
|
||||
<Container className="p-0 m-0">
|
||||
{options
|
||||
.filter((option) => option.isLibraryArchived == props.isArchived)
|
||||
.map((option, index) => (
|
||||
<Row key={index} className="mb-2 me-0">
|
||||
<Col>
|
||||
<span>{option.description}</span>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<button
|
||||
className="btn btn-danger w-25"
|
||||
onClick={() => {
|
||||
setSelectedOption(option);
|
||||
setShow(true);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="ITEM_INFO.PURGE" />
|
||||
</button>
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
|
||||
<Modal
|
||||
show={show}
|
||||
onHide={() => {
|
||||
setShow(false);
|
||||
}}
|
||||
>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<Trans i18nKey="ITEM_INFO.CONFIRM_ACTION" />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p>
|
||||
{i18next.t("ITEM_INFO.CONFIRM_ACTION_MESSAGE") +
|
||||
(selectedOption.withActivity ? ` ${i18next.t("ITEM_INFO.CONFIRM_ACTION_MESSAGE_2")}?` : "?")}
|
||||
</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
onClick={() => {
|
||||
execPurge(selectedOption.url, selectedOption.withActivity);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="ITEM_INFO.PURGE" />
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => {
|
||||
setShow(false);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="CLOSE" />
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LibraryOptions;
|
||||
@@ -1,40 +1,36 @@
|
||||
@import '../variables.module.css';
|
||||
.item-detail-container
|
||||
{
|
||||
color:white;
|
||||
background-color: var(--secondary-background-color);
|
||||
margin: 20px 0;
|
||||
@import "../variables.module.css";
|
||||
.item-detail-container {
|
||||
color: white;
|
||||
background-color: var(--secondary-background-color);
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.item-banner-image
|
||||
{
|
||||
margin-right: 20px;
|
||||
.item-banner-image {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.item-name
|
||||
{
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.item-name {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.item-image
|
||||
{
|
||||
max-width: 200px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
.item-image {
|
||||
max-width: 200px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.item-details div a{
|
||||
text-decoration: none !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.item-details div a:hover{
|
||||
color: var(--secondary-color) !important;
|
||||
}
|
||||
|
||||
.hide-tab-titles {
|
||||
display: none !important;
|
||||
}
|
||||
.item-details div a {
|
||||
text-decoration: none !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.item-details div a:hover {
|
||||
color: var(--secondary-color) !important;
|
||||
}
|
||||
|
||||
.hide-tab-titles {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,12 @@ import "./css/library/libraries.css";
|
||||
import Loading from "./components/general/loading";
|
||||
import LibraryCard from "./components/library/library-card";
|
||||
import ErrorBoundary from "./components/general/ErrorBoundary";
|
||||
import EyeOffFillIcon from 'remixicon-react/EyeOffFillIcon';
|
||||
import EyeFillIcon from 'remixicon-react/EyeFillIcon';
|
||||
import EyeOffFillIcon from "remixicon-react/EyeOffFillIcon";
|
||||
import EyeFillIcon from "remixicon-react/EyeFillIcon";
|
||||
import { Tooltip } from "react-bootstrap";
|
||||
import { Trans } from "react-i18next";
|
||||
import i18next from "i18next";
|
||||
|
||||
|
||||
function Libraries() {
|
||||
const [data, setData] = useState();
|
||||
const [metadata, setMetaData] = useState();
|
||||
@@ -32,8 +31,7 @@ function Libraries() {
|
||||
};
|
||||
|
||||
const fetchLibraries = () => {
|
||||
if(config)
|
||||
{
|
||||
if (config) {
|
||||
const url = `/stats/getLibraryCardStats`;
|
||||
axios
|
||||
.get(url, {
|
||||
@@ -49,9 +47,9 @@ function Libraries() {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
const metadataurl = `/stats/getLibraryMetadata`;
|
||||
const metadataurl = `/stats/getLibraryMetadata`;
|
||||
|
||||
axios
|
||||
axios
|
||||
.get(metadataurl, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.token}`,
|
||||
@@ -67,7 +65,6 @@ function Libraries() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (!config) {
|
||||
fetchConfig();
|
||||
}
|
||||
@@ -75,7 +72,7 @@ function Libraries() {
|
||||
fetchLibraries();
|
||||
const intervalId = setInterval(fetchLibraries, 60000 * 60);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [ config]);
|
||||
}, [config]);
|
||||
|
||||
if (!data || !metadata) {
|
||||
return <Loading />;
|
||||
@@ -84,34 +81,36 @@ function Libraries() {
|
||||
return (
|
||||
<div className="libraries">
|
||||
<div className="d-flex flex-row justify-content-between align-items-center">
|
||||
<h1 className="py-4"><Trans i18nKey="LIBRARIES" /></h1>
|
||||
{
|
||||
showArchived ?
|
||||
<Tooltip title={i18next.t("HIDE_ARCHIVED_LIBRARIES")} className="tooltip-icon-button">
|
||||
<button className="btn" onClick={()=> setShowArchived(!showArchived)}>
|
||||
<EyeFillIcon/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
:
|
||||
<Tooltip title={i18next.t("SHOW_ARCHIVED_LIBRARIES")} className="tooltip-icon-button">
|
||||
<button className="btn" onClick={()=> setShowArchived(!showArchived)}>
|
||||
<EyeOffFillIcon/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
}
|
||||
<h1 className="py-4">
|
||||
<Trans i18nKey="LIBRARIES" />
|
||||
</h1>
|
||||
{data.filter((library) => library.archived === true).length > 0 &&
|
||||
(showArchived ? (
|
||||
<Tooltip title={i18next.t("HIDE_ARCHIVED_LIBRARIES")} className="tooltip-icon-button">
|
||||
<button className="btn" onClick={() => setShowArchived(!showArchived)}>
|
||||
<EyeFillIcon />
|
||||
</button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip title={i18next.t("SHOW_ARCHIVED_LIBRARIES")} className="tooltip-icon-button">
|
||||
<button className="btn" onClick={() => setShowArchived(!showArchived)}>
|
||||
<EyeOffFillIcon />
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
<div xs={1} md={2} lg={4} className="g-0 libraries-container">
|
||||
{data &&
|
||||
data.filter((library) => library.archived ===false || library.archived===showArchived) .sort((a,b) => a.Name-b.Name).map((item) => (
|
||||
<ErrorBoundary key={item.Id} >
|
||||
<LibraryCard data={item} metadata={metadata.find(data => data.Id === item.Id)} base_url={config.hostUrl}/>
|
||||
</ErrorBoundary>
|
||||
|
||||
{data &&
|
||||
data
|
||||
.filter((library) => library.archived === false || library.archived === showArchived)
|
||||
.sort((a, b) => a.Name - b.Name)
|
||||
.map((item) => (
|
||||
<ErrorBoundary key={item.Id}>
|
||||
<LibraryCard data={item} metadata={metadata.find((data) => data.Id === item.Id)} base_url={config.hostUrl} />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user